Initial commit
This commit is contained in:
commit
67efc837b2
15 changed files with 703 additions and 0 deletions
1
.envrc
Normal file
1
.envrc
Normal file
|
|
@ -0,0 +1 @@
|
|||
use nix
|
||||
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
.direnv/
|
||||
/bombai.toml
|
||||
|
||||
|
||||
# Added by cargo
|
||||
|
||||
/target
|
||||
13
Caddyfile
Normal file
13
Caddyfile
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
git.example.com {
|
||||
@read method GET HEAD
|
||||
reverse_proxy @read 127.0.0.1:42067 {
|
||||
@fallback status 421
|
||||
handle_response @fallback
|
||||
|
||||
@iocaine status 423
|
||||
handle_response @iocaine {
|
||||
reverse_proxy 127.0.0.1:42069 # iocaine needs to be configured to always serve its poison for this.
|
||||
}
|
||||
}
|
||||
reverse_proxy localhost:42067
|
||||
}
|
||||
110
Cargo.lock
generated
Normal file
110
Cargo.lock
generated
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "adler2"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
||||
|
||||
[[package]]
|
||||
name = "bombai"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"deborrow",
|
||||
"flate2",
|
||||
"horrorhttp",
|
||||
"microlock",
|
||||
"nanoserde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deborrow"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5f99cc7a6632788aab2c734a6e1e1d8302658f70fb45d99a6e32154a24cc2a2"
|
||||
dependencies = [
|
||||
"deborrow-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deborrow-macro"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "551a13c0871ba8964b30d2407fdfd4c9b8e5f289950c152ff3d0d8de5be6b948"
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "horrorhttp"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9940b42c31d4b7bc31b55b7aaa8c3bfb08ab9ffc5142aa7f882ff7a59719fd1a"
|
||||
dependencies = [
|
||||
"readformat",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "microlock"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72e86c3eafbef4b37cd39b2ee82cc06d4d76231f86f230d14cf8ebdf304125bd"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.8.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
|
||||
dependencies = [
|
||||
"adler2",
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nanoserde"
|
||||
version = "0.2.1"
|
||||
source = "git+https://github.com/tudbut/nanoserde#fc010f51957432aec80dba0a70af0dadc3cbe38f"
|
||||
dependencies = [
|
||||
"nanoserde-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nanoserde-derive"
|
||||
version = "0.2.1"
|
||||
source = "git+https://github.com/tudbut/nanoserde#fc010f51957432aec80dba0a70af0dadc3cbe38f"
|
||||
|
||||
[[package]]
|
||||
name = "readformat"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e1fd097dab477324dfeb476d75627e38fd1a7437bc0e94751084be499d7e6b1"
|
||||
|
||||
[[package]]
|
||||
name = "simd-adler32"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2"
|
||||
11
Cargo.toml
Normal file
11
Cargo.toml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
[package]
|
||||
name = "bombai"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
deborrow = "0.3.1"
|
||||
flate2 = "1.1.5"
|
||||
horrorhttp = "0.2.1"
|
||||
microlock = "0.3.1"
|
||||
nanoserde.git = "https://github.com/tudbut/nanoserde"
|
||||
33
LICENSE
Normal file
33
LICENSE
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
WTFPL+-AI
|
||||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE PLUS MINUS AI
|
||||
Version 1, August 2025
|
||||
|
||||
Copyright (C) 2025 TudbuT
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim or modified
|
||||
copies of this license document, and changing it is allowed as long
|
||||
as the name is changed.
|
||||
|
||||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE PLUS MINUS AI
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. You do not willingly contribute to the spread/use of
|
||||
Generative AI technologies with, within, or without this
|
||||
work.
|
||||
1. You do not relicense this work.
|
||||
2. Otherwise you just DO WHAT THE FUCK YOU WANT TO!
|
||||
|
||||
DISCLAIMER
|
||||
This software is provided "AS IS", without warranty of any kind,
|
||||
express or implied, including but not limited to the warranties of
|
||||
merchantability, fitness for a particular purpose and noninfringement.
|
||||
In no event shall the authors be liable for any claim, damages or
|
||||
other liability, whether in an action of contract, tort or otherwise,
|
||||
arising from, out of or in connection with the software or the use or
|
||||
other dealings in the software.
|
||||
|
||||
So in short:
|
||||
DO NOT USE GENAI,
|
||||
DO WHATEVER YOU WANT,
|
||||
BUT IF IT BREAKS SOMETHING,
|
||||
DON’T COME CRYING — OR SUING — TO ME.
|
||||
29
README.md
Normal file
29
README.md
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
# bombai: bomba ai
|
||||
|
||||
instead of letting the ai boom bomb our websites, lets bomb the ai in return.
|
||||
|
||||
# config
|
||||
|
||||
defalt config is automatically dropped to disk and can also be found at src/bombai.toml
|
||||
|
||||
# how to
|
||||
|
||||
add to caddyfile as per the caddyfile in this repo. the iocaine part is not required.
|
||||
|
||||
```caddyfile
|
||||
@read method GET HEAD
|
||||
reverse_proxy @read 127.0.0.1:42067 {
|
||||
@fallback status 421
|
||||
handle_response @fallback
|
||||
|
||||
# optional, if using fail_response.data = http
|
||||
@iocaine status 423
|
||||
handle_response @iocaine {
|
||||
reverse_proxy 127.0.0.1:42069 # iocaine needs to be configured to always serve its poison for this.
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
# license
|
||||
|
||||
wtfpl+-ai. no ai allowed, everything else allowed.
|
||||
12
shell.nix
Normal file
12
shell.nix
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{ pkgs ? import <nixpkgs> {} }:
|
||||
pkgs.mkShell {
|
||||
nativeBuildInputs = with pkgs; [
|
||||
rust-analyzer
|
||||
cargo
|
||||
rustc
|
||||
clippy
|
||||
rustfmt
|
||||
cargo-watch
|
||||
];
|
||||
}
|
||||
|
||||
79
src/bombai.toml
Normal file
79
src/bombai.toml
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
# bomba + ai = bombai.
|
||||
#
|
||||
# sends zip bombs, infinite data, or other bullshit to AI when it requests lots of data.
|
||||
# example config for a forgejo instance.
|
||||
|
||||
# whether to group counters by address. bots may use many addresses, so this
|
||||
# may not be useful
|
||||
by_addr = false
|
||||
|
||||
[handler]
|
||||
listen_port = 42067 # this is six seven software
|
||||
pass_response = 421 # response code on pass (should be handled by reverse proxy)
|
||||
|
||||
[fail_response]
|
||||
status = 200
|
||||
status_name = "OK"
|
||||
data = "generated" # valid: "generated", "file", "http"
|
||||
|
||||
# used when data = "generated"
|
||||
[fail_response.generated]
|
||||
gzip = true # gzip the response for a zip bomb experience
|
||||
length = "1G" # length of the response in bytes, or "none" for no length header (extra evil but maybe unsupported)
|
||||
content-type = "text/html" # so AIs actually ingest it
|
||||
byte = 97 # 97 = 0x61 = 'a' - byte to fill the length with
|
||||
start_message = "bombai anti ai bomb. have fun<br>\n" # text to insert at the start
|
||||
|
||||
# used when data = "file"
|
||||
[fail_response.file]
|
||||
is_html = true # set to true to serve an html file. otherwise sends the file back as-is
|
||||
path = "failure.html"
|
||||
|
||||
# used when data = "http"
|
||||
[fail_response.http]
|
||||
response = 423 # response code to give on failure (should be handled by reverse proxy)
|
||||
continuous_failure.enable = true
|
||||
continuous_failure.path = "/failure" # path to redirect to. all sub-paths are also failure.
|
||||
|
||||
[[paths]]
|
||||
# whether requesting paths in this area is very costly for the server
|
||||
# must be true for bombai to be active here.
|
||||
# set to false to OK these paths.
|
||||
costly = false
|
||||
|
||||
paths = [
|
||||
"/*/*/archive/main.*" # archives of main branch
|
||||
"/*/*/archive/master.*" # archives of master branch
|
||||
"/*/*/archive/*.*.*" # archives of releases
|
||||
]
|
||||
|
||||
[[paths]]
|
||||
# whether requesting paths in this area is very costly for the server
|
||||
# must be true for bombai to be active here.
|
||||
# set to false to OK these paths.
|
||||
costly = true
|
||||
# whether requesting the same path multiple times is fine
|
||||
cached = true
|
||||
|
||||
counter.max = 10 # when to start returning bullshit
|
||||
counter.decay = 1 # per hour
|
||||
|
||||
paths = [
|
||||
"/*/*/archive/*" # archives of repos
|
||||
]
|
||||
|
||||
[[paths]]
|
||||
# whether requesting paths in this area is very costly for the server
|
||||
# must be true for bombai to be active here.
|
||||
# set to false to OK these paths.
|
||||
costly = true
|
||||
# whether requesting the same path multiple times is fine
|
||||
cached = false
|
||||
|
||||
counter.max = 1000 # when to start returning bullshit
|
||||
counter.decay = 100 # per hour
|
||||
|
||||
paths = [
|
||||
"/*"
|
||||
]
|
||||
|
||||
73
src/checker.rs
Normal file
73
src/checker.rs
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
use horrorhttp::Connection;
|
||||
|
||||
use crate::{CONFIG, Counter, Directive};
|
||||
|
||||
impl Counter {
|
||||
pub fn update(&mut self) {
|
||||
let hours_elapsed = self.last_decay.time_elapsed().as_millis() as f64 / 1_000. / 3600.;
|
||||
let decays_per_hour = self.decay;
|
||||
if hours_elapsed < 1. / decays_per_hour {
|
||||
return;
|
||||
}
|
||||
self.current = (self.current - hours_elapsed * decays_per_hour).max(0.);
|
||||
self.last_decay.restart();
|
||||
}
|
||||
pub fn is_bad(&mut self) -> bool {
|
||||
self.update();
|
||||
|
||||
self.current >= self.max
|
||||
}
|
||||
}
|
||||
|
||||
impl Directive {
|
||||
pub fn check_and_insert(&self, connection: &Connection) -> bool {
|
||||
let path_in = connection.path.trim_matches('/');
|
||||
let mut visited_paths = self.visited_paths.lock().unwrap();
|
||||
if self.cached && visited_paths.contains(&connection.path) {
|
||||
return false;
|
||||
}
|
||||
let path_segments_in: Vec<_> = path_in.split("/").collect();
|
||||
for path in self.paths.iter() {
|
||||
if path.matches("/").count() <= path_segments_in.len()
|
||||
&& path
|
||||
.split("/")
|
||||
.zip(path_segments_in.iter())
|
||||
.all(|(a, b)| if a == "*" { true } else { a == *b })
|
||||
{
|
||||
visited_paths.insert(path_in.to_owned());
|
||||
let mut counter = self.counter.lock().unwrap();
|
||||
counter.update();
|
||||
counter.current += 1.;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn request_is_okay(connection: &mut Connection, directives: &[Directive]) -> bool {
|
||||
if CONFIG["fail_response.data"].str() == "http"
|
||||
&& CONFIG["fail_response.http.continuous_failure.enable"].boolean()
|
||||
&& connection
|
||||
.path
|
||||
.starts_with(CONFIG["fail_response.http.continuous_failure.path"].str())
|
||||
{
|
||||
println!(" is in continuous failure");
|
||||
return false;
|
||||
}
|
||||
for (i, directive) in directives.iter().enumerate() {
|
||||
if directive.check_and_insert(connection) {
|
||||
if directive.costly {
|
||||
println!(" matched costly directive {i}");
|
||||
if directive.counter.lock().unwrap().is_bad() {
|
||||
println!(" counter is bad");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
println!(" matched non-costly directive {i} :)");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
164
src/main.rs
Normal file
164
src/main.rs
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
pub mod checker;
|
||||
pub mod responder;
|
||||
|
||||
use std::{
|
||||
collections::{BTreeMap, HashMap, HashSet},
|
||||
fs,
|
||||
mem::ManuallyDrop,
|
||||
net::TcpListener,
|
||||
ops::Deref,
|
||||
sync::{LazyLock, Mutex},
|
||||
};
|
||||
|
||||
use deborrow::deborrow;
|
||||
use horrorhttp::{BodyReaderHeader, ConnectionState, ResponseWriter};
|
||||
use microlock::timer::{Timer, TimerDuration};
|
||||
use nanoserde::{Toml, TomlParser};
|
||||
|
||||
use crate::responder::BullshitResponder;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Counter {
|
||||
pub max: f64,
|
||||
pub current: f64,
|
||||
pub decay: f64,
|
||||
pub last_decay: Timer,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Directive {
|
||||
pub costly: bool,
|
||||
pub cached: bool,
|
||||
pub paths: Vec<String>,
|
||||
pub visited_paths: CloneMutex<HashSet<String>>,
|
||||
pub counter: CloneMutex<Counter>,
|
||||
}
|
||||
|
||||
// insanity
|
||||
pub struct CloneMutex<T>(Mutex<T>);
|
||||
impl<T: Clone> Clone for CloneMutex<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self(Mutex::new(self.0.lock().unwrap().clone()))
|
||||
}
|
||||
}
|
||||
impl<T> Deref for CloneMutex<T> {
|
||||
type Target = Mutex<T>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Intercept<'a>(&'a Vec<Directive>);
|
||||
|
||||
impl<'a> ConnectionState for Intercept<'a> {
|
||||
fn handle(
|
||||
self: Box<Self>,
|
||||
connection: &mut horrorhttp::Connection,
|
||||
) -> Option<Box<dyn ConnectionState>> {
|
||||
println!(
|
||||
"Handling request from {}.",
|
||||
connection
|
||||
.headers
|
||||
.get("X-Forwarded-For")
|
||||
.cloned()
|
||||
.unwrap_or_else(|| connection.socket.peer_addr().unwrap().ip().to_string())
|
||||
);
|
||||
if checker::request_is_okay(connection, self.0) {
|
||||
println!("Request is OK.");
|
||||
Some(Box::new(ResponseWriter::new().with_status(
|
||||
CONFIG["handler.pass_response"].num() as u32,
|
||||
"Misdirected Request",
|
||||
)))
|
||||
} else {
|
||||
println!("Request is not OK. Sending you to the gallows.");
|
||||
Some(Box::new(BullshitResponder))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const DEFAULT_CONFIG: &str = include_str!("./bombai.toml");
|
||||
pub static CONFIG: LazyLock<BTreeMap<String, Toml>> = LazyLock::new(|| {
|
||||
let s = {
|
||||
match fs::read_to_string("./bombai.toml") {
|
||||
Ok(x) => x,
|
||||
Err(_) => {
|
||||
fs::write("./bombai.toml", DEFAULT_CONFIG).unwrap();
|
||||
println!("dropped default config file because no other config was found");
|
||||
DEFAULT_CONFIG.to_owned()
|
||||
}
|
||||
}
|
||||
};
|
||||
TomlParser::parse(&s).unwrap()
|
||||
});
|
||||
|
||||
fn main() {
|
||||
println!("bomba + ai = bombai. it bombs AI.");
|
||||
println!("config location: ./bombai.toml");
|
||||
println!();
|
||||
println!("this is six seven software.");
|
||||
println!();
|
||||
|
||||
let directives = CONFIG["paths"].arr();
|
||||
|
||||
let directives: ManuallyDrop<Vec<Directive>> =
|
||||
ManuallyDrop::new(transform_directives(directives));
|
||||
|
||||
let mut directives_by_addr: ManuallyDrop<
|
||||
HashMap<std::net::IpAddr, ManuallyDrop<Vec<Directive>>>,
|
||||
> = ManuallyDrop::new(HashMap::new());
|
||||
|
||||
let port = CONFIG["handler.listen_port"].num() as u16;
|
||||
let server = TcpListener::bind(("::0", port)).unwrap();
|
||||
println!("listening on [::0]:{port}");
|
||||
while let Ok((stream, addr)) = server.accept() {
|
||||
// SAFETY: safe because:
|
||||
// - all borrows are not mutable.
|
||||
// - everything is ManuallyDrop
|
||||
let local_directives = unsafe {
|
||||
deborrow(if CONFIG["by_addr"].boolean() {
|
||||
match directives_by_addr.get(&addr.ip()) {
|
||||
Some(x) => x,
|
||||
None => {
|
||||
let directives = directives.clone();
|
||||
directives_by_addr.insert(addr.ip(), directives);
|
||||
directives_by_addr.get(&addr.ip()).unwrap()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
&directives
|
||||
})
|
||||
};
|
||||
horrorhttp::handle(stream, BodyReaderHeader, Intercept(local_directives));
|
||||
}
|
||||
}
|
||||
|
||||
fn transform_directives(directives: &[BTreeMap<String, Toml>]) -> Vec<Directive> {
|
||||
directives
|
||||
.iter()
|
||||
.map(|x| {
|
||||
println!("{x:?}");
|
||||
let costly = x["costly"].boolean();
|
||||
Directive {
|
||||
costly,
|
||||
cached: if costly { x["cached"].boolean() } else { false },
|
||||
paths: x["paths"]
|
||||
.simple_arr()
|
||||
.iter()
|
||||
.map(|x| x.str().trim_matches('/').to_owned())
|
||||
.collect(),
|
||||
visited_paths: CloneMutex(Mutex::new(HashSet::new())),
|
||||
counter: CloneMutex(Mutex::new(Counter {
|
||||
max: if costly {
|
||||
x["counter.max"].num()
|
||||
} else {
|
||||
f64::INFINITY
|
||||
},
|
||||
current: 0.,
|
||||
decay: if costly { x["counter.decay"].num() } else { 0. },
|
||||
last_decay: Timer::new(TimerDuration::Elapsed),
|
||||
})),
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
29
src/responder/file.rs
Normal file
29
src/responder/file.rs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
use std::{fs, io::Write};
|
||||
|
||||
use horrorhttp::{ConnectionState, ResponseWriter};
|
||||
|
||||
use crate::CONFIG;
|
||||
|
||||
pub struct FileBullshitResponder;
|
||||
|
||||
impl ConnectionState for FileBullshitResponder {
|
||||
fn handle(
|
||||
self: Box<Self>,
|
||||
connection: &mut horrorhttp::Connection,
|
||||
) -> Option<Box<dyn ConnectionState>> {
|
||||
let data = fs::read(CONFIG["fail_response.file.path"].str()).unwrap();
|
||||
if CONFIG["fail_response.file.is_html"].boolean() {
|
||||
return Some(Box::new(
|
||||
ResponseWriter::new()
|
||||
.with_status(
|
||||
CONFIG["fail_response.status"].num() as u32,
|
||||
CONFIG["fail_response.status_name"].str(),
|
||||
)
|
||||
.with_header("Content-Type", "text/html")
|
||||
.with_body(data),
|
||||
));
|
||||
}
|
||||
let _ = connection.socket.write_all(&data);
|
||||
None
|
||||
}
|
||||
}
|
||||
84
src/responder/generated.rs
Normal file
84
src/responder/generated.rs
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
use std::io::Write;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use flate2::{Compression, write::GzEncoder};
|
||||
use horrorhttp::{Connection, ConnectionState, ResponseWriter};
|
||||
|
||||
use crate::CONFIG;
|
||||
|
||||
static BODY: LazyLock<Vec<u8>> = LazyLock::new(gen_body);
|
||||
static KILOBYTE: LazyLock<Vec<u8>> = LazyLock::new(|| {
|
||||
let mut kilobyte = Vec::new();
|
||||
for _ in 0..1024 {
|
||||
kilobyte.push(CONFIG["fail_response.generated.byte"].num() as u8);
|
||||
}
|
||||
kilobyte
|
||||
});
|
||||
|
||||
pub struct GeneratedBullshitResponder;
|
||||
|
||||
impl ConnectionState for GeneratedBullshitResponder {
|
||||
fn handle(self: Box<Self>, connection: &mut Connection) -> Option<Box<dyn ConnectionState>> {
|
||||
let mut response_writer = ResponseWriter::new()
|
||||
.with_status(
|
||||
CONFIG["fail_response.status"].num() as u32,
|
||||
CONFIG["fail_response.status_name"].str(),
|
||||
)
|
||||
.with_header(
|
||||
"Content-Type",
|
||||
CONFIG["fail_response.generated.content-type"].str(),
|
||||
);
|
||||
if CONFIG["fail_response.generated.gzip"].boolean() {
|
||||
response_writer = response_writer
|
||||
.with_header("Content-Encoding", "gzip")
|
||||
.with_body(BODY.clone());
|
||||
} else {
|
||||
let length = &CONFIG["fail_response.generated.length"];
|
||||
if length.str() != "none" {
|
||||
response_writer = response_writer
|
||||
.with_header("Content-Length", get_length(length.str()).to_string())
|
||||
}
|
||||
connection.add_next(Box::new(GeneratedBullshitSpammer));
|
||||
}
|
||||
|
||||
Some(Box::new(response_writer))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GeneratedBullshitSpammer;
|
||||
|
||||
impl ConnectionState for GeneratedBullshitSpammer {
|
||||
fn handle(self: Box<Self>, connection: &mut Connection) -> Option<Box<dyn ConnectionState>> {
|
||||
println!("i am spammer of bytes");
|
||||
while connection.socket.write_all(&KILOBYTE).is_ok() {}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn gen_body() -> Vec<u8> {
|
||||
if CONFIG["fail_response.generated.gzip"].boolean() {
|
||||
println!("i am constructor of zip bomb");
|
||||
let mut encoder = GzEncoder::new(Vec::new(), Compression::best());
|
||||
|
||||
for _ in 0..get_length(CONFIG["fail_response.generated.length"].str()) / 1024 {
|
||||
encoder.write_all(&KILOBYTE).unwrap();
|
||||
}
|
||||
println!("done");
|
||||
encoder.finish().unwrap()
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
fn get_length(s: &str) -> u64 {
|
||||
let s = s.trim_end_matches("B");
|
||||
let num: u64 = s.trim_matches(char::is_alphabetic).parse().unwrap();
|
||||
match &s[s.len() - 1..=s.len() - 1] {
|
||||
"K" => num * 1024,
|
||||
"M" => num * 1024 * 1024,
|
||||
"G" => num * 1024 * 1024 * 1024,
|
||||
"T" => num * 1024 * 1024 * 1024 * 1024,
|
||||
_ if s.chars().all(char::is_numeric) => num,
|
||||
_ => panic!("invalid length for generated response"),
|
||||
}
|
||||
}
|
||||
29
src/responder/http.rs
Normal file
29
src/responder/http.rs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
use horrorhttp::{ConnectionState, ResponseWriter};
|
||||
|
||||
use crate::CONFIG;
|
||||
|
||||
pub struct HttpBullshitResponder;
|
||||
|
||||
impl ConnectionState for HttpBullshitResponder {
|
||||
fn handle(
|
||||
self: Box<Self>,
|
||||
connection: &mut horrorhttp::Connection,
|
||||
) -> Option<Box<dyn ConnectionState>> {
|
||||
let continuous_failure_path = CONFIG["fail_response.http.continuous_failure.path"].str();
|
||||
|
||||
let response_writer = if CONFIG["fail_response.http.continuous_failure.enable"].boolean()
|
||||
&& connection.path.starts_with(continuous_failure_path)
|
||||
{
|
||||
ResponseWriter::new().with_status(302, "Found").with_header(
|
||||
"Location",
|
||||
"/".to_owned() + continuous_failure_path.trim_matches('/') + "/" + &connection.path,
|
||||
)
|
||||
} else {
|
||||
ResponseWriter::new().with_status(
|
||||
CONFIG["fail_response.http.response"].num() as u32,
|
||||
"HttpFailResponse",
|
||||
)
|
||||
};
|
||||
Some(Box::new(response_writer))
|
||||
}
|
||||
}
|
||||
29
src/responder/mod.rs
Normal file
29
src/responder/mod.rs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
pub mod file;
|
||||
pub mod generated;
|
||||
pub mod http;
|
||||
|
||||
use horrorhttp::ConnectionState;
|
||||
|
||||
use crate::{
|
||||
CONFIG,
|
||||
responder::{
|
||||
file::FileBullshitResponder, generated::GeneratedBullshitResponder,
|
||||
http::HttpBullshitResponder,
|
||||
},
|
||||
};
|
||||
|
||||
pub struct BullshitResponder;
|
||||
|
||||
impl ConnectionState for BullshitResponder {
|
||||
fn handle(
|
||||
self: Box<Self>,
|
||||
_connection: &mut horrorhttp::Connection,
|
||||
) -> Option<Box<dyn ConnectionState>> {
|
||||
match CONFIG["fail_response.data"].str() {
|
||||
"http" => Some(Box::new(HttpBullshitResponder)),
|
||||
"generated" => Some(Box::new(GeneratedBullshitResponder)),
|
||||
"file" => Some(Box::new(FileBullshitResponder)),
|
||||
_ => panic!("invalid fail_response.data. must be one of 'http', 'generated', 'file'"),
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue