#![cfg(windows)]
#![cfg_attr(not(feature = "std"), no_std)]
mod convert;
mod ntstring;
mod raw;
#[cfg(feature = "std")]
mod string;
#[cfg(feature = "std")]
pub mod traits;
mod windy_str;
pub use ntstring::*;
use raw::*;
#[cfg(feature = "std")]
pub use string::*;
pub use windy_str::*;
#[cfg(not(feature = "std"))]
#[allow(unused_imports)]
pub(crate) mod __lib {
pub(crate) use core::{
borrow, char, cmp, convert, error, fmt, hash, mem, ops, ptr, slice, str,
};
}
#[cfg(feature = "std")]
#[allow(unused_imports)]
pub(crate) mod __lib {
pub(crate) use std::{
borrow, char, cmp, convert, error, fmt, hash, mem, ops, ptr, slice, str,
};
}
use crate::convert::validate_mb;
use __lib::fmt;
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
pub enum StringFormatError {
InteriorNul(usize),
NotNulTerminated,
}
impl fmt::Display for StringFormatError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InteriorNul(position) => {
write!(f, "interior NUL found at index {position}")
}
Self::NotNulTerminated => {
f.write_str("the string is not NUL-terminated")
}
}
}
}
impl __lib::error::Error for StringFormatError {}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum ConvertError {
InvalidAnsiString,
InvalidUnicodeString,
ConvertToAnsiError,
ConvertToUnicodeError,
InvalidCodePage(u32),
CodePageMismatch { left: u32, right: u32 },
InvalidStringFormat(StringFormatError),
OsError(u32),
InsufficientBuffer,
TryFromIntError,
}
impl ConvertError {
#[inline]
pub fn as_error_code(&self) -> u32 {
match self {
Self::InvalidAnsiString => ERROR_NO_UNICODE_TRANSLATION,
Self::InvalidUnicodeString => ERROR_NO_UNICODE_TRANSLATION,
Self::ConvertToAnsiError => ERROR_NO_UNICODE_TRANSLATION,
Self::ConvertToUnicodeError => ERROR_NO_UNICODE_TRANSLATION,
Self::InvalidStringFormat(_) => ERROR_INVALID_PARAMETER,
ConvertError::InvalidCodePage(_) => ERROR_INVALID_PARAMETER,
ConvertError::CodePageMismatch { left: _, right: _ } => {
ERROR_INVALID_PARAMETER
}
ConvertError::TryFromIntError => ERROR_INVALID_PARAMETER,
ConvertError::OsError(e) => *e,
ConvertError::InsufficientBuffer => ERROR_INSUFFICIENT_BUFFER,
}
}
}
impl __lib::error::Error for ConvertError {}
impl fmt::Display for ConvertError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidAnsiString => {
f.write_str("the input is not a valid ANSI string")
}
Self::InvalidUnicodeString => {
f.write_str("the input is not a valid UTF-16 string")
}
Self::ConvertToAnsiError => f.write_str(
"failed to encode the string using the requested code page",
),
Self::ConvertToUnicodeError => f.write_str(
"failed to decode the string using the requested code page",
),
Self::InvalidCodePage(code_page) => {
write!(f, "invalid Windows code page: {code_page}")
}
Self::CodePageMismatch { left, right } => {
write!(
f,
"code page mismatch: left is {left}, but right is {right}",
)
}
Self::InvalidStringFormat(error) => {
write!(f, "invalid string format: {error}")
}
Self::OsError(error_code) => {
write!(
f,
"Windows API failed with error code {error_code} \
(0x{error_code:08X})",
)
}
Self::InsufficientBuffer => {
f.write_str("the output buffer is too small")
}
Self::TryFromIntError => f.write_str(
"integer conversion failed while preparing a Windows API \
argument",
),
}
}
}
impl From<ConvertError> for u32 {
fn from(x: ConvertError) -> Self { x.as_error_code() }
}
impl From<ConvertError> for i32 {
fn from(x: ConvertError) -> Self { x.as_error_code() as i32 }
}
impl From<StringFormatError> for ConvertError {
fn from(x: StringFormatError) -> Self { Self::InvalidStringFormat(x) }
}
pub type ConvertResult<T> = Result<T, ConvertError>;
pub const CP_ACP: u32 = 0;
pub const CP_UTF8: u32 = 65001;
pub const MB_ERR_INVALID_CHARS: DWORD = 0x8;
pub const WC_ERR_INVALID_CHARS: DWORD = 0x80;
pub const WC_NO_BEST_FIT_CHARS: DWORD = 0x400;
pub const ERROR_INVALID_PARAMETER: DWORD = 0x57;
pub const ERROR_INSUFFICIENT_BUFFER: DWORD = 0x7a;
pub const ERROR_NO_UNICODE_TRANSLATION: DWORD = 0x459;
#[inline]
pub(crate) fn check_interior_nul_u8(x: &[u8]) -> ConvertResult<()> {
let Some((_last, body)) = x.split_last() else {
return Ok(());
};
match body.iter().position(|&b| b == 0) {
None => Ok(()),
Some(pos) => Err(StringFormatError::InteriorNul(pos).into()),
}
}
#[inline]
#[allow(unused)]
pub(crate) fn check_nul_in_str(x: &str) -> ConvertResult<()> {
match x.as_bytes().iter().position(|&b| b == 0) {
None => Ok(()),
Some(pos) => Err(StringFormatError::InteriorNul(pos).into()),
}
}
#[inline]
pub(crate) fn check_interior_nul_u16(x: &[u16]) -> ConvertResult<()> {
let Some((_last, body)) = x.split_last() else {
return Ok(());
};
match body.iter().position(|&b| b == 0) {
None => Ok(()),
Some(pos) => Err(StringFormatError::InteriorNul(pos).into()),
}
}
#[inline]
pub(crate) fn find_nul_u8(x: &[u8]) -> Option<usize> {
x.iter().position(|&b| b == 0)
}
#[inline]
pub(crate) fn find_nul_u16(x: &[u16]) -> Option<usize> {
x.iter().position(|&b| b == 0)
}
#[inline]
pub(crate) fn check_nul_terminated_u8(x: &[u8]) -> ConvertResult<()> {
if x.last() == Some(&0) {
Ok(())
} else {
Err(StringFormatError::NotNulTerminated.into())
}
}
#[inline]
pub(crate) fn check_nul_terminated_u16(x: &[u16]) -> ConvertResult<()> {
if x.last() == Some(&0) {
Ok(())
} else {
Err(StringFormatError::NotNulTerminated.into())
}
}
pub fn is_valid_code_page(code_page: u32) -> bool {
unsafe { IsValidCodePage(code_page) != 0 }
}
pub fn is_acceptable_code_page(code_page: u32) -> bool {
match validate_mb(code_page, &[0]) {
Ok(_) => true,
Err(ConvertError::InvalidCodePage(_)) => false,
Err(e) => {
unreachable!(
"internal error while checking code page {code_page}: {e}"
)
}
}
}