Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .github/workflows/libloading.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
strategy:
fail-fast: false
matrix:
rust_toolchain: [nightly, stable, 1.71.0]
rust_toolchain: [nightly, stable, 1.81.0]
os: [ubuntu-latest, windows-latest, macOS-latest]
timeout-minutes: 20
steps:
Expand All @@ -24,10 +24,12 @@ jobs:
- run: rustup default ${{ matrix.rust_toolchain }}
- run: rustup component add clippy
- run: cargo update -p libc --precise 0.2.155
if: ${{ matrix.rust_toolchain == '1.71.0' }}
if: ${{ matrix.rust_toolchain == '1.81.0' }}
- run: cargo clippy
- run: cargo test -- --nocapture
- run: cargo test --release -- --nocapture
- run: cargo test --no-default-features -- --nocapture
- run: cargo test --release --no-default-features -- --nocapture
- run: cargo rustdoc -Zunstable-options --config 'build.rustdocflags=["--cfg", "libloading_docs", "-D", "rustdoc::broken_intra_doc_links"]'
if: ${{ matrix.rust_toolchain == 'nightly' }}
# pwsh.exe drops quotes kekw. https://stackoverflow.com/a/59036879
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ readme = "README.mkd"
description = "Bindings around the platform's dynamic library loading primitives with greatly improved memory safety."
keywords = ["dlopen", "load", "shared", "dylib"]
categories = ["api-bindings"]
rust-version = "1.71.0"
rust-version = "1.81.0"
edition = "2015"

[features]
Expand Down
356 changes: 356 additions & 0 deletions src/as_filename.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,356 @@
use alloc::ffi::CString;
use alloc::string::String;
use alloc::vec::Vec;
use core::ffi::{c_char, CStr};
use util::cstr_cow_from_bytes;
use Error;

mod private {

pub trait AsFilenameSeal {
#[allow(unused)] //Posix doesnt use this
fn windows_filename<R>(
&self,
function: impl FnOnce(*const u16) -> Result<R, crate::Error>,
) -> Result<R, crate::Error>;

#[allow(unused)] //Windows doesnt use this
fn posix_filename<R>(
&self,
function: impl FnOnce(*const core::ffi::c_char) -> Result<R, crate::Error>,
) -> Result<R, crate::Error>;
}
}

/// This trait is implemented on all types where libloading can derrive a filename from.
/// It is sealed and cannot be implemented by a user of libloading.
///
/// This trait is implemented for the following common types:
/// - String &String &str
/// - CString &CString &CStr
/// - OsString &OsString &OsStr
/// - PathBuf &PathBuf &Path
/// - &[u8] assumes utf8 data!
/// - &[u16] assumes utf16-ne data!
///
pub trait AsFilename: private::AsFilenameSeal {}

impl<T> AsFilename for T where T: private::AsFilenameSeal {}

impl private::AsFilenameSeal for &str {
fn windows_filename<R>(
&self,
function: impl FnOnce(*const u16) -> Result<R, Error>,
) -> Result<R, Error> {
let mut utf16: Vec<u16> = self.encode_utf16().collect();
if utf16.last() != Some(&0) {
utf16.push(0);
};
function(utf16.as_ptr())
}

fn posix_filename<R>(
&self,
function: impl FnOnce(*const c_char) -> Result<R, Error>,
) -> Result<R, Error> {
let cow = cstr_cow_from_bytes(self.as_bytes())?;
function(cow.as_ptr())
}
}

impl private::AsFilenameSeal for &String {
fn windows_filename<R>(
&self,
function: impl FnOnce(*const u16) -> Result<R, Error>,
) -> Result<R, Error> {
self.as_str().windows_filename(function)
}

fn posix_filename<R>(
&self,
function: impl FnOnce(*const c_char) -> Result<R, Error>,
) -> Result<R, Error> {
let cow = cstr_cow_from_bytes(self.as_bytes())?;
function(cow.as_ptr())
}
}

