Coverage Report

Created: 2026-05-18 06:32

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/git/checkouts/nss-rs-71e20fe79ef91440/9b94ca3/src/pk11_utils.rs
Line
Count
Source
1
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
2
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
3
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
4
// option. This file may not be copied, modified, or distributed
5
// except according to those terms.
6
7
use crate::err::{Error, Res};
8
9
#[derive(Debug, Clone, PartialEq, Eq)]
10
pub struct Pkcs11Uri {
11
    pub token: Option<String>,
12
}
13
14
0
fn percent_decode(s: &str) -> String {
15
0
    let mut result = Vec::with_capacity(s.len());
16
0
    let bytes = s.as_bytes();
17
0
    let mut i = 0;
18
0
    while i < bytes.len() {
19
0
        if bytes[i] == b'%'
20
0
            && i + 2 < bytes.len()
21
0
            && let Ok(byte) =
22
0
                u8::from_str_radix(std::str::from_utf8(&bytes[i + 1..i + 3]).unwrap_or(""), 16)
23
        {
24
0
            result.push(byte);
25
0
            i += 3;
26
0
            continue;
27
0
        }
28
0
        result.push(bytes[i]);
29
0
        i += 1;
30
    }
31
0
    String::from_utf8_lossy(&result).into_owned()
32
0
}
33
34
0
fn percent_encode(s: &str) -> String {
35
0
    let mut result = String::with_capacity(s.len());
36
0
    for b in s.bytes() {
37
0
        match b {
38
0
            b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'.' | b'_' | b'~' => {
39
0
                result.push(b as char);
40
0
            }
41
            _ => {
42
                use std::fmt::Write as _;
43
0
                write!(result, "%{b:02X}").expect("write to String");
44
            }
45
        }
46
    }
47
0
    result
48
0
}
49
50
/// Parse a PKCS#11 URI (RFC 7512).
51
/// Expects the input to start with "pkcs11:".
52
0
pub fn parse(uri: &str) -> Res<Pkcs11Uri> {
53
0
    let path = uri.strip_prefix("pkcs11:").ok_or(Error::InvalidInput)?;
54
55
0
    let mut token = None;
56
0
    for attr in path.split(';') {
57
0
        if let Some((key, value)) = attr.split_once('=')
58
0
            && key == "token"
59
0
        {
60
0
            token = Some(percent_decode(value));
61
0
        }
62
    }
63
64
0
    Ok(Pkcs11Uri { token })
65
0
}
66
67
/// Build a PKCS#11 URI from a token name.
68
#[must_use]
69
0
pub fn build(token_name: &str) -> String {
70
0
    format!("pkcs11:token={}", percent_encode(token_name))
71
0
}
72
73
#[cfg(test)]
74
mod tests {
75
    use test_fixture::fixture_init;
76
77
    use super::*;
78
79
    #[test]
80
    fn parse_simple() {
81
        fixture_init();
82
        let uri = parse("pkcs11:token=NSS%20Certificate%20DB").unwrap();
83
        assert_eq!(uri.token.as_deref(), Some("NSS Certificate DB"));
84
    }
85
86
    #[test]
87
    fn parse_no_token() {
88
        fixture_init();
89
        let uri = parse("pkcs11:manufacturer=Mozilla").unwrap();
90
        assert_eq!(uri.token, None);
91
    }
92
93
    #[test]
94
    fn parse_multiple_attrs() {
95
        fixture_init();
96
        let uri = parse("pkcs11:token=MyToken;manufacturer=Test;serial=1234").unwrap();
97
        assert_eq!(uri.token.as_deref(), Some("MyToken"));
98
    }
99
100
    #[test]
101
    fn parse_not_pkcs11() {
102
        fixture_init();
103
        assert!(parse("http://example.com").is_err());
104
    }
105
106
    #[test]
107
    fn build_uri() {
108
        fixture_init();
109
        assert_eq!(
110
            build("NSS Certificate DB"),
111
            "pkcs11:token=NSS%20Certificate%20DB"
112
        );
113
    }
114
115
    #[test]
116
    fn roundtrip() {
117
        fixture_init();
118
        let name = "My Token (Test-v2)";
119
        let uri_str = build(name);
120
        let parsed = parse(&uri_str).unwrap();
121
        assert_eq!(parsed.token.as_deref(), Some(name));
122
    }
123
}