Add SMB AAPL Extensions Phase 1-6 + VFS xattr support
Phase 1: AAPL Create Context negotiation Phase 2: AFP_AfpInfo Stream structure (Finder info + creation time) Phase 2.5: SMB Named Stream Backend (NamedStreamPath) Phase 2.6: Backend Named Stream Support in handlers Phase 2.7: VFS Extended Attributes (get/set/remove/list_xattr) Phase 4: Time Machine share config (time_machine field) Phase 5: Server/Volume Capabilities Phase 6: macOS Unicode mapping (private range ↔ ASCII) Tests: 174 smb-server tests pass, 52 VFS tests pass
This commit is contained in:
123
vendor/smb-server/src/unicode_mapping.rs
vendored
Normal file
123
vendor/smb-server/src/unicode_mapping.rs
vendored
Normal file
@@ -0,0 +1,123 @@
|
||||
//! macOS Unicode Private Range Mapping for SMB
|
||||
//!
|
||||
//! macOS SMB client maps NTFS illegal characters to Unicode private range.
|
||||
//! Reference: Samba vfs_fruit.c encoding handling
|
||||
|
||||
pub const FRUIT_ENC_NATIVE: bool = true;
|
||||
pub const FRUIT_ENC_PRIVATE: bool = false;
|
||||
|
||||
const APPLE_SLASH: u16 = 0xF026;
|
||||
const APPLE_COLON: u16 = 0xF02A;
|
||||
const APPLE_ASTERISK: u16 = 0xF02A;
|
||||
const APPLE_QUESTION: u16 = 0xF03F;
|
||||
const APPLE_QUOTE: u16 = 0xF022;
|
||||
const APPLE_LESS_THAN: u16 = 0xF03C;
|
||||
const APPLE_GREATER_THAN: u16 = 0xF03E;
|
||||
const APPLE_PIPE: u16 = 0xF07C;
|
||||
|
||||
const ASCII_SLASH: u16 = '/' as u16;
|
||||
const ASCII_COLON: u16 = ':' as u16;
|
||||
const ASCII_ASTERISK: u16 = '*' as u16;
|
||||
const ASCII_QUESTION: u16 = '?' as u16;
|
||||
const ASCII_QUOTE: u16 = '"' as u16;
|
||||
const ASCII_LESS_THAN: u16 = '<' as u16;
|
||||
const ASCII_GREATER_THAN: u16 = '>' as u16;
|
||||
const ASCII_PIPE: u16 = '|' as u16;
|
||||
|
||||
pub fn map_private_to_ascii(units: &[u16]) -> Vec<u16> {
|
||||
units.iter().map(|u| {
|
||||
match *u {
|
||||
APPLE_SLASH => ASCII_SLASH,
|
||||
APPLE_COLON => ASCII_COLON,
|
||||
APPLE_ASTERISK => ASCII_ASTERISK,
|
||||
APPLE_QUESTION => ASCII_QUESTION,
|
||||
APPLE_QUOTE => ASCII_QUOTE,
|
||||
APPLE_LESS_THAN => ASCII_LESS_THAN,
|
||||
APPLE_GREATER_THAN => ASCII_GREATER_THAN,
|
||||
APPLE_PIPE => ASCII_PIPE,
|
||||
_ => *u,
|
||||
}
|
||||
}).collect()
|
||||
}
|
||||
|
||||
pub fn map_ascii_to_private(units: &[u16]) -> Vec<u16> {
|
||||
units.iter().map(|u| {
|
||||
match *u {
|
||||
ASCII_SLASH => APPLE_SLASH,
|
||||
ASCII_COLON => APPLE_COLON,
|
||||
ASCII_ASTERISK => APPLE_ASTERISK,
|
||||
ASCII_QUESTION => APPLE_QUESTION,
|
||||
ASCII_QUOTE => APPLE_QUOTE,
|
||||
ASCII_LESS_THAN => APPLE_LESS_THAN,
|
||||
ASCII_GREATER_THAN => APPLE_GREATER_THAN,
|
||||
ASCII_PIPE => APPLE_PIPE,
|
||||
_ => *u,
|
||||
}
|
||||
}).collect()
|
||||
}
|
||||
|
||||
pub fn has_private_range_chars(units: &[u16]) -> bool {
|
||||
units.iter().any(|u| {
|
||||
matches!(*u,
|
||||
APPLE_SLASH | APPLE_COLON | APPLE_ASTERISK |
|
||||
APPLE_QUESTION | APPLE_QUOTE | APPLE_LESS_THAN |
|
||||
APPLE_GREATER_THAN | APPLE_PIPE
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn has_ntfs_illegal_chars(units: &[u16]) -> bool {
|
||||
units.iter().any(|u| {
|
||||
matches!(*u,
|
||||
ASCII_SLASH | ASCII_COLON | ASCII_ASTERISK |
|
||||
ASCII_QUESTION | ASCII_QUOTE | ASCII_LESS_THAN |
|
||||
ASCII_GREATER_THAN | ASCII_PIPE
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_map_private_to_ascii() {
|
||||
let input = [APPLE_SLASH, APPLE_COLON, APPLE_QUESTION];
|
||||
let output = map_private_to_ascii(&input);
|
||||
assert_eq!(output, [ASCII_SLASH, ASCII_COLON, ASCII_QUESTION]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_map_ascii_to_private() {
|
||||
let input = [ASCII_SLASH, ASCII_COLON, ASCII_ASTERISK];
|
||||
let output = map_ascii_to_private(&input);
|
||||
assert_eq!(output, [APPLE_SLASH, APPLE_COLON, APPLE_ASTERISK]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_roundtrip() {
|
||||
let original = [ASCII_SLASH, ASCII_COLON, 'a' as u16];
|
||||
let to_private = map_ascii_to_private(&original);
|
||||
let back_to_ascii = map_private_to_ascii(&to_private);
|
||||
assert_eq!(back_to_ascii, original);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_has_private_range_chars() {
|
||||
assert!(has_private_range_chars(&[APPLE_SLASH, 'a' as u16]));
|
||||
assert!(!has_private_range_chars(&[ASCII_SLASH, 'a' as u16]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_has_ntfs_illegal_chars() {
|
||||
assert!(has_ntfs_illegal_chars(&[ASCII_SLASH, 'a' as u16]));
|
||||
assert!(!has_ntfs_illegal_chars(&['a' as u16, 'b' as u16]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_preserve_non_mapped() {
|
||||
let input = ['a' as u16, 'b' as u16, 'c' as u16];
|
||||
let output = map_private_to_ascii(&input);
|
||||
assert_eq!(output, input);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user