impl private::AsFilenameSeal for String {
fn windows_filename<R>(
&self,
function: impl FnOnce(*const u16) -> Result<R, Error>,
) -> Result<R, Error> {
self.as_str().windows_filename(function)
}

fn posix_filename<R>(
&self,
function: impl FnOnce(*const c_char) -> Result<R, Error>,
) -> Result<R, Error> {
let cow = cstr_cow_from_bytes(self.as_bytes())?;
function(cow.as_ptr())
}
}

impl private::AsFilenameSeal for &CStr {
fn windows_filename<R>(
&self,
function: impl FnOnce(*const u16) -> Result<R, Error>,
) -> Result<R, Error> {
//We assume cstr is utf-8 here, if it's something bespoke like CESU-8 (thanks java) then yeah... no.
let utf8 = self.to_str()?;
utf8.windows_filename(function)
}

fn posix_filename<R>(
&self,
function: impl FnOnce(*const c_char) -> Result<R, Error>,
) -> Result<R, Error> {
function(self.as_ptr())
}
}

impl private::AsFilenameSeal for &CString {
fn windows_filename<R>(
&self,
function: impl FnOnce(*const u16) -> Result<R, Error>,
) -> Result<R, Error> {
self.as_c_str().windows_filename(function)
}

fn posix_filename<R>(
&self,
function: impl FnOnce(*const c_char) -> Result<R, Error>,
) -> Result<R, Error> {
self.as_c_str().posix_filename(function)
}
}

impl private::AsFilenameSeal for CString {
fn windows_filename<R>(
&self,
function: impl FnOnce(*const u16) -> Result<R, Error>,
) -> Result<R, Error> {
self.as_c_str().windows_filename(function)
}

fn posix_filename<R>(
&self,
function: impl FnOnce(*const c_char) -> Result<R, Error>,
) -> Result<R, Error> {
self.as_c_str().posix_filename(function)
}
}

/// This implementation assumes that a slice always contains utf-8 bytes.
/// (which is likely the most common case if the slice originated in rust)
impl private::AsFilenameSeal for &[u8] {
fn windows_filename<R>(
&self,
function: impl FnOnce(*const u16) -> Result<R, Error>,
) -> Result<R, Error> {
let utf8 = core::str::from_utf8(self)?;
utf8.windows_filename(function)
}

fn posix_filename<R>(
&self,
function: impl FnOnce(*const c_char) -> Result<R, Error>,
) -> Result<R, Error> {
let utf8 = core::str::from_utf8(self)?;
utf8.posix_filename(function)
}
}

impl<const N: usize> private::AsFilenameSeal for [u8; N] {
fn windows_filename<R>(
&self,
function: impl FnOnce(*const u16) -> Result<R, Error>,
) -> Result<R, Error> {
self.as_slice().windows_filename(function)
}

fn posix_filename<R>(
&self,
function: impl FnOnce(*const c_char) -> Result<R, Error>,
) -> Result<R, Error> {
self.as_slice().posix_filename(function)
}
}

impl<const N: usize> private::AsFilenameSeal for &[u8; N] {
fn windows_filename<R>(
&self,
function: impl FnOnce(*const u16) -> Result<R, Error>,
) -> Result<R, Error> {
self.as_slice().windows_filename(function)
}

fn posix_filename<R>(
&self,
function: impl FnOnce(*const c_char) -> Result<R, Error>,
) -> Result<R, Error> {
self.as_slice().posix_filename(function)
}
}

/// This implementation assumes that the slice contains utf-16 in native endian.
/// Sidenote: For windows this is always utf-16-le because the last big endian Windows system was the xbox 360 that rust doesn't support.
/// For linux this is highly likely to also be utf-16-le because big endian is only used in some old mips routers or some IBM hardware.
impl private::AsFilenameSeal for &[u16] {
fn windows_filename<R>(
&self,
function: impl FnOnce(*const u16) -> Result<R, Error>,
) -> Result<R, Error> {
//Check that we have valid utf-16
for c in core::char::decode_utf16(self.iter().copied()) {
let _ = c?;
}

if self.last() != Some(&0) {
let mut copy = self.to_vec();
copy.push(0);
return function(copy.as_ptr());
}

function(self.as_ptr())
}

fn posix_filename<R>(
&self,
function: impl FnOnce(*const c_char) -> Result<R, Error>,
) -> Result<R, Error> {
let utf8 = String::from_utf16(self)?;
utf8.posix_filename(function)
}
}

