diff --git a/Cargo.toml b/Cargo.toml index 8d88aae..12e40be 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "iana-time-zone" description = "get the IANA time zone for the current system" -version = "0.1.44" +version = "0.1.45" authors = ["Andrew Straw "] repository = "https://github.com/strawlab/iana-time-zone" license = "MIT OR Apache-2.0" diff --git a/src/tz_macos.rs b/src/tz_macos.rs index a42c00a..41ead59 100644 --- a/src/tz_macos.rs +++ b/src/tz_macos.rs @@ -1,22 +1,61 @@ -use core_foundation_sys::base::{CFRelease, CFTypeRef}; -use core_foundation_sys::string::{kCFStringEncodingUTF8, CFStringGetCStringPtr}; +use core_foundation_sys::base::{Boolean, CFRange, CFRelease, CFTypeRef}; +use core_foundation_sys::string::{ + kCFStringEncodingUTF8, CFStringGetBytes, CFStringGetCStringPtr, CFStringGetLength, +}; use core_foundation_sys::timezone::{CFTimeZoneCopySystem, CFTimeZoneGetName}; pub(crate) fn get_timezone_inner() -> Result { - unsafe { - Dropping::new(CFTimeZoneCopySystem()) - .and_then(|tz| Dropping::new(CFTimeZoneGetName(tz.0))) - .and_then(|name| { - let name = CFStringGetCStringPtr(name.0, kCFStringEncodingUTF8); - if name.is_null() { - None - } else { - Some(name) - } - }) - .and_then(|name| std::ffi::CStr::from_ptr(name).to_str().ok()) - .map(|name| name.to_owned()) - .ok_or(crate::GetTimezoneError::OsError) + unsafe { get_timezone().ok_or(crate::GetTimezoneError::OsError) } +} + +#[inline] +unsafe fn get_timezone() -> Option { + // The longest name in the IANA time zone database is 25 ASCII characters long. + const MAX_LEN: usize = 32; + + // Get system time zone, and borrow its name. + let tz = Dropping::new(CFTimeZoneCopySystem())?; + let name = CFTimeZoneGetName(tz.0); + if name.is_null() { + return None; + } + + // If the name is encoded in UTF-8, copy it directly. + let cstr = CFStringGetCStringPtr(name, kCFStringEncodingUTF8); + if !cstr.is_null() { + let cstr = std::ffi::CStr::from_ptr(cstr); + if let Ok(name) = cstr.to_str() { + return Some(name.to_owned()); + } + } + + // Otherwise convert the name to UTF-8. + let mut buf = [0; MAX_LEN]; + let mut buf_bytes = 0; + let range = CFRange { + location: 0, + length: CFStringGetLength(name), + }; + if CFStringGetBytes( + name, + range, + kCFStringEncodingUTF8, + b'\0', + false as Boolean, + buf.as_mut_ptr(), + buf.len() as isize, + &mut buf_bytes, + ) != range.length + { + // Could not convert the name. + None + } else if !(1..MAX_LEN as isize).contains(&buf_bytes) { + // The name should not be empty, or excessively long. + None + } else { + // Convert the name to a `String`. + let name = core::str::from_utf8(&buf[..buf_bytes as usize]).ok()?; + Some(name.to_owned()) } }