win_crypto_ng/helpers/
windows_string.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
//! Owned Unicode, nul-aware and nul-terminated wide string
//!
//! Provides a lossless conversion for FFI APIs expecting an Unicode
//! nul-terminated string.

use std::borrow::Cow;
use std::ffi::{OsStr, OsString};
use std::fmt;
use std::os::windows::ffi::{OsStrExt, OsStringExt};

use winapi::shared::ntdef::LPCWSTR;

/// C-string terminator.
const NUL: u16 = 0;

/// Owned, wide variant of the `CString` type.
#[derive(Debug, PartialEq)]
pub struct WindowsString {
    // Guaranteed to end with NUL
    inner: Vec<u16>,
}

/// Whether the input bytes were not terminated with NUL or one was found but
/// not at the end.
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum FromBytesWithNulError {
    NotTerminated,
    InteriorNul { index: usize },
}

impl fmt::Display for FromBytesWithNulError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::NotTerminated => f.write_str("not terminated with NUL byte"),
            Self::InteriorNul { index } => f.write_fmt(format_args!(
                "NUL byte found not at the end (index: {})",
                index
            )),
        }
    }
}

impl std::error::Error for FromBytesWithNulError {}

impl WindowsString {
    pub(crate) fn new() -> Self {
        WindowsString { inner: Vec::new() }
    }

    pub fn from_bytes_with_nul(val: Cow<'_, [u16]>) -> Result<Self, FromBytesWithNulError> {
        match val.iter().position(|&x| x == NUL) {
            Some(idx) if idx == val.len() - 1 => Ok(Self {
                inner: val.into_owned(),
            }),
            None => Err(FromBytesWithNulError::NotTerminated),
            Some(index) => Err(FromBytesWithNulError::InteriorNul { index }),
        }
    }

    pub fn as_slice_with_nul(&self) -> &[u16] {
        self.inner.as_slice()
    }

    pub fn as_ptr(&self) -> LPCWSTR {
        self.inner.as_ptr()
    }
}

impl ToString for WindowsString {
    fn to_string(&self) -> String {
        let without_nul = &self.inner[..self.inner.len().saturating_sub(1)];

        OsString::from_wide(without_nul)
            .to_string_lossy()
            .as_ref()
            .to_string()
    }
}

impl From<&str> for WindowsString {
    fn from(value: &str) -> WindowsString {
        Self {
            inner: OsStr::new(value)
                .encode_wide()
                .chain(Some(NUL).into_iter())
                .collect(),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn from_bytes_with_nul() {
        assert_eq!(
            WindowsString::from_bytes_with_nul([NUL].to_vec().into()),
            Ok(WindowsString { inner: vec![NUL] })
        );
        assert_eq!(
            WindowsString::from_bytes_with_nul([97, 110, NUL][..].into()),
            Ok(WindowsString {
                inner: vec![97, 110, NUL]
            })
        );

        assert_eq!(
            WindowsString::from_bytes_with_nul([].to_vec().into()),
            Err(FromBytesWithNulError::NotTerminated)
        );
        assert_eq!(
            WindowsString::from_bytes_with_nul([97, 110].to_vec().into()),
            Err(FromBytesWithNulError::NotTerminated)
        );

        assert_eq!(
            WindowsString::from_bytes_with_nul([97, NUL, 110].to_vec().into()),
            Err(FromBytesWithNulError::InteriorNul { index: 1 })
        );
        assert_eq!(
            WindowsString::from_bytes_with_nul([NUL, NUL, 110].to_vec().into()),
            Err(FromBytesWithNulError::InteriorNul { index: 0 })
        );
        assert_eq!(
            WindowsString::from_bytes_with_nul([97, NUL, NUL].to_vec().into()),
            Err(FromBytesWithNulError::InteriorNul { index: 1 })
        );
    }

    #[test]
    fn string() {
        assert_eq!(
            WindowsString::from("abc"),
            WindowsString {
                inner: vec![97, 98, 99, NUL]
            }
        );
        assert_eq!(
            WindowsString::from("🦀"),
            WindowsString {
                inner: vec![0xD83E, 0xDD80, NUL]
            }
        );

        assert_eq!(
            WindowsString::from("abc").as_slice_with_nul(),
            &[97, 98, 99, NUL]
        );
        assert_eq!(
            WindowsString::from("🦀").as_slice_with_nul(),
            &[0xD83E, 0xDD80, NUL]
        );

        assert_eq!(WindowsString::from("abc").to_string().as_str(), "abc");
        assert_eq!(WindowsString::from("abc").to_string().len(), 3);
        assert_eq!(WindowsString::from("🦀").to_string().as_str(), "🦀");
        assert_eq!(WindowsString::from("🦀").to_string().len(), 4);
    }
}