#[cfg(feature = "std")]
#[cfg(any(windows, unix))]
mod std {
use as_filename::private;
use core::ffi::c_char;
use std::ffi::{OsStr, OsString};
use Error;

impl private::AsFilenameSeal for &OsStr {
#[cfg(unix)]
fn windows_filename<R>(
&self,
_function: impl FnOnce(*const u16) -> Result<R, Error>,
) -> Result<R, Error> {
panic!("windows_filename() not implemented for OsStr on posix platform");
}

#[cfg(windows)]
fn windows_filename<R>(
&self,
function: impl FnOnce(*const u16) -> Result<R, Error>,
) -> Result<R, Error> {
let mut utf16: alloc::vec::Vec<u16> =
std::os::windows::ffi::OsStrExt::encode_wide(*self).collect();
if utf16.last() != Some(&0) {
utf16.push(0);
};
function(utf16.as_ptr())
}

#[cfg(unix)]
fn posix_filename<R>(
&self,
function: impl FnOnce(*const c_char) -> Result<R, Error>,
) -> Result<R, Error> {
let cow =
crate::util::cstr_cow_from_bytes(std::os::unix::ffi::OsStrExt::as_bytes(*self))?;
function(cow.as_ptr())
}

#[cfg(windows)]
fn posix_filename<R>(
&self,
_function: impl FnOnce(*const c_char) -> Result<R, Error>,
) -> Result<R, Error> {
panic!("posix_filename() not implemented for OsStr on windows")
}
}

impl private::AsFilenameSeal for &OsString {
fn windows_filename<R>(
&self,
function: impl FnOnce(*const u16) -> Result<R, Error>,
) -> Result<R, Error> {
self.as_os_str().windows_filename(function)
}

fn posix_filename<R>(
&self,
function: impl FnOnce(*const c_char) -> Result<R, Error>,
) -> Result<R, Error> {
self.as_os_str().posix_filename(function)
}
}

impl private::AsFilenameSeal for OsString {
fn windows_filename<R>(
&self,
function: impl FnOnce(*const u16) -> Result<R, Error>,
) -> Result<R, Error> {
self.as_os_str().windows_filename(function)
}

fn posix_filename<R>(
&self,
function: impl FnOnce(*const c_char) -> Result<R, Error>,
) -> Result<R, Error> {
self.as_os_str().posix_filename(function)
}
}

impl private::AsFilenameSeal for std::path::PathBuf {
fn windows_filename<R>(
&self,
function: impl FnOnce(*const u16) -> Result<R, Error>,
) -> Result<R, Error> {
self.as_os_str().windows_filename(function)
}

fn posix_filename<R>(
&self,
function: impl FnOnce(*const c_char) -> Result<R, Error>,
) -> Result<R, Error> {
self.as_os_str().posix_filename(function)
}
}

impl private::AsFilenameSeal for &std::path::PathBuf {
fn windows_filename<R>(
&self,
function: impl FnOnce(*const u16) -> Result<R, Error>,
) -> Result<R, Error> {
self.as_os_str().windows_filename(function)
}

fn posix_filename<R>(
&self,
function: impl FnOnce(*const c_char) -> Result<R, Error>,
) -> Result<R, Error> {
self.as_os_str().posix_filename(function)
}
}

impl private::AsFilenameSeal for &std::path::Path {
fn windows_filename<R>(
&self,
function: impl FnOnce(*const u16) -> Result<R, Error>,
) -> Result<R, Error> {
self.as_os_str().windows_filename(function)
}

fn posix_filename<R>(
&self,
function: impl FnOnce(*const c_char) -> Result<R, Error>,
) -> Result<R, Error> {
self.as_os_str().posix_filename(function)
}
}
}
Loading