links_domainmap/
lib.rs

1//! A map with [domain name][Domain] keys, with support for wildcards
2//!
3//! A [`DomainMap<T>`] holds "[reference identifiers]" (domain names possibly
4//! with wildcards) as [`Domain`]s. A [`DomainMap`] can be indexed using a
5//! [`Domain`], which stores either a "[reference identifier]" (for matching
6//! methods, e.g. `get` or `get_mut`) or a "[presented identifier]" (for
7//! equality-comparing methods, e.g. `get_eq` or `remove`).
8//!
9//! # Cargo features
10//!
11//! - `serde`: Enable `serde` serialization and deserialization for `DomainMap`
12//!   and `Domain`
13//!
14//! # Example usage
15//!
16//! ```rust
17//! use links_domainmap::{Domain, DomainMap};
18//!
19//! # use links_domainmap::ParseError;
20//! # fn main() -> Result<(), ParseError> {
21//! // Create a new `DomainMap` with `u32` values
22//! let mut domainmap = DomainMap::<u32>::new(); // or `with_capacity()`
23//!
24//! // Set a value for `example.com`
25//! domainmap.set(Domain::presented("example.com")?, 5);
26//!
27//! // Set a value for the wildcard domain `*.example.net`
28//! domainmap.set(Domain::presented("*.example.net")?, 100);
29//!
30//! // Get the value for the domain matching `example.com`
31//! assert_eq!(domainmap.get(&Domain::reference("example.com")?), Some(&5));
32//!
33//! // Get the value for the domain matching `foo.example.net`
34//! assert_eq!(
35//! 	domainmap.get(&Domain::reference("foo.example.net")?),
36//! 	Some(&100)
37//! );
38//!
39//! // Get the value for the domain `*.example.net` (using `==` internally)
40//! assert_eq!(
41//! 	domainmap.get_eq(&Domain::presented("*.example.net")?),
42//! 	Some(&100)
43//! );
44//!
45//! // Try to get the value for the domain matching `a.b.c.example.net`
46//! assert_eq!(
47//! 	domainmap.get(&Domain::reference("a.b.c.example.net")?),
48//! 	None // Wildcards only work for one label
49//! );
50//!
51//! // Update the value for `example.com`
52//! let old_value = domainmap.set(Domain::presented("example.com")?, 50);
53//! assert_eq!(old_value, Some(5));
54//!
55//! // Modify the value for the domain matching `foo.example.net`
56//! let val = domainmap.get_mut(&Domain::reference("foo.example.net")?);
57//! if let Some(val) = val {
58//! 	*val += 1;
59//! 	assert_eq!(val, &101);
60//! }
61//!
62//! // Set a value for `www.example.net`, overriding the wildcard `*.example.net`
63//! domainmap.set(Domain::presented("www.example.net")?, 250);
64//!
65//! // The wildcard still exists, but is overridden for `www.example.net`
66//! assert_eq!(
67//! 	domainmap.get(&Domain::reference("www.example.net")?),
68//! 	Some(&250)
69//! );
70//! assert_eq!(
71//! 	domainmap.get(&Domain::reference("other.example.net")?),
72//! 	Some(&101)
73//! );
74//!
75//! // Remove the entry for `example.com`
76//! let old_value = domainmap.remove(&Domain::presented("example.com")?);
77//! assert_eq!(old_value, Some(50));
78//! assert_eq!(
79//! 	domainmap.get(&Domain::reference("example.com")?),
80//! 	None // Not in the map anymore
81//! );
82//!
83//! // Show the amount of key-value pairs in the map
84//! assert_eq!(domainmap.len(), 2); // `*.example.net` and `www.example.net`
85//!
86//! // Clear the map
87//! domainmap.clear();
88//! assert!(domainmap.is_empty());
89//! # Ok(())
90//! # }
91//! ```
92//!
93//! # [`Domain`] name syntax
94//!
95//! Rules for domain names as implemented here (based on a mix of [RFC 952],
96//! [RFC 1034], [RFC 1123], [RFC 2181], the [WHATWG URL specification][whatwg
97//! url], [browser][chrome] [implementations][firefox], and [browser
98//! bugs][bugzilla]) are:
99//! - A domain name has a maximum total length (including '.'s) of 253
100//!   characters/octets/bytes ([RFC 1034] section 3.1, [Unicode TR46] section 4)
101//! - Domain name labels (the things seperated by '.') have a maximum length of
102//!   63 characters/octets/bytes each, not including the separators ([RFC 1034]
103//!   section 3.5, [RFC 1123] section 2.1, [RFC 2181] section 11)
104//! - A domain name label must consist of only ASCII letters (`'a'..='z' |
105//!   'A'..='Z'`), digits (`'0'..='9'`), and hyphens (`'-'`). A label can not
106//!   start or end with a hyphen. ([RFC 952] section B, [RFC 1034] section 3.5,
107//!   [RFC 1123] section 2.1)
108//! - Additionally, a label can also contain underscores (`'_'`) in the same
109//!   places as letters for compatibility reasons. ([Additional discussion
110//!   around underscores in a Firefox bug][bugzilla], implementations in
111//!   [Chromium][chrome] and [Firefox][firefox])
112//! - A wildcard (`"*"`) can only comprise the entire left-most label of a
113//!   domain name and matches exactly one label. ([RFC 2818] section 3.1, [RFC
114//!   6125] section 6.4.3; i.e. `"*.example.com"` is valid and matches
115//!   `"foo.example.com"` and `"bar.example.com"` but does not match
116//!   `"foo.bar.example.com"` or `"example.com"`, and all of the following are
117//!   invalid: `"*.*.example.com"`, `"foo.*.example.com"`, `"fo*.example.com"`,
118//!   `"*oo.example.com"`, `"f*o.example.com"`)
119//! - No special treatment is given to wildcards on top-level domains (e.g.
120//!   `"*.com"`), or on other public suffixes (e.g. "`*.co.uk`" or
121//!   `"*.pvt.k12.ma.us"`), which allows some potentially invalid wildcard
122//!   domains; full wildcard domains (`"*"`) are not allowed
123//! - [Percent-encoded domain names][whatwg url] (e.g. `"e%78ample.com"`) are
124//!   not supported, and `'%'` is treated as an invalid character
125//!
126//! [RFC 952]: https://www.rfc-editor.org/rfc/rfc952
127//! [RFC 1034]: https://www.rfc-editor.org/rfc/rfc1034
128//! [RFC 1123]: https://www.rfc-editor.org/rfc/rfc1123
129//! [RFC 2181]: https://www.rfc-editor.org/rfc/rfc2181
130//! [RFC 5890]: https://www.rfc-editor.org/rfc/rfc5890
131//! [RFC 6125]: https://www.rfc-editor.org/rfc/rfc6125
132//! [Unicode TR46]: https://www.unicode.org/reports/tr46/tr46-29.html
133//! [bugzilla]: https://bugzilla.mozilla.org/show_bug.cgi?id=1136616
134//! [whatwg url]: https://url.spec.whatwg.org/#host-parsing
135//! [chrome]: https://github.com/chromium/chromium/blob/18095fefc0746e934e623019294b10844d8ec989/net/base/url_util.cc#L359-L377
136//! [firefox]: https://searchfox.org/mozilla-central/rev/23690c9281759b41eedf730d3dcb9ae04ccaddf8/security/nss/lib/mozpkix/lib/pkixnames.cpp#1979-1997
137//! [reference identifier]: https://www.rfc-editor.org/rfc/rfc6125#page-12
138//! [presented identifier]: https://www.rfc-editor.org/rfc/rfc6125#page-11
139
140#![no_std]
141#![forbid(unsafe_code)]
142#![warn(
143	clippy::pedantic,
144	clippy::cargo,
145	clippy::nursery,
146	missing_docs,
147	rustdoc::missing_crate_level_docs
148)]
149#![allow(clippy::tabs_in_doc_comments, clippy::module_name_repetitions)]
150
151extern crate alloc;
152
153mod domain;
154mod map;
155
156#[cfg(feature = "serde")]
157mod serde;
158#[cfg(test)]
159mod tests;
160
161pub use domain::{Domain, Label, ParseError};
162pub use map::DomainMap;