1
//! String libraries.
2

            
3
use std::sync::LazyLock;
4

            
5
use chrono::{DateTime, SecondsFormat, Utc};
6
use hex;
7
use hmac::Hmac;
8
use pbkdf2;
9
use rand::{RngExt, distr::Alphanumeric};
10
use regex::Regex;
11
use sha2::{Digest, Sha256};
12
use url::Url;
13

            
14
const PASSWORD_ROUNDS: u32 = 10000;
15

            
16
static CODE_REGEX: LazyLock<Regex> =
17
2
    LazyLock::new(|| Regex::new(r"^[a-z0-9]{1}[a-z0-9_-]*$").unwrap());
18
2
static EMAIL_REGEX: LazyLock<Regex> = LazyLock::new(|| {
19
2
    Regex::new(
20
2
        r"^([a-z0-9_+]([a-z0-9_+.]*[a-z0-9_+])?)@([a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,6})$",
21
    )
22
2
    .unwrap()
23
2
});
24
static NAME_REGEX: LazyLock<Regex> =
25
2
    LazyLock::new(|| Regex::new(r"^[a-z0-9]{1}[a-z0-9_-]*$").unwrap());
26
static SCOPE_REGEX: LazyLock<Regex> =
27
2
    LazyLock::new(|| Regex::new(r"^[a-z0-9]+([\.]{1}[a-z0-9]+)*$").unwrap());
28

            
29
/// To transfer hex address string to 128-bit integer.
30
10
pub fn hex_addr_to_u128(addr: &str) -> Result<u128, &'static str> {
31
10
    if addr.len() == 0 || addr.len() > 32 || addr.len() % 2 != 0 {
32
6
        return Err("invalid address format");
33
4
    }
34

            
35
4
    match u128::from_str_radix(addr, 16) {
36
2
        Err(_) => Err("invalid address format"),
37
2
        Ok(value) => Ok(value),
38
    }
39
10
}
40

            
41
/// To check if the account is valid.
42
8
pub fn is_account(account: &str) -> bool {
43
8
    NAME_REGEX.is_match(account) || EMAIL_REGEX.is_match(account)
44
8
}
45

            
46
/// To check if the (unit/application/network) code is valid.
47
4
pub fn is_code(code: &str) -> bool {
48
4
    CODE_REGEX.is_match(code)
49
4
}
50

            
51
/// To check if the (client) scope is valid.
52
4
pub fn is_scope(scope: &str) -> bool {
53
4
    SCOPE_REGEX.is_match(scope)
54
4
}
55

            
56
/// To check if the (redirect) URI is valid.
57
4
pub fn is_uri(uri: &str) -> bool {
58
4
    Url::parse(uri).is_ok()
59
4
}
60

            
61
/// To hash the password.
62
2
pub fn password_hash(password: &str, salt: &str) -> String {
63
2
    let mut res: [u8; 32] = [0; 32];
64
2
    let _ = pbkdf2::pbkdf2::<Hmac<Sha256>>(
65
2
        password.as_bytes(),
66
2
        salt.as_bytes(),
67
2
        PASSWORD_ROUNDS,
68
2
        &mut res,
69
2
    );
70
2
    hex::encode(res)
71
2
}
72

            
73
/// To generate item ID in `[timestamp-milliseconds]-[random-alphanumeric]` format.
74
8
pub fn random_id(time: &DateTime<Utc>, len: usize) -> String {
75
8
    format!("{}-{}", time.timestamp_millis(), randomstring(len))
76
8
}
77

            
78
/// To generate hex-string item ID using [`random_id`] and additional hash.
79
4
pub fn random_id_sha(time: &DateTime<Utc>, len: usize) -> String {
80
4
    let str = random_id(time, len);
81
4
    let mut hasher = Sha256::new();
82
4
    hasher.update(str.as_bytes());
83
4
    hex::encode(hasher.finalize())
84
4
}
85

            
86
/// To generate random alphanumeric string with the specified length.
87
12
pub fn randomstring(len: usize) -> String {
88
12
    let mut rng = rand::rng();
89
12
    std::iter::repeat(())
90
120
        .map(|()| rng.sample(Alphanumeric))
91
12
        .map(char::from)
92
12
        .take(len)
93
12
        .collect()
94
12
}
95

            
96
/// To convert time to RFC 3339 format with milliseconds precision (`YYYY-MM-DDThh:mm:ss.SSSZ`).
97
2
pub fn time_str(time: &DateTime<Utc>) -> String {
98
2
    time.to_rfc3339_opts(SecondsFormat::Millis, true)
99
2
}
100

            
101
/// Escapes regex special characters in a string so it can be used as a literal match pattern.
102
10
pub fn escape_regex_str(value: &str) -> String {
103
10
    let mut escaped = String::with_capacity(value.len());
104
56
    for c in value.chars() {
105
56
        match c {
106
30
            '\\' | '.' | '^' | '$' | '*' | '+' | '?' | '(' | ')' | '[' | ']' | '{' | '}' | '|' => {
107
30
                escaped.push('\\');
108
30
                escaped.push(c);
109
30
            }
110
26
            _ => escaped.push(c),
111
        }
112
    }
113
10
    escaped
114
10
}
115

            
116
/// To generate hex address string with the specified length (hex string length).
117
32
pub fn u128_to_addr(value: u128, len: usize) -> String {
118
32
    match len {
119
2
        0 | 1 | 2 => format!("{:02x}", value & 0xff),
120
2
        3 | 4 => format!("{:04x}", value & 0xffff),
121
2
        5 | 6 => format!("{:06x}", value & 0xff_ffff),
122
2
        7 | 8 => format!("{:08x}", value & 0xffff_ffff),
123
2
        9 | 10 => format!("{:010x}", value & 0xff_ffff_ffff),
124
2
        11 | 12 => format!("{:012x}", value & 0xffff_ffff_ffff),
125
2
        13 | 14 => format!("{:014x}", value & 0xff_ffff_ffff_ffff),
126
2
        15 | 16 => format!("{:016x}", value & 0xffff_ffff_ffff_ffff),
127
2
        17 | 18 => format!("{:018x}", value & 0xff_ffff_ffff_ffff_ffff),
128
2
        19 | 20 => format!("{:020x}", value & 0xffff_ffff_ffff_ffff_ffff),
129
2
        21 | 22 => format!("{:022x}", value & 0xff_ffff_ffff_ffff_ffff_ffff),
130
2
        23 | 24 => format!("{:024x}", value & 0xffff_ffff_ffff_ffff_ffff_ffff),
131
2
        25 | 26 => format!("{:026x}", value & 0xff_ffff_ffff_ffff_ffff_ffff_ffff),
132
2
        27 | 28 => format!("{:028x}", value & 0xffff_ffff_ffff_ffff_ffff_ffff_ffff),
133
2
        29 | 30 => format!("{:030x}", value & 0xff_ffff_ffff_ffff_ffff_ffff_ffff_ffff),
134
2
        _ => format!("{:032x}", value),
135
    }
136
32
}