Add a simple StringTable wrapper around string table section bytes
This interprets the string table section's data bytes as &str references with lifetime checking bound to the underlying section data lifetime, so the underyling data bytes for the string contents aren't copied or re-allocated when getting a string from the table. It does do two read passes over the string contents, first to find the terminating NUL byte and second to do Utf8 validation checking. I opted to do the Utf8 checking because my goal for this crate is to have zero unsafe code in it, and the unchecked from_utf8 is unsafe. I have not yet thought through what opinion I want this crate to have w.r.t. reading malformed data in general but so far I've been making it return errors as it encounters it.
This commit is contained in:
parent
a7ce73ee98
commit
3455f4dd1e
@ -13,6 +13,7 @@ pub mod parse;
|
||||
use crate::parse::{Parse, Reader, read_u16, read_u32, read_u64};
|
||||
|
||||
mod utils;
|
||||
mod string_table;
|
||||
|
||||
pub struct File {
|
||||
pub ehdr: file::FileHeader,
|
||||
@ -58,6 +59,12 @@ impl std::convert::From<std::io::Error> for ParseError {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<std::str::Utf8Error> for ParseError {
|
||||
fn from(e: std::str::Utf8Error) -> Self {
|
||||
ParseError(e.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<std::string::FromUtf8Error> for ParseError {
|
||||
fn from(e: std::string::FromUtf8Error) -> Self {
|
||||
ParseError(e.to_string())
|
||||
|
77
src/string_table.rs
Normal file
77
src/string_table.rs
Normal file
@ -0,0 +1,77 @@
|
||||
use crate::ParseError;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct StringTable<'data> {
|
||||
#[allow(dead_code)]
|
||||
data: Option<&'data [u8]>,
|
||||
}
|
||||
|
||||
impl<'data> StringTable<'data> {
|
||||
#[allow(dead_code)]
|
||||
pub fn new(data: &'data [u8]) -> Self {
|
||||
StringTable { data: Some(data) }
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn get(&self, offset: usize) -> Result<&'data str, ParseError> {
|
||||
if self.data.is_none() {
|
||||
return Err(ParseError(format!(
|
||||
"Invalid offset into empty string table: {offset}"
|
||||
)));
|
||||
}
|
||||
|
||||
let start = self.data.unwrap().split_at(offset).1;
|
||||
let end = start
|
||||
.iter()
|
||||
.position(|&b| b == 0u8)
|
||||
.ok_or(ParseError(format!(
|
||||
"Invalid string table contents. Could not find terminating NUL byte."
|
||||
)))?;
|
||||
|
||||
let substr = start.split_at(end).0;
|
||||
let string = std::str::from_utf8(substr)?;
|
||||
Ok(string)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'data> Default for StringTable<'data> {
|
||||
fn default() -> Self {
|
||||
StringTable { data: None }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_empty_table_errors() {
|
||||
let st = StringTable::default();
|
||||
assert!(st.get(0).is_err());
|
||||
assert!(st.get(1).is_err());
|
||||
}
|
||||
|
||||
/// Note: ELF string tables are defined to always start with a NUL and use
|
||||
/// index 0 to give an empty string, so getting a string starting at a NUL
|
||||
/// should properly give an empty string.
|
||||
#[test]
|
||||
fn test_get_index_0_gives_empty_string() {
|
||||
let data = [0u8, 42u8, 0u8];
|
||||
let st = StringTable::new(&data);
|
||||
assert_eq!(st.get(0).unwrap(), "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_string_works() {
|
||||
let data = [0u8, 0x45, 0x4C, 0x46, 0u8];
|
||||
let st = StringTable::new(&data);
|
||||
assert_eq!(st.get(1).unwrap(), "ELF");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_with_malformed_table_no_trailing_nul() {
|
||||
let data = [0u8, 0x45, 0x4C, 0x46];
|
||||
let st = StringTable::new(&data);
|
||||
assert!(st.get(1).is_err());
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user