1#![doc = include_str!("../README.md")]
10#![cfg_attr(not(feature = "std"), no_std)]
11#![cfg_attr(docsrs, feature(doc_cfg))]
12#![warn(
13 missing_copy_implementations,
14 missing_debug_implementations,
15 rust_2018_idioms,
16 unused_lifetimes,
17 unused_qualifications
18)]
19
20#[cfg(all(feature = "alloc", not(feature = "std")))]
21extern crate alloc;
22
23use core::{fmt, str::FromStr};
24
25#[cfg(all(feature = "alloc", not(feature = "std")))]
26use alloc::{format, string::ToString, vec::Vec};
27#[cfg(feature = "std")]
28use std::{format, string::ToString, vec::Vec};
29
30use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
31
32#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
34#[non_exhaustive]
35pub struct LsRules<'a> {
36 #[serde(skip_serializing_if = "Option::is_none")]
37 pub name: Option<&'a str>,
38 #[serde(skip_serializing_if = "Option::is_none")]
39 pub description: Option<&'a str>,
40 #[serde(skip_serializing_if = "Option::is_none")]
41 pub rules: Option<Vec<Rule<'a>>>,
42 #[serde(
43 rename = "denied-remote-domains",
44 skip_serializing_if = "Option::is_none"
45 )]
46 pub denied_remote_domains: Option<Vec<&'a str>>,
47 #[serde(
48 rename = "denied-remote-hosts",
49 skip_serializing_if = "Option::is_none"
50 )]
51 pub denied_remote_hosts: Option<Vec<&'a str>>,
52 #[serde(
53 rename = "denied-remote-addresses",
54 skip_serializing_if = "Option::is_none"
55 )]
56 pub denied_remote_addresses: Option<Vec<&'a str>>,
57 #[serde(
58 rename = "denied-remote-notes",
59 skip_serializing_if = "Option::is_none"
60 )]
61 pub denied_remote_notes: Option<&'a str>,
62}
63
64#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
66#[non_exhaustive]
67pub struct Rule<'a> {
68 pub process: &'a str,
69 #[serde(skip_serializing_if = "Option::is_none")]
70 pub via: Option<&'a str>,
71 #[serde(rename = "remote-addresses", skip_serializing_if = "Option::is_none")]
72 pub remote_addresses: Option<&'a str>,
73 #[serde(rename = "remote-hosts", skip_serializing_if = "Option::is_none")]
74 pub remote_hosts: Option<RemoteHosts<'a>>,
75 #[serde(rename = "remote-domains", skip_serializing_if = "Option::is_none")]
76 pub remote_domains: Option<RemoteDomains<'a>>,
77 #[serde(skip_serializing_if = "Option::is_none", borrow)]
78 pub remote: Option<Remote<'a>>,
79 #[serde(skip_serializing_if = "Option::is_none")]
80 pub direction: Option<Direction<'a>>,
81 #[serde(skip_serializing_if = "Option::is_none")]
82 pub action: Option<Action<'a>>,
83 #[serde(skip_serializing_if = "Option::is_none")]
84 pub priority: Option<Priority<'a>>,
85 #[serde(skip_serializing_if = "Option::is_none")]
86 pub disabled: Option<bool>,
87 #[serde(skip_serializing_if = "Option::is_none")]
88 pub ports: Option<Ports<'a>>,
89 #[serde(skip_serializing_if = "Option::is_none")]
90 pub protocol: Option<&'a str>,
91 #[serde(skip_serializing_if = "Option::is_none")]
92 pub notes: Option<&'a str>,
93}
94
95#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
96#[serde(untagged)]
97#[non_exhaustive]
98pub enum RemoteHosts<'a> {
99 Single(&'a str),
100 Multiple(Vec<&'a str>),
101}
102
103#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
104#[serde(untagged)]
105#[non_exhaustive]
106pub enum RemoteDomains<'a> {
107 Single(&'a str),
108 Multiple(Vec<&'a str>),
109}
110
111#[derive(Clone, Debug, PartialEq)]
112#[non_exhaustive]
113pub enum Remote<'a> {
114 Any,
115 LocalNet,
116 Multicast,
117 Broadcast,
118 Bonjour,
119 DnsServers,
120 Bpf,
121 Unknown(&'a str),
122}
123
124impl<'a> Serialize for Remote<'a> {
125 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
126 where
127 S: Serializer,
128 {
129 match self {
130 Remote::Any => serializer.serialize_str("any"),
131 Remote::LocalNet => serializer.serialize_str("local-net"),
132 Remote::Multicast => serializer.serialize_str("multicast"),
133 Remote::Broadcast => serializer.serialize_str("broadcast"),
134 Remote::Bonjour => serializer.serialize_str("bonjour"),
135 Remote::DnsServers => serializer.serialize_str("dns-servers"),
136 Remote::Bpf => serializer.serialize_str("bpf"),
137 Remote::Unknown(s) => serializer.serialize_str(s),
138 }
139 }
140}
141
142struct RemoteVisitor;
143
144impl<'de> Visitor<'de> for RemoteVisitor {
145 type Value = Remote<'de>;
146
147 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
148 formatter.write_str("a string value")
149 }
150
151 fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E>
152 where
153 E: serde::de::Error,
154 {
155 match &v.to_lowercase()[..] {
156 "any" => Ok(Remote::Any),
157 "local-net" => Ok(Remote::LocalNet),
158 "multicast" => Ok(Remote::Multicast),
159 "broadcast" => Ok(Remote::Broadcast),
160 "bonjour" => Ok(Remote::Bonjour),
161 "dns-servers" => Ok(Remote::DnsServers),
162 "bpf" => Ok(Remote::Bpf),
163 _ => Ok(Remote::Unknown(v)),
164 }
165 }
166}
167
168impl<'de, 'a> Deserialize<'de> for Remote<'a>
169where
170 'de: 'a,
171{
172 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
173 where
174 D: Deserializer<'de>,
175 {
176 deserializer.deserialize_str(RemoteVisitor)
177 }
178}
179
180#[derive(Clone, Debug, PartialEq)]
181#[non_exhaustive]
182pub enum Direction<'a> {
183 Incoming,
184 Outgoing,
185 Unknown(&'a str),
186}
187
188impl<'a> Default for Direction<'a> {
189 fn default() -> Self {
190 Direction::Outgoing
191 }
192}
193
194impl<'a> Serialize for Direction<'a> {
195 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
196 where
197 S: Serializer,
198 {
199 match self {
200 Direction::Incoming => serializer.serialize_str("incoming"),
201 Direction::Outgoing => serializer.serialize_str("outgoing"),
202 Direction::Unknown(s) => serializer.serialize_str(s),
203 }
204 }
205}
206
207struct DirectionVisitor;
208
209impl<'de> Visitor<'de> for DirectionVisitor {
210 type Value = Direction<'de>;
211
212 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
213 formatter.write_str("a string value")
214 }
215
216 fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E>
217 where
218 E: serde::de::Error,
219 {
220 match &v.to_lowercase()[..] {
221 "incoming" => Ok(Direction::Incoming),
222 "outgoing" => Ok(Direction::Outgoing),
223 _ => Ok(Direction::Unknown(v)),
224 }
225 }
226}
227
228impl<'de, 'a> Deserialize<'de> for Direction<'a>
229where
230 'de: 'a,
231{
232 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
233 where
234 D: Deserializer<'de>,
235 {
236 deserializer.deserialize_str(DirectionVisitor)
237 }
238}
239
240#[derive(Clone, Debug, PartialEq)]
241#[non_exhaustive]
242pub enum Action<'a> {
243 Allow,
244 Deny,
245 Ask,
246 Unknown(&'a str),
247}
248
249impl<'a> Default for Action<'a> {
250 fn default() -> Self {
251 Action::Ask
252 }
253}
254
255impl<'a> Serialize for Action<'a> {
256 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
257 where
258 S: Serializer,
259 {
260 match self {
261 Action::Allow => serializer.serialize_str("allow"),
262 Action::Deny => serializer.serialize_str("deny"),
263 Action::Ask => serializer.serialize_str("ask"),
264 Action::Unknown(s) => serializer.serialize_str(s),
265 }
266 }
267}
268
269struct ActionVisitor;
270
271impl<'de> Visitor<'de> for ActionVisitor {
272 type Value = Action<'de>;
273
274 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
275 formatter.write_str("a string value")
276 }
277
278 fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E>
279 where
280 E: serde::de::Error,
281 {
282 match &v.to_lowercase()[..] {
283 "allow" => Ok(Action::Allow),
284 "deny" => Ok(Action::Deny),
285 "ask" => Ok(Action::Ask),
286 _ => Ok(Action::Unknown(v)),
287 }
288 }
289}
290
291impl<'de, 'a> Deserialize<'de> for Action<'a>
292where
293 'de: 'a,
294{
295 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
296 where
297 D: Deserializer<'de>,
298 {
299 deserializer.deserialize_str(ActionVisitor)
300 }
301}
302
303#[derive(Clone, Debug, PartialEq)]
304#[non_exhaustive]
305pub enum Priority<'a> {
306 Default,
307 High,
308 Unknown(&'a str),
309}
310
311impl<'a> Default for Priority<'a> {
312 fn default() -> Self {
313 Priority::Default
314 }
315}
316
317impl<'a> Serialize for Priority<'a> {
318 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
319 where
320 S: Serializer,
321 {
322 match self {
323 Priority::Default => serializer.serialize_str("default"),
324 Priority::High => serializer.serialize_str("high"),
325 Priority::Unknown(s) => serializer.serialize_str(s),
326 }
327 }
328}
329
330struct PriorityVisitor;
331
332impl<'de> Visitor<'de> for PriorityVisitor {
333 type Value = Priority<'de>;
334
335 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
336 formatter.write_str("a string value")
337 }
338
339 fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E>
340 where
341 E: serde::de::Error,
342 {
343 match &v.to_lowercase()[..] {
344 "default" => Ok(Priority::Default),
345 "high" => Ok(Priority::High),
346 _ => Ok(Priority::Unknown(v)),
347 }
348 }
349}
350
351impl<'de, 'a> Deserialize<'de> for Priority<'a>
352where
353 'de: 'a,
354{
355 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
356 where
357 D: Deserializer<'de>,
358 {
359 deserializer.deserialize_str(PriorityVisitor)
360 }
361}
362
363#[derive(Clone, Debug, PartialEq)]
364#[non_exhaustive]
365pub enum Ports<'a> {
366 Any,
367 Single(u16),
368 Range(u16, u16),
369 Unknown(&'a str),
370}
371
372impl<'a> Default for Ports<'a> {
373 fn default() -> Self {
374 Ports::Any
375 }
376}
377
378impl<'a> Serialize for Ports<'a> {
379 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
380 where
381 S: Serializer,
382 {
383 match self {
384 Ports::Any => serializer.serialize_str("any"),
385 Ports::Single(p) => serializer.serialize_str(&p.to_string()),
386 Ports::Range(p1, p2) => {
387 serializer.serialize_str(&format!("{}-{}", &p1.to_string(), &p2.to_string()))
388 }
389 Ports::Unknown(s) => serializer.serialize_str(s),
390 }
391 }
392}
393
394struct PortsVisitor;
395
396impl<'de> Visitor<'de> for PortsVisitor {
397 type Value = Ports<'de>;
398
399 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
400 formatter.write_str("a string, integer, or range value")
401 }
402
403 fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E>
404 where
405 E: serde::de::Error,
406 {
407 if matches!(&v.to_lowercase()[..], "any") {
408 return Ok(Ports::Any);
409 }
410
411 if let Ok(v) = u16::from_str(v) {
412 return Ok(Ports::Single(v));
413 }
414
415 let ports: Vec<&str> = v.split('-').collect();
416 if ports.len() == 2 {
417 if let Ok(p1) = u16::from_str(ports[0]) {
418 if let Ok(p2) = u16::from_str(ports[1]) {
419 return Ok(Ports::Range(p1, p2));
420 }
421 }
422 }
423
424 Ok(Ports::Unknown(v))
425 }
426}
427
428impl<'de, 'a> Deserialize<'de> for Ports<'a>
429where
430 'de: 'a,
431{
432 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
433 where
434 D: Deserializer<'de>,
435 {
436 deserializer.deserialize_str(PortsVisitor)
437 }
438}
439
440#[cfg(test)]
441mod tests {
442 use super::*;
443
444 #[cfg(all(feature = "alloc", not(feature = "std")))]
445 use alloc::vec;
446
447 #[test]
448 fn test_default_rules() {
449 let rules: LsRules<'_> = LsRules::default();
450 assert_eq!(rules.name, None);
451 assert_eq!(rules.description, None);
452 assert_eq!(rules.rules, None);
453 assert_eq!(rules.denied_remote_domains, None);
454 assert_eq!(rules.denied_remote_hosts, None);
455 assert_eq!(rules.denied_remote_addresses, None);
456 assert_eq!(rules.denied_remote_notes, None);
457 }
458
459 #[test]
460 fn test_empty_rules() -> Result<(), serde_json::Error> {
461 let json = r"
462{
463}
464";
465 let rules: LsRules<'_> = serde_json::from_str(json)?;
466 assert_eq!(rules.name, None);
467 assert_eq!(rules.description, None);
468 Ok(())
469 }
470
471 #[test]
472 fn test_metadata() -> Result<(), serde_json::Error> {
473 let json = r#"
474{
475 "name": "Social Media Block List",
476 "description": "Blocks access to popular social media sites."
477}
478"#;
479 let rules: LsRules<'_> = serde_json::from_str(json)?;
480 assert_eq!(rules.name, Some("Social Media Block List"));
481 assert_eq!(
482 rules.description,
483 Some("Blocks access to popular social media sites.")
484 );
485 Ok(())
486 }
487
488 #[test]
489 fn test_denied_remote_domains() -> Result<(), serde_json::Error> {
490 let json = r#"
491{
492 "name": "Social Media Block List",
493 "description": "Blocks access to popular social media sites.",
494 "denied-remote-domains": ["facebook.com", "twitter.com", "youtube.com"]
495}
496"#;
497 let rules: LsRules<'_> = serde_json::from_str(json)?;
498 assert_eq!(rules.name, Some("Social Media Block List"));
499 assert_eq!(
500 rules.description,
501 Some("Blocks access to popular social media sites.")
502 );
503 assert_eq!(
504 rules.denied_remote_domains,
505 Some(vec!["facebook.com", "twitter.com", "youtube.com"])
506 );
507 Ok(())
508 }
509
510 #[test]
511 fn test_basic_rule() -> Result<(), serde_json::Error> {
512 let json = r#"
513{
514 "name": "LaunchBar Software Update",
515 "description": "This rule allows LaunchBar to check for updates.",
516 "rules": [
517 {
518 "action": "allow",
519 "process": "/Applications/LaunchBar.app/Contents/MacOS/LaunchBar",
520 "remote-hosts": "sw-update.obdev.at"
521 }
522 ]
523}
524"#;
525 let rules: LsRules<'_> = serde_json::from_str(json)?;
526 assert_eq!(rules.name, Some("LaunchBar Software Update"));
527 assert_eq!(
528 rules.description,
529 Some("This rule allows LaunchBar to check for updates.")
530 );
531
532 let rules = rules.rules.expect("expecting rules");
533 assert_eq!(rules.len(), 1);
534 let rule = rules.first().expect("first rule to exist");
535 assert_eq!(rule.action, Some(Action::Allow));
536 assert_eq!(
537 rule.process,
538 "/Applications/LaunchBar.app/Contents/MacOS/LaunchBar"
539 );
540 assert_eq!(
541 rule.remote_hosts,
542 Some(RemoteHosts::Single("sw-update.obdev.at"))
543 );
544 Ok(())
545 }
546
547 #[test]
548 fn test_multiple_hosts() -> Result<(), serde_json::Error> {
549 let json = r#"
550{
551 "name": "LaunchBar Software Update",
552 "description": "This rule allows LaunchBar to check for updates.",
553 "rules": [
554 {
555 "action": "allow",
556 "process": "/Applications/LaunchBar.app/Contents/MacOS/LaunchBar",
557 "remote-hosts": ["sw-update.obdev.at", "example.com"]
558 }
559 ]
560}
561"#;
562 let rules: LsRules<'_> = serde_json::from_str(json)?;
563 assert_eq!(rules.name, Some("LaunchBar Software Update"));
564 assert_eq!(
565 rules.description,
566 Some("This rule allows LaunchBar to check for updates.")
567 );
568
569 let rules = rules.rules.expect("expecting rules");
570 assert_eq!(rules.len(), 1);
571 let rule = rules.first().expect("first rule to exist");
572 assert_eq!(rule.action, Some(Action::Allow));
573 assert_eq!(
574 rule.process,
575 "/Applications/LaunchBar.app/Contents/MacOS/LaunchBar"
576 );
577 assert_eq!(
578 rule.remote_hosts,
579 Some(RemoteHosts::Multiple(vec![
580 "sw-update.obdev.at",
581 "example.com"
582 ]))
583 );
584 Ok(())
585 }
586
587 #[test]
588 fn test_remote_enum_any() -> Result<(), serde_json::Error> {
589 let json = r#"
590{
591 "rules": [
592 {
593 "action": "allow",
594 "process": "/Applications/LaunchBar.app/Contents/MacOS/LaunchBar",
595 "remote": "any"
596 }
597 ]
598}
599"#;
600 let rules: LsRules<'_> = serde_json::from_str(json)?;
601 let rules = rules.rules.expect("expecting rules");
602 assert_eq!(rules.len(), 1);
603 let rule = rules.first().expect("first rule to exist");
604 assert_eq!(rule.action, Some(Action::Allow));
605 assert_eq!(
606 rule.process,
607 "/Applications/LaunchBar.app/Contents/MacOS/LaunchBar"
608 );
609 assert_eq!(rule.remote, Some(Remote::Any));
610 Ok(())
611 }
612
613 #[test]
614 fn test_remote_enum_local_net() -> Result<(), serde_json::Error> {
615 let json = r#"
616{
617 "rules": [
618 {
619 "action": "allow",
620 "process": "/Applications/LaunchBar.app/Contents/MacOS/LaunchBar",
621 "remote": "local-net"
622 }
623 ]
624}
625"#;
626 let rules: LsRules<'_> = serde_json::from_str(json)?;
627 let rules = rules.rules.expect("expecting rules");
628 assert_eq!(rules.len(), 1);
629 let rule = rules.first().expect("first rule to exist");
630 assert_eq!(rule.action, Some(Action::Allow));
631 assert_eq!(
632 rule.process,
633 "/Applications/LaunchBar.app/Contents/MacOS/LaunchBar"
634 );
635 assert_eq!(rule.remote, Some(Remote::LocalNet));
636 Ok(())
637 }
638
639 #[test]
640 fn test_remote_enum_unknown() -> Result<(), serde_json::Error> {
641 let json = r#"
642{
643 "rules": [
644 {
645 "action": "allow",
646 "process": "/Applications/LaunchBar.app/Contents/MacOS/LaunchBar",
647 "remote": "my-custom"
648 }
649 ]
650}
651"#;
652 let rules: LsRules<'_> = serde_json::from_str(json)?;
653 let rules = rules.rules.expect("expecting rules");
654 assert_eq!(rules.len(), 1);
655 let rule = rules.first().expect("first rule to exist");
656 assert_eq!(rule.action, Some(Action::Allow));
657 assert_eq!(
658 rule.process,
659 "/Applications/LaunchBar.app/Contents/MacOS/LaunchBar"
660 );
661 assert_eq!(rule.remote, Some(Remote::Unknown("my-custom")));
662 Ok(())
663 }
664
665 #[test]
666 fn test_ports_any() -> Result<(), serde_json::Error> {
667 let json = r#"
668{
669 "rules": [
670 {
671 "action": "allow",
672 "process": "/Applications/Safari.app/Contents/MacOS/Safari",
673 "remote": "any",
674 "ports": "any"
675 }
676 ]
677}
678"#;
679 let rules: LsRules<'_> = serde_json::from_str(json)?;
680 let rules = rules.rules.expect("expecting rules");
681 assert_eq!(rules.len(), 1);
682 let rule = rules.first().expect("first rule to exist");
683 assert_eq!(rule.ports, Some(Ports::Any));
684 Ok(())
685 }
686
687 #[test]
688 fn test_ports_single() -> Result<(), serde_json::Error> {
689 let json = r#"
690{
691 "rules": [
692 {
693 "action": "allow",
694 "process": "/Applications/Safari.app/Contents/MacOS/Safari",
695 "remote": "any",
696 "ports": "443"
697 }
698 ]
699}
700"#;
701 let rules: LsRules<'_> = serde_json::from_str(json)?;
702 let rules = rules.rules.expect("expecting rules");
703 assert_eq!(rules.len(), 1);
704 let rule = rules.first().expect("first rule to exist");
705 assert_eq!(rule.ports, Some(Ports::Single(443)));
706 Ok(())
707 }
708
709 #[test]
710 fn test_ports_range() -> Result<(), serde_json::Error> {
711 let json = r#"
712{
713 "rules": [
714 {
715 "action": "allow",
716 "process": "/Applications/Safari.app/Contents/MacOS/Safari",
717 "remote": "any",
718 "ports": "80-443"
719 }
720 ]
721}
722"#;
723 let rules: LsRules<'_> = serde_json::from_str(json)?;
724 let rules = rules.rules.expect("expecting rules");
725 assert_eq!(rules.len(), 1);
726 let rule = rules.first().expect("first rule to exist");
727 assert_eq!(rule.ports, Some(Ports::Range(80, 443)));
728 Ok(())
729 }
730
731 #[test]
732 fn test_ports_custom() -> Result<(), serde_json::Error> {
733 let json = r#"
734{
735 "rules": [
736 {
737 "action": "allow",
738 "process": "/Applications/Safari.app/Contents/MacOS/Safari",
739 "remote": "any",
740 "ports": "my-custom"
741 }
742 ]
743}
744"#;
745 let rules: LsRules<'_> = serde_json::from_str(json)?;
746 let rules = rules.rules.expect("expecting rules");
747 assert_eq!(rules.len(), 1);
748 let rule = rules.first().expect("first rule to exist");
749 assert_eq!(rule.ports, Some(Ports::Unknown("my-custom")));
750 Ok(())
751 }
752}