actix_plus_utils/
lib.rs

1//! # Overview
2//! This crate simply provides various miscellaneous utilities that are useful in the course of actix-web development, like a function to sanitize control characters from a string (commonly used in user input). See the docs.rs documentation for a complete list of currently available functions.
3//!
4//! # License
5//! Dual licenced under MIT or Apache-2.0 license, the same license as actix-web.
6use actix_plus_error::{ResponseError, ResponseResult};
7use actix_web::http::StatusCode;
8use rand::{thread_rng, Rng};
9use std::time::{SystemTime, UNIX_EPOCH};
10use unic_ucd_category::GeneralCategory;
11
12/// Returns the current unix time in seconds. This is useful both for when working with external APIs or libraries that expect a UNIX time, and for cleanly keeping track of time in one's own code.
13pub fn current_unix_time_secs() -> u64 {
14    SystemTime::now()
15        .duration_since(UNIX_EPOCH)
16        .expect("Time went backwards")
17        .as_secs()
18}
19
20#[test]
21fn test_unix_time_increasing_at_proper_rate() {
22    use std::thread::sleep;
23    use std::time::Duration;
24
25    let first_time = current_unix_time_secs();
26    sleep(Duration::from_millis(1000));
27    let second_time = current_unix_time_secs();
28    assert_eq!(first_time, second_time - 1);
29}
30
31/// Generates a secure random string. This is useful for token generation, such as email verification tokens. This string can contain any of the characters in the string "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890" with equal probability.
32pub fn secure_random_string(len: usize) -> String {
33    let chars = b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
34    let mut random_string = Vec::new();
35    let mut rng = thread_rng();
36    random_string.reserve(len);
37    for _i in 0..len {
38        random_string.push(chars[rng.gen_range(0, chars.len())]);
39    }
40    String::from_utf8(random_string).expect("Random string contains non-UTF data.")
41}
42
43#[test]
44fn test_secure_random_strings() {
45    let length = 1024;
46    let string_1 = secure_random_string(length);
47    let string_2 = secure_random_string(length);
48    assert_ne!(string_1, string_2);
49    assert_eq!(string_1.len(), length);
50    assert_eq!(string_2.len(), length);
51}
52
53/// Validates a given string to contain only text characters (from any language), and no control characters, thus making it safe to display in a web page IF IT IS THEN PROPERLY ESCAPED, AS AN ADDITIONAL STEP. THIS METHOD DOES NOT ESCAPE FOR HTML, JAVASCRIPT, OR ANY OTHER LANGUAGE.
54/// If allow_new_line is set to true, then \n and \r are allowed, but \r is removed.
55/// If a string contains control characters (other than \n and \r when allow_new_line is true) then a ResponseResult that allows 400 Bad Request to be propagated is returned. If you prefer to use your own error handling, you can simply match on the Err variant and interpret as documented here.
56pub fn validate_and_sanitize_string(string: &str, allow_new_line: bool) -> ResponseResult<String> {
57    let mut output = String::new();
58    output.reserve(string.len());
59    for ch in string.chars() {
60        if ch == ' ' || (allow_new_line && ch == '\n') {
61            output.push(ch);
62        } else if allow_new_line && ch == '\r' {
63            //do nothing, remove this character
64        } else {
65            let ctg = GeneralCategory::of(ch);
66            if ctg.is_other() || ctg.is_separator() {
67                return Err(ResponseError::StatusCodeError {
68                    message: String::from("Input strings for user-supplied content must not contain non-printable characters, excepting newlines in some cases."),
69                    code: StatusCode::BAD_REQUEST
70                });
71            } else {
72                output.push(ch);
73            }
74        }
75    }
76
77    Ok(output)
78}
79
80#[test]
81fn test_string_validation() {
82    assert_eq!(
83        validate_and_sanitize_string("Test String", false).is_ok(),
84        true
85    );
86    assert_eq!(
87        validate_and_sanitize_string("Test String", true).is_ok(),
88        true
89    );
90    assert_eq!(
91        validate_and_sanitize_string("Test String\n\r", true).is_ok(),
92        true
93    );
94    assert_eq!(
95        validate_and_sanitize_string("Test String\n\r", false).is_ok(),
96        false
97    );
98    assert_eq!(
99        validate_and_sanitize_string("Test String\n", true).is_ok(),
100        true
101    );
102    assert_eq!(
103        validate_and_sanitize_string("Test String\n", false).is_ok(),
104        false
105    );
106    assert_eq!(
107        validate_and_sanitize_string("Test String\t", false).is_ok(),
108        false
109    );
110    assert_eq!(
111        validate_and_sanitize_string("Test String\t", true).is_ok(),
112        false
113    );
114    assert_eq!(
115        validate_and_sanitize_string("Test\n\rString", true).unwrap(),
116        "Test\nString"
117    );
118}