ntfsanitise/
lib.rs

1// SPDX-FileCopyrightText: 2025 Lynnesbian <lynne@bune.city>
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4#![allow(clippy::needless_doctest_main)]
5#![doc = include_str!("../README.md")]
6#![cfg_attr(not(feature = "std"), no_std)]
7
8#[cfg(feature = "std")]
9mod path;
10#[cfg(test)]
11mod tests;
12
13#[cfg(feature = "std")]
14pub use path::{PathBufExt, PathExt};
15
16#[cfg(all(not(feature = "std"), feature = "alloc"))]
17extern crate alloc;
18
19#[cfg(all(not(feature = "std"), feature = "alloc"))]
20use alloc::string::String;
21
22/// Banned characters for NTFS filenames.
23///
24/// The banned character set is based on
25/// [Microsoft's guidelines](https://learn.microsoft.com/en-au/windows/win32/fileio/naming-a-file#naming-conventions).
26/// Characters with ASCII values of zero ([NUL](https://www.ascii-code.com/character/%E2%90%80)) through 31
27/// ([UNIT SEPARATOR](https://www.ascii-code.com/character/%E2%90%9F)) are banned, as well as a few reserved
28/// characters.
29/// See the guidelines for further details.
30pub const BANNED: &[char] = &generate_banned();
31
32const fn generate_banned() -> [char; 41] {
33	let mut banned = ['\0'; 41];
34	let mut i = 0;
35
36	// > Integer value zero, sometimes referred to as the ASCII NUL character
37	// > Characters whose integer representations are in the range from 1 through 31
38	// note: this includes \n, which you would probably want to ban anyway
39	while i <= 31 {
40		banned[i as usize] = char::from_u32(i).unwrap();
41		i += 1;
42	}
43
44	// > The following reserved characters:
45	let mut i = 0;
46	let reserved = &['<', '>', ':', '"', '/', '\\', '|', '?', '*'];
47	while i < reserved.len() {
48		banned[i + 32] = reserved[i];
49		i += 1;
50	}
51
52	banned
53}
54
55/// Replaces banned characters with the given replacement.
56///
57/// # Example
58/// ```rust
59/// use ntfsanitise::sanitise;
60///
61/// let input = "Hello\tworld";
62/// let sanitised = sanitise(input, "_");
63/// assert_eq!(sanitised, String::from("Hello_world"));
64/// ```
65#[doc(alias = "sanitize")]
66#[must_use]
67#[cfg(feature = "alloc")]
68pub fn sanitise(input: impl AsRef<str>, to: &str) -> String { input.as_ref().replace(BANNED, to) }
69
70/// Checks whether the given string is "dirty" (i.e. contains banned characters).
71///
72/// # Example
73/// ```rust
74/// use ntfsanitise::is_dirty;
75///
76/// assert!(is_dirty("Me? Dirty?"));
77/// assert!(!is_dirty("Nice and clean!"));
78/// ```
79#[must_use]
80pub fn is_dirty(input: impl AsRef<str>) -> bool { input.as_ref().contains(BANNED) }
81
82/// Checks whether the given string is "clean" (i.e. does not contain any banned characters).
83///
84/// # Example
85/// ```rust
86/// use ntfsanitise::is_clean;
87///
88/// assert!(is_clean("Nice and clean!"));
89/// assert!(!is_clean("Me? Dirty?"));
90/// ```
91#[must_use]
92pub fn is_clean(input: impl AsRef<str>) -> bool { !is_dirty(input) }