mirror of
https://codeberg.org/YGGverse/psocks.git
synced 2026-03-31 08:25:27 +00:00
disallow server start if at least one list parsed with errors; add initial listing api
This commit is contained in:
parent
0b08744f8f
commit
752ea23f80
4 changed files with 111 additions and 36 deletions
|
|
@ -22,9 +22,11 @@ RUST_LOG=trace cargo run -- --allow=http://localhost/allow.txt \
|
|||
```
|
||||
* set `socks5://127.0.0.1:1080` proxy in your application
|
||||
* use http://127.0.0.1:8010 for API:
|
||||
* `/api/allow/{domain.com}` - add rule to the current session
|
||||
* `/api/block/{domain.com}` - delete rule from the current session
|
||||
* `/api/allow/<domain.com>` - add rule to the current session
|
||||
* `/api/block/<domain.com>` - delete rule from the current session
|
||||
* `/api/rules` - return active rules (from server memory)
|
||||
* `/api/lists` - get parsed lists with its ID
|
||||
* `/api/list/<ID>` - get all parsed rules for list ID (see `/api/lists`)
|
||||
|
||||
### Allow list example
|
||||
|
||||
|
|
|
|||
26
src/main.rs
26
src/main.rs
|
|
@ -59,10 +59,30 @@ async fn api_block(
|
|||
#[rocket::get("/api/rules")]
|
||||
async fn api_rules(rules: &State<Arc<Rules>>) -> Result<Json<Vec<String>>, Status> {
|
||||
let active = rules.active().await;
|
||||
info!("Get rules (total: {})", active.len());
|
||||
debug!("Get rules (total: {})", active.len());
|
||||
Ok(Json(active))
|
||||
}
|
||||
|
||||
#[rocket::get("/api/lists")]
|
||||
async fn api_lists(rules: &State<Arc<Rules>>) -> Result<Json<Vec<rules::ListEntry>>, Status> {
|
||||
let lists = rules.lists();
|
||||
debug!("Get lists index (total: {})", lists.len());
|
||||
Ok(Json(lists))
|
||||
}
|
||||
|
||||
#[rocket::get("/api/list/<id>")]
|
||||
async fn api_list(
|
||||
id: usize,
|
||||
rules: &State<Arc<Rules>>,
|
||||
) -> Result<Json<Option<&rules::List>>, Status> {
|
||||
let list = rules.list(&id);
|
||||
debug!(
|
||||
"Get list #{id} rules (total: {:?})",
|
||||
list.map(|l| l.items.len())
|
||||
);
|
||||
Ok(Json(list))
|
||||
}
|
||||
|
||||
#[rocket::launch]
|
||||
async fn rocket() -> _ {
|
||||
env_logger::init();
|
||||
|
|
@ -94,7 +114,9 @@ async fn rocket() -> _ {
|
|||
.manage(Instant::now())
|
||||
.mount(
|
||||
"/",
|
||||
rocket::routes![index, api_totals, api_allow, api_block, api_rules],
|
||||
rocket::routes![
|
||||
index, api_totals, api_allow, api_block, api_rules, api_lists, api_list
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
113
src/rules.rs
113
src/rules.rs
|
|
@ -1,80 +1,104 @@
|
|||
mod item;
|
||||
|
||||
use anyhow::Result;
|
||||
use anyhow::{Result, bail};
|
||||
use item::Item;
|
||||
use log::*;
|
||||
use std::collections::HashSet;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use tokio::{fs, sync::RwLock};
|
||||
|
||||
/// In-memory registry, based on `--allow-list`
|
||||
pub struct Rules(RwLock<HashSet<Item>>);
|
||||
/// In-memory registry, based on the `--allow-list`
|
||||
pub struct Rules {
|
||||
/// Active, unique rules for this session
|
||||
active: RwLock<HashSet<Item>>,
|
||||
/// Hold original lists rules asset with unique ID generated once on server init
|
||||
/// * it allows to enable/disable specified rules in-memory by index ID
|
||||
lists: HashMap<usize, List>,
|
||||
}
|
||||
|
||||
impl Rules {
|
||||
/// Build new List object
|
||||
pub async fn from_opt(list: &Vec<String>) -> Result<Self> {
|
||||
fn handle(this: &mut HashSet<Item>, line: &str) -> Option<bool> {
|
||||
if line.starts_with("/") || line.starts_with("#") || line.is_empty() {
|
||||
return None;
|
||||
}
|
||||
Some(this.insert(Item::from_line(line)))
|
||||
}
|
||||
|
||||
let mut index = HashSet::new();
|
||||
pub async fn from_opt(list: &[String]) -> Result<Self> {
|
||||
let mut active = HashSet::new();
|
||||
let mut lists = HashMap::new();
|
||||
|
||||
let mut rules_total = 0;
|
||||
|
||||
for l in list {
|
||||
for line in if l.contains("://") {
|
||||
for (i, l) in list.iter().enumerate() {
|
||||
let mut list_items = HashSet::new();
|
||||
let list = if l.contains("://") {
|
||||
let response = reqwest::get(l).await?;
|
||||
let status = response.status();
|
||||
if status.is_success() {
|
||||
response.text().await?
|
||||
} else {
|
||||
warn!("Could not receive remote list `{l}`: `{status}`");
|
||||
continue;
|
||||
bail!("Could not receive remote list `{l}`: `{status}`")
|
||||
}
|
||||
} else {
|
||||
fs::read_to_string(l).await?
|
||||
}
|
||||
.lines()
|
||||
{
|
||||
if handle(&mut index, line).is_some_and(|status| !status) {
|
||||
warn!("Ruleset `{l}` contains duplicated entry: `{line}`")
|
||||
};
|
||||
for line in list.lines() {
|
||||
if line.starts_with("/") || line.starts_with("#") || line.is_empty() {
|
||||
continue; // skip comments
|
||||
}
|
||||
let item = Item::from_line(line);
|
||||
if !active.insert(item.clone()) {
|
||||
warn!("Ruleset index `{l}` contains duplicated entry: `{line}`")
|
||||
}
|
||||
if !list_items.insert(item) {
|
||||
warn!("List `{l}` contains duplicated entry: `{line}`")
|
||||
}
|
||||
rules_total += 1
|
||||
}
|
||||
assert!(
|
||||
lists
|
||||
.insert(
|
||||
i,
|
||||
List {
|
||||
alias: l.clone(),
|
||||
items: list_items
|
||||
}
|
||||
)
|
||||
.is_none()
|
||||
)
|
||||
}
|
||||
|
||||
let len = index.len();
|
||||
info!("Total rules parsed: {len} (added: {rules_total})",);
|
||||
info!(
|
||||
"Total rules parsed: {} (added: {rules_total}) / lists parsed: {} (added: {})",
|
||||
active.len(),
|
||||
list.len(),
|
||||
lists.len()
|
||||
);
|
||||
|
||||
Ok(Self(RwLock::new(index)))
|
||||
Ok(Self {
|
||||
active: RwLock::new(active),
|
||||
lists,
|
||||
})
|
||||
}
|
||||
/// Check if rule is exist in the (allow) index
|
||||
pub async fn any(&self, value: &str) -> bool {
|
||||
self.0.read().await.iter().any(|item| match item {
|
||||
self.active.read().await.iter().any(|item| match item {
|
||||
Item::Exact(v) => v == value,
|
||||
Item::Ending(v) => value.ends_with(v),
|
||||
})
|
||||
}
|
||||
/// Get total rules from the current session
|
||||
pub async fn total(&self) -> u64 {
|
||||
self.0.read().await.len() as u64
|
||||
self.active.read().await.len() as u64
|
||||
}
|
||||
/// Allow given `rule`
|
||||
/// * return `false` if the `rule` is exist
|
||||
pub async fn allow(&self, rule: &str) -> Result<bool> {
|
||||
Ok(self.0.write().await.insert(Item::from_line(rule)))
|
||||
Ok(self.active.write().await.insert(Item::from_line(rule)))
|
||||
}
|
||||
/// Block given `rule`
|
||||
/// * return `false` if the `rule` is not exist
|
||||
pub async fn block(&self, rule: &str) -> Result<bool> {
|
||||
Ok(self.0.write().await.remove(&Item::from_line(rule)))
|
||||
Ok(self.active.write().await.remove(&Item::from_line(rule)))
|
||||
}
|
||||
/// Return active rules (from server memory)
|
||||
/// Return active rules
|
||||
pub async fn active(&self) -> Vec<String> {
|
||||
let mut rules: Vec<String> = self
|
||||
.0
|
||||
.active
|
||||
.read()
|
||||
.await
|
||||
.iter()
|
||||
|
|
@ -83,4 +107,31 @@ impl Rules {
|
|||
rules.sort(); // HashSet does not keep the order
|
||||
rules
|
||||
}
|
||||
/// Return original list references
|
||||
pub fn lists(&self) -> Vec<ListEntry> {
|
||||
let mut list = Vec::with_capacity(self.lists.len());
|
||||
for l in self.lists.iter() {
|
||||
list.push(ListEntry {
|
||||
id: *l.0,
|
||||
alias: l.1.alias.clone(),
|
||||
})
|
||||
}
|
||||
list
|
||||
}
|
||||
/// Return original list references
|
||||
pub fn list(&self, id: &usize) -> Option<&List> {
|
||||
self.lists.get(id)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
pub struct ListEntry {
|
||||
pub id: usize,
|
||||
pub alias: String,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
pub struct List {
|
||||
pub alias: String,
|
||||
pub items: HashSet<Item>,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use log::debug;
|
||||
|
||||
#[derive(PartialEq, Eq, Hash)]
|
||||
#[derive(PartialEq, Eq, Hash, Clone, serde::Serialize)]
|
||||
pub enum Item {
|
||||
Ending(String),
|
||||
Exact(String),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue