summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNeal H. Walfield <neal@pep.foundation>2019-04-11 23:33:15 +0200
committerNeal H. Walfield <neal@pep.foundation>2019-04-12 11:44:32 +0200
commit6b8e5c59d44047e4c462235c17753187254bf052 (patch)
tree861c8c44b1e03901881ba18d9be375417de78224
parent5c7e4274102748b287e81ffb195a918f442e2e13 (diff)
openpgp: Parse User IDs.
- Provide an interface to query the name, comment and email address of RFC 2822 name-addr and addr-spec encoded User IDs.
-rw-r--r--openpgp/Cargo.toml1
-rw-r--r--openpgp/src/lib.rs2
-rw-r--r--openpgp/src/packet/userid.rs167
3 files changed, 169 insertions, 1 deletions
diff --git a/openpgp/Cargo.toml b/openpgp/Cargo.toml
index 740e3495..560af969 100644
--- a/openpgp/Cargo.toml
+++ b/openpgp/Cargo.toml
@@ -32,6 +32,7 @@ nettle = "5.0"
quickcheck = "0.8"
rand = "0.6"
time = "0.1.40"
+sequoia-rfc2822 = { path = "../rfc2822", version = "*" }
[build-dependencies]
lalrpop = "0.16"
diff --git a/openpgp/src/lib.rs b/openpgp/src/lib.rs
index 7f1e4b9b..584ae406 100644
--- a/openpgp/src/lib.rs
+++ b/openpgp/src/lib.rs
@@ -65,6 +65,8 @@ extern crate quickcheck;
extern crate rand;
extern crate time;
+
+extern crate sequoia_rfc2822 as rfc2822;
#[macro_use]
mod macros;
diff --git a/openpgp/src/packet/userid.rs b/openpgp/src/packet/userid.rs
index 99cb2a0d..4a1ecec4 100644
--- a/openpgp/src/packet/userid.rs
+++ b/openpgp/src/packet/userid.rs
@@ -1,6 +1,11 @@
use std::fmt;
+use std::str;
+use std::hash::{Hash, Hasher};
+use std::cell::RefCell;
use quickcheck::{Arbitrary, Gen};
+use rfc2822::{NameAddr, AddrSpec};
+use Result;
use packet;
use Packet;
@@ -9,7 +14,6 @@ use Packet;
/// See [Section 5.11 of RFC 4880] for details.
///
/// [Section 5.11 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.11
-#[derive(PartialEq, Eq, Hash, Clone)]
pub struct UserID {
/// CTB packet header fields.
pub(crate) common: packet::Common,
@@ -23,6 +27,8 @@ pub struct UserID {
///
/// Use `UserID::default()` to get a UserID with a default settings.
value: Vec<u8>,
+
+ parsed: RefCell<Option<(Option<String>, Option<String>, Option<String>)>>,
}
impl From<Vec<u8>> for UserID {
@@ -30,6 +36,7 @@ impl From<Vec<u8>> for UserID {
UserID {
common: Default::default(),
value: u,
+ parsed: RefCell::new(None),
}
}
}
@@ -60,11 +67,95 @@ impl fmt::Debug for UserID {
}
}
+impl PartialEq for UserID {
+ fn eq(&self, other: &UserID) -> bool {
+ self.common == other.common
+ && self.value == other.value
+ }
+}
+
+impl Eq for UserID {
+}
+
+
+impl Hash for UserID {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ // We hash only the data; the cache does not implement hash.
+ self.common.hash(state);
+ self.value.hash(state);
+ }
+}
+
+impl Clone for UserID {
+ fn clone(&self) -> Self {
+ UserID {
+ common: self.common.clone(),
+ value: self.value.clone(),
+ parsed: RefCell::new(None),
+ }
+ }
+}
+
impl UserID {
/// Gets the user ID packet's value.
pub fn value(&self) -> &[u8] {
self.value.as_slice()
}
+
+ fn do_parse(&self) -> Result<()> {
+ if self.parsed.borrow().is_none() {
+ let s = str::from_utf8(&self.value)?;
+
+ *self.parsed.borrow_mut() = Some(match NameAddr::parse(s) {
+ Ok(na) => (na.name().map(|s| s.to_string()),
+ na.comment().map(|s| s.to_string()),
+ na.address().map(|s| s.to_string())),
+ Err(err) => {
+ // Try with the addr-spec parser.
+ if let Ok(a) = AddrSpec::parse(s) {
+ (None, None, Some(a.address().to_string()))
+ } else {
+ // Return the error from the NameAddr parser.
+ return Err(err.into());
+ }
+ }
+ });
+ }
+ Ok(())
+ }
+
+ /// Treats the user ID as an RFC 2822 name-addr and extracts the
+ /// display name, if any.
+ pub fn name(&self) -> Result<Option<String>> {
+ self.do_parse()?;
+ match *self.parsed.borrow() {
+ Some((ref name, ref _comment, ref _address)) =>
+ Ok(name.as_ref().map(|s| s.clone())),
+ None => unreachable!(),
+ }
+ }
+
+ /// Treats the user ID as an RFC 2822 name-addr and extracts the
+ /// first comment, if any.
+ pub fn comment(&self) -> Result<Option<String>> {
+ self.do_parse()?;
+ match *self.parsed.borrow() {
+ Some((ref _name, ref comment, ref _address)) =>
+ Ok(comment.as_ref().map(|s| s.clone())),
+ None => unreachable!(),
+ }
+ }
+
+ /// Treats the user ID as an RFC 2822 name-addr and extracts the
+ /// address, if any.
+ pub fn address(&self) -> Result<Option<String>> {
+ self.do_parse()?;
+ match *self.parsed.borrow() {
+ Some((ref _name, ref _comment, ref address)) =>
+ Ok(address.as_ref().map(|s| s.clone())),
+ None => unreachable!(),
+ }
+ }
}
impl From<UserID> for Packet {
@@ -92,4 +183,78 @@ mod tests {
true
}
}
+
+ #[test]
+ fn name_addr() {
+ fn c(value: &str, ok: bool,
+ name: Option<&str>, comment: Option<&str>, address: Option<&str>)
+ {
+ let name = name.map(|s| s.to_string());
+ let comment = comment.map(|s| s.to_string());
+ let address = address.map(|s| s.to_string());
+
+ let u = UserID::from(value);
+ for _ in 0..2 {
+ match u.name() {
+ Ok(ref v) if ok =>
+ assert_eq!(v, &name),
+ Ok(_) if !ok =>
+ panic!("Expected parse to fail."),
+ Err(ref err) if ok =>
+ panic!("Expected parse to succeed: {:?}", err),
+ Err(_) if !ok =>
+ (),
+ _ => unreachable!(),
+ };
+ match u.comment() {
+ Ok(ref v) if ok =>
+ assert_eq!(v, &comment),
+ Ok(_) if !ok =>
+ panic!("Expected parse to fail."),
+ Err(ref err) if ok =>
+ panic!("Expected parse to succeed: {:?}", err),
+ Err(_) if !ok =>
+ (),
+ _ => unreachable!(),
+ };
+ match u.address() {
+ Ok(ref v) if ok =>
+ assert_eq!(v, &address),
+ Ok(_) if !ok =>
+ panic!("Expected parse to fail."),
+ Err(ref err) if ok =>
+ panic!("Expected parse to succeed: {:?}", err),
+ Err(_) if !ok =>
+ (),
+ _ => unreachable!(),
+ };
+ }
+ }
+
+ c("Henry Ford (CEO) <henry@ford.com>", true,
+ Some("Henry Ford"), Some("CEO"), Some("henry@ford.com"));
+
+ // The quotes disappear. Unexpected, but true.
+ c("Thomas \"Tomakin\" (DHC) <thomas@clh.co.uk>", true,
+ Some("Thomas Tomakin"), Some("DHC"), Some("thomas@clh.co.uk"));
+
+ c("Aldous L. Huxley <huxley@old-world.org>", true,
+ Some("Aldous L. Huxley"), None, Some("huxley@old-world.org"));
+
+ // Make sure bare email addresses work. This is an extension
+ // to 2822 where addresses normally have to be in angle
+ // brackets.
+ c("huxley@old-world.org", true,
+ None, None, Some("huxley@old-world.org"));
+
+ // Tricky...
+ c("\"<loki@bar.com>\" <foo@bar.com>", true,
+ Some("<loki@bar.com>"), None, Some("foo@bar.com"));
+
+ // Invalid.
+ c("<huxley@@old-world.org>", false, None, None, None);
+ c("huxley@@old-world.org", false, None, None, None);
+ c("huxley@old-world.org.", false, None, None, None);
+ c("@old-world.org", false, None, None, None);
+ }
}