summaryrefslogtreecommitdiffstats
path: root/vendor/iana-time-zone/src/tz_macos.rs
blob: 70c39e83f3f3c0240128c1bf1374ed94dde5243c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
pub(crate) fn get_timezone_inner() -> Result<String, crate::GetTimezoneError> {
    get_timezone().ok_or(crate::GetTimezoneError::OsError)
}

#[inline]
fn get_timezone() -> Option<String> {
    // The longest name in the IANA time zone database is 25 ASCII characters long.
    const MAX_LEN: usize = 32;
    let mut buf = [0; MAX_LEN];

    // Get system time zone, and borrow its name.
    let tz = system_time_zone::SystemTimeZone::new()?;
    let name = tz.name()?;

    // If the name is encoded in UTF-8, copy it directly.
    let name = if let Some(name) = name.as_utf8() {
        name
    } else {
        // Otherwise convert the name to UTF-8.
        name.to_utf8(&mut buf)?
    };

    if name.is_empty() || name.len() >= MAX_LEN {
        // The name should not be empty, or excessively long.
        None
    } else {
        Some(name.to_owned())
    }
}

mod system_time_zone {
    //! create a safe wrapper around `CFTimeZoneRef`

    use core_foundation_sys::base::{CFRelease, CFTypeRef};
    use core_foundation_sys::timezone::{CFTimeZoneCopySystem, CFTimeZoneGetName, CFTimeZoneRef};

    pub(crate) struct SystemTimeZone(CFTimeZoneRef);

    impl Drop for SystemTimeZone {
        fn drop(&mut self) {
            // SAFETY: `SystemTimeZone` is only ever created with a valid `CFTimeZoneRef`.
            unsafe { CFRelease(self.0 as CFTypeRef) };
        }
    }

    impl SystemTimeZone {
        pub(crate) fn new() -> Option<Self> {
            // SAFETY: No invariants to uphold. We'll release the pointer when we don't need it anymore.
            let v: CFTimeZoneRef = unsafe { CFTimeZoneCopySystem() };
            if v.is_null() {
                None
            } else {
                Some(SystemTimeZone(v))
            }
        }

        /// Get the time zone name as a [super::string_ref::StringRef].
        ///
        /// The lifetime of the `StringRef` is bound to our lifetime. Mutable
        /// access is also prevented by taking a reference to `self`.
        pub(crate) fn name(&self) -> Option<super::string_ref::StringRef<'_, Self>> {
            // SAFETY: `SystemTimeZone` is only ever created with a valid `CFTimeZoneRef`.
            let string = unsafe { CFTimeZoneGetName(self.0) };
            if string.is_null() {
                None
            } else {
                // SAFETY: here we ensure that `string` is a valid pointer.
                Some(unsafe { super::string_ref::StringRef::new(string, self) })
            }
        }
    }
}

mod string_ref {
    //! create safe wrapper around `CFStringRef`

    use std::convert::TryInto;

    use core_foundation_sys::base::{Boolean, CFRange};
    use core_foundation_sys::string::{
        kCFStringEncodingUTF8, CFStringGetBytes, CFStringGetCStringPtr, CFStringGetLength,
        CFStringRef,
    };

    pub(crate) struct StringRef<'a, T> {
        string: CFStringRef,
        // We exclude mutable access to the parent by taking a reference to the
        // parent (rather than, for example, just using a marker to enforce the
        // parent's lifetime).
        _parent: &'a T,
    }

    impl<'a, T> StringRef<'a, T> {
        // SAFETY: `StringRef` must be valid pointer
        pub(crate) unsafe fn new(string: CFStringRef, _parent: &'a T) -> Self {
            Self { string, _parent }
        }

        pub(crate) fn as_utf8(&self) -> Option<&'a str> {
            // SAFETY: `StringRef` is only ever created with a valid `CFStringRef`.
            let v = unsafe { CFStringGetCStringPtr(self.string, kCFStringEncodingUTF8) };
            if !v.is_null() {
                // SAFETY: `CFStringGetCStringPtr()` returns NUL-terminated strings.
                let v = unsafe { std::ffi::CStr::from_ptr(v) };
                if let Ok(v) = v.to_str() {
                    return Some(v);
                }
            }
            None
        }

        pub(crate) fn to_utf8<'b>(&self, buf: &'b mut [u8]) -> Option<&'b str> {
            // SAFETY: `StringRef` is only ever created with a valid `CFStringRef`.
            let length = unsafe { CFStringGetLength(self.string) };

            let mut buf_bytes = 0;
            let range = CFRange {
                location: 0,
                length,
            };

            let converted_bytes = unsafe {
                // SAFETY: `StringRef` is only ever created with a valid `CFStringRef`.
                CFStringGetBytes(
                    self.string,
                    range,
                    kCFStringEncodingUTF8,
                    b'\0',
                    false as Boolean,
                    buf.as_mut_ptr(),
                    buf.len() as isize,
                    &mut buf_bytes,
                )
            };
            if converted_bytes != length {
                return None;
            }

            let len = buf_bytes.try_into().ok()?;
            let s = buf.get(..len)?;
            std::str::from_utf8(s).ok()
        }
    }
}