add base: browser window, hints, history, cookies, adblock

This commit is contained in:
2026-10-15 23:03:56 +02:00
commit a841178cf7
26 changed files with 2711 additions and 0 deletions
+182
View File
@@ -0,0 +1,182 @@
//! C API over adblock-rust's network blocking. Declared in `att_adblock.h`.
//!
//! Every entry point catches panics: unwinding into C++ is undefined behaviour,
//! and a bad filter list or URL should never take the browser down.
use std::ffi::{c_char, CStr};
use std::panic::{catch_unwind, AssertUnwindSafe};
use std::ptr;
use adblock::lists::{FilterSet, ParseOptions};
use adblock::request::Request;
use adblock::Engine;
pub struct AttAdblockEngine(Engine);
fn into_raw(engine: Engine) -> *mut AttAdblockEngine {
Box::into_raw(Box::new(AttAdblockEngine(engine)))
}
unsafe fn bytes<'a>(data: *const u8, len: usize) -> &'a [u8] {
if data.is_null() || len == 0 {
&[]
} else {
std::slice::from_raw_parts(data, len)
}
}
unsafe fn str_arg<'a>(s: *const c_char) -> Option<&'a str> {
if s.is_null() {
None
} else {
CStr::from_ptr(s).to_str().ok()
}
}
/// Compiles an engine from filter list text (Adblock Plus / uBlock syntax;
/// several lists may simply be concatenated). Returns null on failure.
#[no_mangle]
pub unsafe extern "C" fn att_adblock_engine_from_rules(
rules: *const u8,
len: usize,
) -> *mut AttAdblockEngine {
let text = String::from_utf8_lossy(bytes(rules, len)).into_owned();
catch_unwind(move || {
let mut set = FilterSet::new(false);
set.add_filter_list(text, ParseOptions::default());
into_raw(Engine::new_with_filter_set(set))
})
.unwrap_or(ptr::null_mut())
}
/// Loads an engine saved by `att_adblock_engine_serialize`. Returns null if
/// the data is corrupt or was written by an incompatible adblock-rust version.
#[no_mangle]
pub unsafe extern "C" fn att_adblock_engine_deserialize(
data: *const u8,
len: usize,
) -> *mut AttAdblockEngine {
let data = bytes(data, len);
catch_unwind(|| {
let mut engine = Engine::default();
match engine.deserialize(data) {
Ok(()) => into_raw(engine),
Err(_) => ptr::null_mut(),
}
})
.unwrap_or(ptr::null_mut())
}
/// Serializes the engine. The returned buffer of `*out_len` bytes must be
/// released with `att_adblock_buffer_free`. Returns null on failure.
#[no_mangle]
pub unsafe extern "C" fn att_adblock_engine_serialize(
engine: *const AttAdblockEngine,
out_len: *mut usize,
) -> *mut u8 {
if engine.is_null() || out_len.is_null() {
return ptr::null_mut();
}
let engine = &(*engine).0;
match catch_unwind(AssertUnwindSafe(|| engine.serialize())) {
Ok(data) => {
let data = data.into_boxed_slice();
*out_len = data.len();
Box::into_raw(data) as *mut u8
}
Err(_) => ptr::null_mut(),
}
}
#[no_mangle]
pub unsafe extern "C" fn att_adblock_buffer_free(data: *mut u8, len: usize) {
if !data.is_null() {
drop(Box::from_raw(ptr::slice_from_raw_parts_mut(data, len)));
}
}
#[no_mangle]
pub unsafe extern "C" fn att_adblock_engine_free(engine: *mut AttAdblockEngine) {
if !engine.is_null() {
drop(Box::from_raw(engine));
}
}
/// Whether a request for `url`, made by a page at `source_url`, should be
/// blocked. `request_type` uses the webRequest names ("script", "image",
/// "xhr", "sub_frame", ...) and `method` an HTTP method, in any case. Safe to
/// call from several threads at once.
#[no_mangle]
pub unsafe extern "C" fn att_adblock_should_block(
engine: *const AttAdblockEngine,
url: *const c_char,
source_url: *const c_char,
request_type: *const c_char,
method: *const c_char,
) -> bool {
if engine.is_null() {
return false;
}
let engine = &(*engine).0;
let (Some(url), Some(source_url), Some(request_type)) =
(str_arg(url), str_arg(source_url), str_arg(request_type))
else {
return false;
};
let method = str_arg(method).unwrap_or("GET").to_ascii_lowercase();
catch_unwind(AssertUnwindSafe(|| {
match Request::new(url, source_url, request_type, &method) {
Ok(request) => engine.check_network_request(&request).should_block(),
Err(_) => false,
}
}))
.unwrap_or(false)
}
#[cfg(test)]
mod tests {
use super::*;
use std::ffi::CString;
fn check(engine: *const AttAdblockEngine, url: &str, source: &str, ty: &str) -> bool {
let (u, s, t, m) = (
CString::new(url).unwrap(),
CString::new(source).unwrap(),
CString::new(ty).unwrap(),
CString::new("GET").unwrap(),
);
unsafe { att_adblock_should_block(engine, u.as_ptr(), s.as_ptr(), t.as_ptr(), m.as_ptr()) }
}
#[test]
fn blocks_exceptions_and_round_trips() {
let rules = b"||ads.example.com^\n/banner/*$image\n@@||ads.example.com/ok.js\n";
unsafe {
let engine = att_adblock_engine_from_rules(rules.as_ptr(), rules.len());
assert!(!engine.is_null());
let page = "https://news.example.org/";
assert!(check(engine, "https://ads.example.com/x.js", page, "script"));
assert!(!check(engine, "https://ads.example.com/ok.js", page, "script"));
assert!(check(engine, "https://cdn.example.org/banner/a.png", page, "image"));
assert!(!check(engine, "https://cdn.example.org/banner/a.js", page, "script"));
assert!(!check(engine, "https://news.example.org/app.js", page, "script"));
let mut len = 0;
let data = att_adblock_engine_serialize(engine, &mut len);
assert!(!data.is_null() && len > 0);
let restored = att_adblock_engine_deserialize(data, len);
att_adblock_buffer_free(data, len);
assert!(!restored.is_null());
assert!(check(restored, "https://ads.example.com/x.js", page, "script"));
assert!(!check(restored, "https://ads.example.com/ok.js", page, "script"));
let junk = b"not an engine";
assert!(att_adblock_engine_deserialize(junk.as_ptr(), junk.len()).is_null());
att_adblock_engine_free(engine);
att_adblock_engine_free(restored);
}
}
}