rustbust 0.1.0

Fast & robust URL fuzzer that uses parallel requests
Documentation
use reqwest::Client;
use std::{
    error::Error,
    io::{stdout, BufRead, BufWriter, Lines, Write},
    sync::{mpsc::channel, Arc},
    thread::{sleep, spawn},
    time::Duration,
};

pub struct Response {
    pub name: String,
    pub status: u16,
}

pub fn request_n<T, U>(
    lines: &mut Lines<T>,
    n: usize,
    url: &str,
    outfile: &mut BufWriter<U>,
) -> Result<bool, Box<Error>>
where
    T: BufRead,
    U: Write,
{
    let cli = Arc::new(Client::new());
    let (tx, rx) = channel();
    let lines = lines.take(n).collect::<Result<Vec<String>, _>>()?;
    let len = lines.len();

    print!("{}[2K", 27 as char);
    print!("fuzzing {}..{}\r", &lines[0], &lines[len - 1]);
    stdout().flush()?;

    for line in lines {
        let fuzz_url = format!("{}/{}", url, &line);
        let cli = Arc::clone(&cli);
        let tx = tx.clone();

        spawn(move || {
            let mut counter = 0;

            loop {
                match cli.get(&fuzz_url).send() {
                    Ok(res) => {
                        let s = res.status().as_u16();

                        if s != 404 {
                            println!("\n{} returned {}", &line, s);

                            tx.send(Some(Response {
                                status: s,
                                name: line,
                            }))
                            .unwrap();
                        } else {
                            tx.send(None).unwrap();
                        }

                        break;
                    }

                    Err(e) => {
                        counter += 1;

                        println!(
                            "\nerror encountered while trying to fuzz {}: {}\nretrying in 1s",
                            &line, e
                        );

                        if counter > 5 {
                            println!(
                                "\ntoo many errors encountered trying to fuzz {}, skipped",
                                line
                            );

                            tx.send(Some(Response {
                                name: line,
                                status: 500,
                            }))
                            .unwrap();

                            break;
                        }

                        sleep(Duration::from_secs(1));
                    }
                }
            }
        });
    }

    for _ in 0..len {
        if let Some(res) = rx.recv()? {
            outfile.write(&format!("{}\t{}\n", res.name, res.status).into_bytes())?;
        }
    }

    outfile.flush()?;

    Ok(n == len)
}