Skip to main content

varnish_sys/
txt.rs

1use std::ffi::{c_char, CStr};
2use std::slice::from_raw_parts;
3use std::str::from_utf8;
4
5use crate::ffi::txt;
6use crate::vcl::StrOrBytes;
7
8impl txt {
9    /// Internal helper to create a `txt` struct from a byte slice.
10    /// The entire slice is assumed to not contain any null bytes.
11    fn from_bytes(s: &[u8]) -> Self {
12        Self {
13            b: s.as_ptr().cast::<c_char>(),
14            e: unsafe { s.as_ptr().add(s.len()).cast::<c_char>() },
15        }
16    }
17
18    /// FIXME: This method is only used when calling [`crate::ffi::VSLbt`],
19    /// and current implementation creates a string without a null terminator to pass it in.
20    /// Going forward, we should probably refactor it to avoid extra string allocation.
21    #[expect(clippy::should_implement_trait)]
22    pub fn from_str(s: &str) -> Self {
23        Self::from_bytes(s.as_bytes())
24    }
25
26    pub fn from_cstr(s: &CStr) -> Self {
27        Self::from_bytes(s.to_bytes())
28    }
29
30    /// Convert the `txt` struct to a `&[u8]`.
31    /// We want to explicitly differentiate between empty (`None`) and null (`Some([])`) strings.
32    #[expect(clippy::wrong_self_convention)] // TODO: drop Copy trait for txt?
33    pub fn to_slice(&self) -> Option<&[u8]> {
34        if self.b.is_null() {
35            None
36        } else {
37            // SAFETY: We assume that txt instance was created correctly,
38            //         so the pointers are valid and the end is after the beginning.
39            //         Txt instances are part of ffi, so inherently unsafe.
40            unsafe {
41                Some(from_raw_parts(
42                    self.b.cast::<u8>(),
43                    self.e.offset_from(self.b) as usize,
44                ))
45            }
46        }
47    }
48
49    /// Convert the `txt` struct to a `StrOrBytes` enum.
50    #[expect(clippy::wrong_self_convention)] // TODO: drop Copy trait for txt?
51    pub fn to_str(&self) -> Option<StrOrBytes<'_>> {
52        self.to_slice().map(StrOrBytes::from)
53    }
54
55    /// Parse the `txt` struct as a header, returning a tuple with the key and value,
56    /// trimming the value of leading whitespace.
57    pub fn parse_header(&self) -> Option<(&str, StrOrBytes<'_>)> {
58        // We expect varnishd to always given us a string with a ':' in it
59        // If it's not the case, blow up as it might be a sign of a bigger problem.
60        let slice = self.to_slice()?;
61        let index = slice
62            .iter()
63            .position(|c| *c == b':')
64            .expect("headers should always have a :");
65
66        let (key_slice, value_slice) = slice.split_at(index);
67
68        Some((
69            from_utf8(key_slice).expect("header names must be UTF-8"),
70            value_slice[1..].trim_ascii_start().into(),
71        ))
72    }
73}