ls_rules/
lib.rs

1// Copyright 2022 Bryant Luk
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9#![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/// The container for all data.
33#[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/// A specific rule.
65#[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}