rpsl_parser/parser/main.rs
1use super::component;
2use crate::rpsl;
3use nom::{
4 branch::alt,
5 bytes::complete::tag,
6 character::complete::multispace0,
7 combinator::all_consuming,
8 error::Error,
9 multi::{many0, many1},
10 sequence::delimited,
11 Finish,
12};
13
14/// Parse a string containing a RPSL object.
15///
16/// # Errors
17/// Returns nom `Error` if any error occurs during parsing.
18///
19/// # Examples
20/// ```
21/// # use rpsl_parser::{parse_rpsl_object, rpsl};
22/// # fn main() -> Result<(), nom::error::Error<&'static str>> {
23/// let role_acme = "
24/// role: ACME Company
25/// address: Packet Street 6
26/// address: 128 Series of Tubes
27/// address: Internet
28/// email: rpsl-parser@github.com
29/// nic-hdl: RPSL1-RIPE
30/// source: RIPE
31/// ";
32/// let parsed_attributes = parse_rpsl_object(role_acme)?;
33/// assert_eq!(
34/// parsed_attributes,
35/// rpsl::Object::new(vec![
36/// rpsl::Attribute::new("role".to_string(), vec![Some("ACME Company".to_string())]),
37/// rpsl::Attribute::new(
38/// "address".to_string(),
39/// vec![Some("Packet Street 6".to_string())],
40/// ),
41/// rpsl::Attribute::new(
42/// "address".to_string(),
43/// vec![Some("128 Series of Tubes".to_string())],
44/// ),
45/// rpsl::Attribute::new("address".to_string(), vec![Some("Internet".to_string())]),
46/// rpsl::Attribute::new(
47/// "email".to_string(),
48/// vec![Some("rpsl-parser@github.com".to_string())],
49/// ),
50/// rpsl::Attribute::new("nic-hdl".to_string(), vec![Some("RPSL1-RIPE".to_string())]),
51/// rpsl::Attribute::new("source".to_string(), vec![Some("RIPE".to_string())]),
52/// ])
53/// );
54/// # Ok(())
55/// # }
56/// ```
57///
58/// Values spread over multiple lines can be parsed too.
59/// ```
60/// # use rpsl_parser::{parse_rpsl_object, rpsl};
61/// # fn main() -> Result<(), nom::error::Error<&'static str>> {
62/// let multi_value = "
63/// remarks: Value 1
64/// Value 2
65/// ";
66/// assert_eq!(
67/// parse_rpsl_object(multi_value)?,
68/// rpsl::Object::new(vec![rpsl::Attribute::new(
69/// "remarks".to_string(),
70/// vec![Some("Value 1".to_string()), Some("Value 2".to_string())]
71/// ),])
72/// );
73/// # Ok(())
74/// # }
75/// ```
76///
77/// Empty values are valid RPSL and are represented as `None`.
78/// ```
79/// # use rpsl_parser::{parse_rpsl_object, rpsl};
80/// # fn main() -> Result<(), nom::error::Error<&'static str>> {
81/// let empty_value = "
82/// as-name: REMARKABLE
83/// remarks:
84/// remarks: ^^^^^^^^^^ nothing here
85/// ";
86/// assert_eq!(
87/// parse_rpsl_object(empty_value)?,
88/// rpsl::Object::new(vec![
89/// rpsl::Attribute::new("as-name".to_string(), vec![Some("REMARKABLE".to_string())]),
90/// rpsl::Attribute::new("remarks".to_string(), vec![None]),
91/// rpsl::Attribute::new(
92/// "remarks".to_string(),
93/// vec![Some("^^^^^^^^^^ nothing here".to_string())]
94/// ),
95/// ])
96/// );
97/// # Ok(())
98/// # }
99/// ```
100///
101/// The same goes for values containing only whitespace.
102/// Since whitespace to the left of a value is trimmed, they are equivalent to an empty value.
103///
104/// ```
105/// # use rpsl_parser::{parse_rpsl_object, rpsl};
106/// # fn main() -> Result<(), nom::error::Error<&'static str>> {
107/// let whitespace_value = "
108/// as-name: REMARKABLE
109/// remarks:
110/// remarks: ^^^^^^^^^^ nothing but hot air
111/// ";
112/// assert_eq!(
113/// parse_rpsl_object(whitespace_value)?,
114/// rpsl::Object::new(vec![
115/// rpsl::Attribute::new("as-name".to_string(), vec![Some("REMARKABLE".to_string())]),
116/// rpsl::Attribute::new("remarks".to_string(), vec![None]),
117/// rpsl::Attribute::new(
118/// "remarks".to_string(),
119/// vec![Some("^^^^^^^^^^ nothing but hot air".to_string())]
120/// ),
121/// ])
122/// );
123/// # Ok(())
124/// # }
125/// ```
126pub fn parse_rpsl_object(rpsl: &str) -> Result<rpsl::Object, Error<&str>> {
127 let (_, object) = all_consuming(delimited(
128 multispace0,
129 many1(component::attribute),
130 multispace0,
131 ))(rpsl)
132 .finish()?;
133
134 Ok(rpsl::Object::from(object))
135}
136
137/// Parse a string containing a whois server response into a vector of RPSL objects.
138/// # Errors
139/// Returns nom `Error` if any error occurs during parsing.
140///
141/// # Examples
142/// ```
143/// # use rpsl_parser::{parse_whois_server_response, rpsl};
144/// # fn main() -> Result<(), nom::error::Error<&'static str>> {
145/// let whois_response = "
146/// ASNumber: 32934
147/// ASName: FACEBOOK
148/// ASHandle: AS32934
149/// RegDate: 2004-08-24
150/// Updated: 2012-02-24
151/// Comment: Please send abuse reports to abuse@facebook.com
152/// Ref: https://rdap.arin.net/registry/autnum/32934
153///
154///
155/// OrgName: Facebook, Inc.
156/// OrgId: THEFA-3
157/// Address: 1601 Willow Rd.
158/// City: Menlo Park
159/// StateProv: CA
160/// PostalCode: 94025
161/// Country: US
162/// RegDate: 2004-08-11
163/// Updated: 2012-04-17
164/// Ref: https://rdap.arin.net/registry/entity/THEFA-3
165/// ";
166/// let parsed_attributes = parse_whois_server_response(whois_response)?;
167/// assert_eq!(
168/// parsed_attributes,
169/// rpsl::ObjectCollection::new(vec![
170/// rpsl::Object::new(vec![
171/// rpsl::Attribute::new("ASNumber".to_string(), vec![Some("32934".to_string())]),
172/// rpsl::Attribute::new("ASName".to_string(), vec![Some("FACEBOOK".to_string())]),
173/// rpsl::Attribute::new("ASHandle".to_string(), vec![Some("AS32934".to_string())]),
174/// rpsl::Attribute::new("RegDate".to_string(), vec![Some("2004-08-24".to_string())]),
175/// rpsl::Attribute::new("Updated".to_string(), vec![Some("2012-02-24".to_string())]),
176/// rpsl::Attribute::new("Comment".to_string(), vec![Some("Please send abuse reports to abuse@facebook.com".to_string())]),
177/// rpsl::Attribute::new("Ref".to_string(), vec![Some("https://rdap.arin.net/registry/autnum/32934".to_string())]),
178/// ]),
179/// rpsl::Object::new(vec![
180/// rpsl::Attribute::new("OrgName".to_string(), vec![Some("Facebook, Inc.".to_string())]),
181/// rpsl::Attribute::new("OrgId".to_string(), vec![Some("THEFA-3".to_string())]),
182/// rpsl::Attribute::new("Address".to_string(), vec![Some("1601 Willow Rd.".to_string())]),
183/// rpsl::Attribute::new("City".to_string(), vec![Some("Menlo Park".to_string())]),
184/// rpsl::Attribute::new("StateProv".to_string(), vec![Some("CA".to_string())]),
185/// rpsl::Attribute::new("PostalCode".to_string(), vec![Some("94025".to_string())]),
186/// rpsl::Attribute::new("Country".to_string(), vec![Some("US".to_string())]),
187/// rpsl::Attribute::new("RegDate".to_string(), vec![Some("2004-08-11".to_string())]),
188/// rpsl::Attribute::new("Updated".to_string(), vec![Some("2012-04-17".to_string())]),
189/// rpsl::Attribute::new("Ref".to_string(), vec![Some("https://rdap.arin.net/registry/entity/THEFA-3".to_string())]),
190/// ]),
191/// ])
192/// );
193/// # Ok(())
194/// # }
195/// ```
196pub fn parse_whois_server_response(response: &str) -> Result<rpsl::ObjectCollection, Error<&str>> {
197 let rpsl_object = many1(component::attribute);
198
199 let (_, objects) = all_consuming(many1(delimited(
200 many0(alt((component::server_message, tag("\n")))),
201 rpsl_object,
202 many0(alt((component::server_message, tag("\n")))), // TODO: DRY
203 )))(response)
204 .finish()?;
205
206 Ok(rpsl::ObjectCollection::from(objects))
207}
208
209#[cfg(test)]
210mod tests {
211 use super::*;
212 use crate::rpsl;
213
214 #[test]
215 fn parse_valid_rpsl_object() {
216 let rpsl = concat!(
217 "\n",
218 "role: Twelve99 Routing Registry\n",
219 "remarks:\n",
220 "remarks: This is a remark.\n",
221 "address: Arelion Sweden AB\n",
222 "address: Evenemangsgatan 2C\n",
223 "address: SE-169 79 SOLNA\n",
224 "address: Sweden\n",
225 "e-mail: routing-registry@99.net\n",
226 "nic-hdl: TRR2-RIPE\n",
227 "notify: routing-registry@99.net\n",
228 "mnt-by: Twelve99-IRR-MNT\n",
229 "created: 2002-05-27T15:05:16Z\n",
230 "last-modified: 2023-01-30T11:49:56Z\n",
231 "source: RIPE\n",
232 "\n",
233 "\n",
234 );
235 let expected = rpsl::Object::new(vec![
236 rpsl::Attribute::new(
237 "role".to_string(),
238 vec![Some("Twelve99 Routing Registry".to_string())],
239 ),
240 rpsl::Attribute::new("remarks".to_string(), vec![None]),
241 rpsl::Attribute::new(
242 "remarks".to_string(),
243 vec![Some("This is a remark.".to_string())],
244 ),
245 rpsl::Attribute::new(
246 "address".to_string(),
247 vec![Some("Arelion Sweden AB".to_string())],
248 ),
249 rpsl::Attribute::new(
250 "address".to_string(),
251 vec![Some("Evenemangsgatan 2C".to_string())],
252 ),
253 rpsl::Attribute::new(
254 "address".to_string(),
255 vec![Some("SE-169 79 SOLNA".to_string())],
256 ),
257 rpsl::Attribute::new("address".to_string(), vec![Some("Sweden".to_string())]),
258 rpsl::Attribute::new(
259 "e-mail".to_string(),
260 vec![Some("routing-registry@99.net".to_string())],
261 ),
262 rpsl::Attribute::new("nic-hdl".to_string(), vec![Some("TRR2-RIPE".to_string())]),
263 rpsl::Attribute::new(
264 "notify".to_string(),
265 vec![Some("routing-registry@99.net".to_string())],
266 ),
267 rpsl::Attribute::new(
268 "mnt-by".to_string(),
269 vec![Some("Twelve99-IRR-MNT".to_string())],
270 ),
271 rpsl::Attribute::new(
272 "created".to_string(),
273 vec![Some("2002-05-27T15:05:16Z".to_string())],
274 ),
275 rpsl::Attribute::new(
276 "last-modified".to_string(),
277 vec![Some("2023-01-30T11:49:56Z".to_string())],
278 ),
279 rpsl::Attribute::new("source".to_string(), vec![Some("RIPE".to_string())]),
280 ]);
281
282 assert_eq!(parse_rpsl_object(rpsl).unwrap(), expected);
283 }
284
285 #[test]
286 fn parse_valid_server_response() {
287 let rpsl = concat!(
288 "as-block: AS12557 - AS13223\n",
289 "descr: RIPE NCC ASN block\n",
290 "remarks: These AS Numbers are assigned to network operators in the RIPE NCC service region.\n",
291 "mnt-by: RIPE-NCC-HM-MNT\n",
292 "created: 2018-11-22T15:27:24Z\n",
293 "last-modified: 2018-11-22T15:27:24Z\n",
294 "source: RIPE\n",
295 "\n",
296 "% Information related to 'AS13030\n",
297 "\n",
298 "% Abuse contact for 'AS13030' is 'abuse@init7.net'\n",
299 "\n",
300 "aut-num: AS13030\n",
301 "as-name: INIT7\n",
302 "org: ORG-ISA1-RIPE\n",
303 "remarks: Init7 Global Backbone\n",
304 "\n",
305 "organisation: ORG-ISA1-RIPE\n",
306 "org-name: Init7 (Switzerland) Ltd.\n",
307 "\n"
308 );
309 let expected = rpsl::ObjectCollection::new(vec![
310 rpsl::Object::new(vec![
311 rpsl::Attribute::new(
312 "as-block".to_string(),
313 vec![Some("AS12557 - AS13223".to_string())],
314 ),
315 rpsl::Attribute::new(
316 "descr".to_string(),
317 vec![Some("RIPE NCC ASN block".to_string())],
318 ),
319 rpsl::Attribute::new(
320 "remarks".to_string(),
321 vec![Some(
322 "These AS Numbers are assigned to network operators in the RIPE NCC service region.".to_string(),
323 )],
324 ),
325 rpsl::Attribute::new(
326 "mnt-by".to_string(),
327 vec![Some("RIPE-NCC-HM-MNT".to_string())],
328 ),
329 rpsl::Attribute::new(
330 "created".to_string(),
331 vec![Some("2018-11-22T15:27:24Z".to_string())],
332 ),
333 rpsl::Attribute::new(
334 "last-modified".to_string(),
335 vec![Some("2018-11-22T15:27:24Z".to_string())],
336 ),
337 rpsl::Attribute::new("source".to_string(), vec![Some("RIPE".to_string())]),
338 ]),
339 rpsl::Object::new(vec![
340 rpsl::Attribute::new("aut-num".to_string(), vec![Some("AS13030".to_string())]),
341 rpsl::Attribute::new("as-name".to_string(), vec![Some("INIT7".to_string())]),
342 rpsl::Attribute::new("org".to_string(), vec![Some("ORG-ISA1-RIPE".to_string())]),
343 rpsl::Attribute::new(
344 "remarks".to_string(),
345 vec![Some("Init7 Global Backbone".to_string())],
346 ),
347 ]),
348 rpsl::Object::new(vec![
349 rpsl::Attribute::new(
350 "organisation".to_string(),
351 vec![Some("ORG-ISA1-RIPE".to_string())],
352 ),
353 rpsl::Attribute::new(
354 "org-name".to_string(),
355 vec![Some("Init7 (Switzerland) Ltd.".to_string())],
356 ),
357 ]),
358 ]);
359
360 assert_eq!(parse_whois_server_response(rpsl).unwrap(), expected);
361 }
362}