use crate::error::{CsvError, Result};
use crate::from_csv::config::FromCsvConfig;
pub(crate) fn validate_headers(headers: &csv::StringRecord, config: &FromCsvConfig) -> Result<()> {
let column_count = headers.len();
if column_count > config.max_columns {
return Err(CsvError::Security {
limit_type: "column count".to_string(),
limit: config.max_columns,
actual: column_count,
message: format!(
"CSV has {} columns, exceeds limit of {}",
column_count, config.max_columns
),
});
}
let header_size: usize = headers.iter().map(str::len).sum();
if header_size > config.max_header_size {
return Err(CsvError::Security {
limit_type: "header size".to_string(),
limit: config.max_header_size,
actual: header_size,
message: format!(
"CSV header size {} bytes, exceeds limit of {} bytes",
header_size, config.max_header_size
),
});
}
for (i, header) in headers.iter().enumerate() {
if header.len() > config.max_cell_size {
let preview = if header.len() > 100 {
let mut preview_end = 100;
while !header.is_char_boundary(preview_end) && preview_end > 0 {
preview_end -= 1;
}
format!("{}...", &header[..preview_end])
} else {
header.to_string()
};
return Err(CsvError::Security {
limit_type: "column name size".to_string(),
limit: config.max_cell_size,
actual: header.len(),
message: format!(
"Column name '{}' at index {} is {} bytes, exceeds cell size limit of {} bytes",
preview,
i,
header.len(),
config.max_cell_size
),
});
}
}
Ok(())
}
pub(crate) fn validate_cell(
cell: &str,
row: usize,
column: usize,
config: &FromCsvConfig,
) -> Result<()> {
if cell.len() > config.max_cell_size {
let preview = if cell.len() > 100 {
let mut preview_end = 100;
while !cell.is_char_boundary(preview_end) && preview_end > 0 {
preview_end -= 1;
}
format!("{}...", &cell[..preview_end])
} else {
cell.to_string()
};
return Err(CsvError::Security {
limit_type: "cell size".to_string(),
limit: config.max_cell_size,
actual: cell.len(),
message: format!(
"Cell at row {}, column {} is {} bytes, exceeds limit of {} bytes. Content preview: '{}'",
row,
column,
cell.len(),
config.max_cell_size,
preview
),
});
}
Ok(())
}
pub(crate) struct CsvSizeTracker {
pub(crate) bytes_read: usize,
max_total_size: usize,
}
impl CsvSizeTracker {
pub(crate) fn new(max_total_size: usize) -> Self {
Self {
bytes_read: 0,
max_total_size,
}
}
pub(crate) fn track_record(&mut self, record: &csv::StringRecord) -> Result<()> {
let record_size: usize = record.iter().map(str::len).sum();
self.bytes_read += record_size;
if self.bytes_read > self.max_total_size {
return Err(CsvError::Security {
limit_type: "total size".to_string(),
limit: self.max_total_size,
actual: self.bytes_read,
message: format!(
"CSV total size {} bytes exceeds limit of {} bytes",
self.bytes_read, self.max_total_size
),
});
}
Ok(())
}
}