#![allow(clippy::needless_doctest_main, clippy::must_use_candidate)]
use std::cmp::Ordering;
use std::fs::{File, OpenOptions};
use std::io::{self, BufRead, BufReader, BufWriter, Write};
use std::path::Path;
use std::{fmt, net};
use regex::bytes::Regex;
#[macro_use(lazy_static)]
extern crate lazy_static;
lazy_static! {
static ref RE: Regex = Regex::new(" \\[[0-9]{1,2}/").unwrap();
}
#[derive(Debug)]
pub struct IOError {
message: String,
}
impl fmt::Display for IOError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.message)
}
}
impl From<io::Error> for IOError {
fn from(error: io::Error) -> Self {
IOError {
message: error.to_string(),
}
}
}
#[derive(Debug)]
pub struct IOConfig<'a> {
input: Option<Vec<&'a Path>>,
output: Option<&'a Path>,
}
#[allow(clippy::struct_excessive_bools)]
#[derive(Debug)]
pub struct Config<'a> {
pub ipv4: &'a str,
pub ipv6: &'a str,
pub host: &'a str,
pub skip: bool,
pub authuser: bool,
pub trim: bool,
pub optimize: 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,
authuser: false,
trim: true,
optimize: true,
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_authuser(&self) -> bool {
self.authuser
}
pub fn get_trim(&self) -> bool {
self.trim
}
pub fn get_optimize(&self) -> bool {
self.optimize
}
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_authuser(&mut self, b: bool) {
self.authuser = b;
}
pub fn set_trim(&mut self, b: bool) {
self.trim = b;
}
pub fn set_optimize(&mut self, b: bool) {
self.optimize = 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<(), io::Error> {
let mut buf = vec![];
'lines: loop {
buf.clear();
let bytes_read = reader.read_until(b'\n', &mut buf)?;
if bytes_read == 0 {
break;
}
if config.get_trim() {
let s = match buf.iter().position(|&x| !x.is_ascii_whitespace()) {
Some(s) => s,
_ => 0,
};
buf.drain(..s);
}
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())?;
}
if config.get_authuser() {
match &buf[i + 3..i + 6].iter().cmp(b"- [") {
Ordering::Equal if config.get_optimize() => {
writer.write_all(&buf[i..])?;
}
_ => {
if let Some(time_field) = RE.find_at(&buf, i) {
write!(&mut writer, " - -")?;
writer.write_all(&buf[time_field.start()..])?;
} else {
writer.write_all(&buf[i..])?;
}
}
}
} else {
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) -> Result<(), IOError> {
let stdout = io::stdout();
let mut writer: Box<dyn Write> = match ioconfig.get_output() {
Some(output) => {
let f = match OpenOptions::new()
.create(true)
.append(true)
.open(Path::new(output))
{
Ok(f) => f,
Err(e) => {
return Err(IOError {
message: format!("Can not open output '{}': {e}", output.display()),
})
}
};
Box::new(BufWriter::new(f)) as _
}
None => Box::new(BufWriter::new(stdout.lock())),
};
if let Some(input) = ioconfig.get_input() {
for arg in input {
match File::open(Path::new(arg)) {
Err(e) => {
return Err(IOError {
message: format!("Can not open input '{}': {e}", arg.display()),
})
}
Ok(f) => {
let reader: Box<dyn BufRead> = Box::new(BufReader::new(f));
if let Err(e) = replace_remote_address(config, reader, &mut writer) {
return Err(IOError {
message: e.to_string(),
});
}
}
}
}
} 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) {
return Err(IOError {
message: e.to_string(),
});
}
}
Ok(())
}
pub fn run_raw<R: BufRead, W: Write>(
config: &Config,
reader: R,
mut writer: W,
) -> Result<(), IOError> {
replace_remote_address(config, reader, &mut writer)?;
Ok(())
}
#[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).unwrap();
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 clear_authuser() {
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 - - [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_authuser(true);
replace_remote_address(&conf, 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);
}
#[test]
fn notrim_and_auth() {
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 = "localhost - - [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_trim(false);
conf.set_authuser(true);
replace_remote_address(&conf, log, &mut buffer).unwrap();
assert_eq!(&buffer.into_inner(), &local_log);
}
}