use std::cell::UnsafeCell;
use std::ffi::CString;
use std::marker::PhantomData;
use coraza_sys::*;
use crate::error::Error;
use crate::intervention::Intervention;
pub struct Transaction {
handle: coraza_transaction_t,
closed: bool,
_phantom: PhantomData<UnsafeCell<i32>>, }
impl Transaction {
pub(crate) fn new(handle: coraza_transaction_t) -> Self {
Self {
handle,
closed: false,
_phantom: PhantomData,
}
}
pub fn is_closed(&self) -> bool {
self.closed
}
pub fn process_connection(
&mut self,
src_addr: &str,
client_port: i32,
server_host: &str,
server_port: i32,
) -> Result<(), Error> {
self.ensure_open()?;
let c_src = CString::new(src_addr).map_err(|_| Error::InvalidTransaction)?;
let c_host = CString::new(server_host).map_err(|_| Error::InvalidTransaction)?;
let ret = unsafe {
coraza_process_connection(
self.handle,
c_src.as_ptr(),
client_port,
c_host.as_ptr(),
server_port,
)
};
check_result(ret)
}
pub fn process_uri(&mut self, uri: &str, method: &str, proto: &str) -> Result<(), Error> {
self.ensure_open()?;
let c_uri = CString::new(uri).map_err(|_| Error::InvalidTransaction)?;
let c_method = CString::new(method).map_err(|_| Error::InvalidTransaction)?;
let c_proto = CString::new(proto).map_err(|_| Error::InvalidTransaction)?;
let ret = unsafe {
coraza_process_uri(
self.handle,
c_uri.as_ptr(),
c_method.as_ptr(),
c_proto.as_ptr(),
)
};
check_result(ret)
}
pub fn add_request_header(&mut self, name: &str, value: &str) {
if self.closed {
return;
}
let c_name = match CString::new(name) {
Ok(s) => s,
Err(_) => return,
};
let c_value = match CString::new(value) {
Ok(s) => s,
Err(_) => return,
};
unsafe {
coraza_add_request_header(
self.handle,
c_name.as_ptr(),
name.len() as i32,
c_value.as_ptr(),
value.len() as i32,
);
}
}
pub fn add_request_headers(&mut self, headers: &[(&str, &str)]) {
if self.closed || headers.is_empty() {
return;
}
let packed = pack_headers(headers);
unsafe {
coraza_add_request_headers(
self.handle,
packed.as_ptr() as *const i8,
packed.len() as i32,
headers.len() as i32,
);
}
}
pub fn process_request_headers(&mut self) -> Result<(), Error> {
self.ensure_open()?;
let ret = unsafe { coraza_process_request_headers(self.handle) };
check_result(ret)
}
pub fn add_get_argument(&mut self, name: &str, value: &str) {
if self.closed {
return;
}
let c_name = match CString::new(name) {
Ok(s) => s,
Err(_) => return,
};
let c_value = match CString::new(value) {
Ok(s) => s,
Err(_) => return,
};
unsafe {
coraza_add_get_args(self.handle, c_name.as_ptr(), c_value.as_ptr());
}
}
pub fn append_request_body(&mut self, data: &[u8]) -> Result<(), Error> {
self.ensure_open()?;
if data.is_empty() {
return Ok(());
}
let ret =
unsafe { coraza_append_request_body(self.handle, data.as_ptr(), data.len() as i32) };
if ret != 0 {
return Err(Error::BodyOperation("failed to append request body".into()));
}
Ok(())
}
pub fn process_request_body(&mut self) -> Result<(), Error> {
self.ensure_open()?;
let ret = unsafe { coraza_process_request_body(self.handle) };
check_result(ret)
}
pub fn request_body_from_file(&mut self, path: &str) -> Result<(), Error> {
self.ensure_open()?;
let c_path = CString::new(path).map_err(|_| Error::InvalidTransaction)?;
let ret = unsafe { coraza_request_body_from_file(self.handle, c_path.as_ptr()) };
if ret != 0 {
return Err(Error::BodyOperation(format!(
"failed to read body from file: {}",
path
)));
}
Ok(())
}
pub fn set_status_code(&mut self, code: i32) -> Result<(), Error> {
self.ensure_open()?;
let ret = unsafe { coraza_update_status_code(self.handle, code) };
if ret != 0 {
return Err(Error::InvalidTransaction);
}
Ok(())
}
pub fn add_response_header(&mut self, name: &str, value: &str) {
if self.closed {
return;
}
let c_name = match CString::new(name) {
Ok(s) => s,
Err(_) => return,
};
let c_value = match CString::new(value) {
Ok(s) => s,
Err(_) => return,
};
unsafe {
coraza_add_response_header(
self.handle,
c_name.as_ptr(),
name.len() as i32,
c_value.as_ptr(),
value.len() as i32,
);
}
}
pub fn add_response_headers(&mut self, headers: &[(&str, &str)]) {
if self.closed || headers.is_empty() {
return;
}
let packed = pack_headers(headers);
unsafe {
coraza_add_response_headers(
self.handle,
packed.as_ptr() as *const i8,
packed.len() as i32,
headers.len() as i32,
);
}
}
pub fn process_response_headers(&mut self, status: i32, proto: &str) -> Result<(), Error> {
self.ensure_open()?;
let c_proto = CString::new(proto).map_err(|_| Error::InvalidTransaction)?;
let ret = unsafe { coraza_process_response_headers(self.handle, status, c_proto.as_ptr()) };
check_result(ret)
}
pub fn append_response_body(&mut self, data: &[u8]) -> Result<(), Error> {
self.ensure_open()?;
if data.is_empty() {
return Ok(());
}
let ret =
unsafe { coraza_append_response_body(self.handle, data.as_ptr(), data.len() as i32) };
if ret != 0 {
return Err(Error::BodyOperation(
"failed to append response body".into(),
));
}
Ok(())
}
pub fn is_response_body_processable(&self) -> bool {
if self.closed {
return false;
}
unsafe { coraza_is_response_body_processable(self.handle) != 0 }
}
pub fn process_response_body(&mut self) -> Result<(), Error> {
self.ensure_open()?;
let ret = unsafe { coraza_process_response_body(self.handle) };
check_result(ret)
}
pub fn process_logging(&mut self) {
if self.closed {
return;
}
unsafe {
coraza_process_logging(self.handle);
}
}
pub fn intervention(&self) -> Option<Intervention> {
if self.closed {
return None;
}
let ptr = unsafe { coraza_intervention(self.handle) };
if ptr.is_null() {
return None;
}
let intervention = unsafe { &*ptr };
let result = Intervention {
action: if intervention.action.is_null() {
String::new()
} else {
unsafe { std::ffi::CStr::from_ptr(intervention.action) }
.to_string_lossy()
.into_owned()
},
status: intervention.status,
data: if intervention.data.is_null() {
None
} else {
Some(
unsafe { std::ffi::CStr::from_ptr(intervention.data) }
.to_string_lossy()
.into_owned(),
)
},
rule_id: intervention.rule_id,
};
unsafe {
coraza_free_intervention(ptr);
}
Some(result)
}
pub fn close(&mut self) -> Result<(), Error> {
if self.closed {
return Ok(());
}
self.closed = true;
let ret = unsafe { coraza_free_transaction(self.handle) };
if ret != 0 {
return Err(Error::InvalidTransaction);
}
Ok(())
}
fn ensure_open(&self) -> Result<(), Error> {
if self.closed {
return Err(Error::InvalidTransaction);
}
Ok(())
}
}
impl Drop for Transaction {
fn drop(&mut self) {
if !self.closed {
unsafe {
coraza_free_transaction(self.handle);
}
}
}
}
fn check_result(ret: i32) -> Result<(), Error> {
match ret {
coraza_result_t_CORAZA_OK => Ok(()),
coraza_result_t_CORAZA_INTERRUPTION => Err(Error::Intervention {
action: String::new(),
status: 0,
data: None,
rule_id: 0,
}),
_ => Err(Error::RuleEngineError),
}
}
fn pack_headers(headers: &[(&str, &str)]) -> Vec<u8> {
let total_size: usize = headers.iter().map(|(n, v)| 2 + n.len() + 4 + v.len()).sum();
let mut buf = Vec::with_capacity(total_size);
for (name, value) in headers {
let name_len = name.len() as u16;
buf.extend_from_slice(&name_len.to_be_bytes());
buf.extend_from_slice(name.as_bytes());
let value_len = value.len() as u32;
buf.extend_from_slice(&value_len.to_be_bytes());
buf.extend_from_slice(value.as_bytes());
}
buf
}