#![allow(clippy::needless_doctest_main, clippy::must_use_candidate)]
use std::fs::File;
use std::fs::OpenOptions;
use std::io::{self, BufRead, BufReader, BufWriter, Write};
use std::net;
use std::path::Path;
use std::process;
#[derive(Debug)]
pub struct IOConfig<'a> {
input: Option<Vec<&'a Path>>,
output: Option<&'a Path>,
}
#[derive(Debug)]
pub struct Config<'a> {
pub ipv4: &'a str,
pub ipv6: &'a str,
pub host: &'a str,
pub skip: bool,
pub flush: bool,
}
impl<'a> Default for IOConfig<'a> {
fn default() -> Self {
IOConfig {
input: None,
output: None,
}
}
}
impl<'a> Default for Config<'a> {
fn default() -> Self {
Config {
ipv4: "127.0.0.1",
ipv6: "::1",
host: "localhost",
skip: false,
flush: false,
}
}
}
impl<'a> Config<'a> {
pub fn new() -> Self {
Self::default()
}
pub fn get_ipv4_value(&self) -> &'a str {
&self.ipv4
}
pub fn get_ipv6_value(&self) -> &'a str {
&self.ipv6
}
pub fn get_host_value(&self) -> &'a str {
&self.host
}
pub fn get_skip(&self) -> bool {
self.skip
}
pub fn get_flush(&self) -> bool {
self.flush
}
pub fn set_ipv4_value(&mut self, ipv4: &'a str) {
self.ipv4 = ipv4;
}
pub fn set_ipv6_value(&mut self, ipv6: &'a str) {
self.ipv6 = ipv6;
}
pub fn set_host_value(&mut self, host: &'a str) {
self.host = host;
}
pub fn set_flush(&mut self, b: bool) {
self.flush = b;
}
pub fn set_skip(&mut self, b: bool) {
self.skip = b;
}
}
impl<'a> IOConfig<'a> {
pub fn new() -> Self {
Self::default()
}
pub fn get_input(&self) -> Option<&Vec<&'a Path>> {
self.input.as_ref()
}
pub fn get_output(&self) -> Option<&'a Path> {
self.output
}
pub fn push_input<P: AsRef<Path> + ?Sized>(&mut self, i: &'a P) {
if let Some(input) = &mut self.input {
input.push(i.as_ref());
} else {
self.input = Some(vec![]);
self.push_input(i);
}
}
pub fn set_output(&mut self, o: &'a Path) {
self.output = Some(o);
}
}
fn replace_remote_address<R: BufRead, W: Write>(
config: &Config,
mut reader: R,
mut writer: W,
) -> Result<(), std::io::Error> {
let mut buf = vec![];
'lines: loop {
buf.clear();
let bytes_read = reader.read_until(b'\n', &mut buf)?;
if let 0 = bytes_read {
break;
} else {
for (i, byte) in buf.iter().enumerate() {
if *byte == b' ' {
if String::from_utf8_lossy(&buf[..i])
.parse::<net::Ipv4Addr>()
.is_ok()
{
write!(&mut writer, "{}", config.get_ipv4_value())?;
} else if String::from_utf8_lossy(&buf[..i])
.parse::<net::Ipv6Addr>()
.is_ok()
{
write!(&mut writer, "{}", config.get_ipv6_value())?;
} else {
write!(&mut writer, "{}", config.get_host_value())?;
}
writer.write_all(&buf[i..])?;
if config.get_flush() {
writer.flush()?;
}
continue 'lines;
}
}
if !config.get_skip() {
writer.write_all(&buf)?;
if config.get_flush() {
writer.flush()?;
}
}
};
}
writer.flush()?;
Ok(())
}
pub fn run(config: &Config, ioconfig: &IOConfig) {
let mut writer: Box<dyn Write> = match ioconfig.get_output() {
Some(output) => {
let f = OpenOptions::new()
.write(true)
.create_new(true)
.open(Path::new(output));
let f = match f {
Ok(file) => file,
Err(e) => {
eprintln!("Error writing to file {}: {}.", output.display(), e);
std::process::exit(1);
}
};
Box::new(BufWriter::new(f)) as _
}
None => Box::new(BufWriter::new(io::stdout())),
};
if let Some(input) = ioconfig.get_input() {
for arg in input {
let f = File::open(Path::new(arg));
let f = match f {
Ok(file) => file,
Err(e) => {
eprintln!("Error reading file '{}': {}.", arg.display(), e);
if let Some(output) = ioconfig.get_output() {
std::fs::remove_file(Path::new(output)).unwrap();
}
process::exit(1);
}
};
let reader: Box<dyn BufRead> = Box::new(BufReader::new(f));
if let Err(e) = replace_remote_address(config, reader, &mut writer) {
eprintln!("Error: {}", e);
if let Some(output) = ioconfig.get_output() {
std::fs::remove_file(Path::new(output)).unwrap();
}
process::exit(1);
}
}
} else {
let stdin = io::stdin();
let reader: Box<dyn BufRead> = Box::new(stdin.lock());
if let Err(e) = replace_remote_address(config, reader, &mut writer) {
eprintln!("Error: {}", e);
if let Some(output) = ioconfig.get_output() {
std::fs::remove_file(Path::new(output)).unwrap();
}
process::exit(1);
}
}
}
pub fn run_raw<R: BufRead, W: Write>(config: &Config, reader: R, mut writer: W) {
if let Err(e) = replace_remote_address(config, reader, &mut writer) {
eprintln!("Error: {}", e);
process::exit(1);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn run_raw_function() {
use std::io::Cursor;
let line = Cursor::new(b"8.8.8.8 XxX");
let mut buffer = vec![];
run_raw(&Config::default(), line, &mut buffer);
assert_eq!(buffer, b"127.0.0.1 XxX");
}
#[test]
fn replace_ipv4() {
use std::io::Cursor;
let mut buffer = Cursor::new(vec![]);
let log = Box::new("8.8.8.8 - frank [10/Oct/2000:13:55:36 -0700] \"GET /apache_pb.gif HTTP/1.0\" 200 2326 \"http://www.example.com/start.html\" \"Mozilla/4.08 [en] (Win98; I ;Nav)\"".as_bytes());
let local_log = "127.0.0.1 - frank [10/Oct/2000:13:55:36 -0700] \"GET /apache_pb.gif HTTP/1.0\" 200 2326 \"http://www.example.com/start.html\" \"Mozilla/4.08 [en] (Win98; I ;Nav)\"".as_bytes();
replace_remote_address(&Config::default(), log, &mut buffer).unwrap();
assert_eq!(&buffer.into_inner(), &local_log);
}
#[test]
fn replace_ipv6() {
use std::io::Cursor;
let mut buffer = Cursor::new(vec![]);
let log = Box::new("2a00:1450:4001:81b::2004 - frank [10/Oct/2000:13:55:36 -0700] \"GET /apache_pb.gif HTTP/1.0\" 200 2326 \"http://www.example.com/start.html\" \"Mozilla/4.08 [en] (Win98; I ;Nav)\"".as_bytes());
let local_log = "::1 - frank [10/Oct/2000:13:55:36 -0700] \"GET /apache_pb.gif HTTP/1.0\" 200 2326 \"http://www.example.com/start.html\" \"Mozilla/4.08 [en] (Win98; I ;Nav)\"".as_bytes();
replace_remote_address(&Config::default(), log, &mut buffer).unwrap();
assert_eq!(&buffer.into_inner(), &local_log);
}
#[test]
fn replace_host() {
use std::io::Cursor;
let mut buffer = Cursor::new(vec![]);
let log = Box::new("google.com - frank [10/Oct/2000:13:55:36 -0700] \"GET /apache_pb.gif HTTP/1.0\" 200 2326 \"http://www.example.com/start.html\" \"Mozilla/4.08 [en] (Win98; I ;Nav)\"".as_bytes());
let local_log = "localhost - frank [10/Oct/2000:13:55:36 -0700] \"GET /apache_pb.gif HTTP/1.0\" 200 2326 \"http://www.example.com/start.html\" \"Mozilla/4.08 [en] (Win98; I ;Nav)\"".as_bytes();
replace_remote_address(&Config::default(), log, &mut buffer).unwrap();
assert_eq!(&buffer.into_inner(), &local_log);
}
#[test]
fn replace_custom_ipv4() {
use std::io::Cursor;
let mut buffer = Cursor::new(vec![]);
let log = Box::new("8.8.8.8 - frank [10/Oct/2000:13:55:36 -0700] \"GET /apache_pb.gif HTTP/1.0\" 200 2326 \"http://www.example.com/start.html\" \"Mozilla/4.08 [en] (Win98; I ;Nav)\"".as_bytes());
let local_log = "custom_ipv4 - frank [10/Oct/2000:13:55:36 -0700] \"GET /apache_pb.gif HTTP/1.0\" 200 2326 \"http://www.example.com/start.html\" \"Mozilla/4.08 [en] (Win98; I ;Nav)\"".as_bytes();
let mut conf = Config::default();
conf.set_ipv4_value("custom_ipv4");
replace_remote_address(&conf, log, &mut buffer).unwrap();
assert_eq!(&buffer.into_inner(), &local_log);
}
#[test]
fn replace_custom_ipv6() {
use std::io::Cursor;
let mut buffer = Cursor::new(vec![]);
let log = Box::new("2a00:1450:4001:81b::2004 - frank [10/Oct/2000:13:55:36 -0700] \"GET /apache_pb.gif HTTP/1.0\" 200 2326 \"http://www.example.com/start.html\" \"Mozilla/4.08 [en] (Win98; I ;Nav)\"".as_bytes());
let local_log = "custom_ipv6 - frank [10/Oct/2000:13:55:36 -0700] \"GET /apache_pb.gif HTTP/1.0\" 200 2326 \"http://www.example.com/start.html\" \"Mozilla/4.08 [en] (Win98; I ;Nav)\"".as_bytes();
let mut conf = Config::default();
conf.set_ipv6_value("custom_ipv6");
replace_remote_address(&conf, log, &mut buffer).unwrap();
assert_eq!(&buffer.into_inner(), &local_log);
}
#[test]
fn replace_custom_host() {
use std::io::Cursor;
let mut buffer = Cursor::new(vec![]);
let log = Box::new("google.com - frank [10/Oct/2000:13:55:36 -0700] \"GET /apache_pb.gif HTTP/1.0\" 200 2326 \"http://www.example.com/start.html\" \"Mozilla/4.08 [en] (Win98; I ;Nav)\"".as_bytes());
let local_log = "custom_host - frank [10/Oct/2000:13:55:36 -0700] \"GET /apache_pb.gif HTTP/1.0\" 200 2326 \"http://www.example.com/start.html\" \"Mozilla/4.08 [en] (Win98; I ;Nav)\"".as_bytes();
let mut conf = Config::default();
conf.set_host_value("custom_host");
replace_remote_address(&conf, log, &mut buffer).unwrap();
assert_eq!(&buffer.into_inner(), &local_log);
}
}