#![forbid(unsafe_code)]
#![deny(
missing_copy_implementations,
rustdoc::missing_crate_level_docs,
missing_debug_implementations,
missing_docs,
nonstandard_style,
unused_qualifications
)]
use futures_lite::{AsyncRead, ready};
use lol_html::{OutputSink, send::HtmlRewriter};
use pin_project_lite::pin_project;
use std::{
collections::VecDeque,
fmt::{self, Debug},
io::{self, Read},
pin::Pin,
sync::{Arc, Mutex},
task::{Context, Poll},
};
pub use lol_html as html;
pub use lol_html::send::Settings;
#[derive(Clone)]
struct OutputBuffer(Arc<Mutex<VecDeque<u8>>>);
impl OutputSink for OutputBuffer {
fn handle_chunk(&mut self, chunk: &[u8]) {
self.0.lock().unwrap().extend(chunk);
}
}
pin_project! {
pub struct Rewriter<'h, R> {
#[pin] source: R,
rewriter: Option<HtmlRewriter<'h, OutputBuffer>>,
output: OutputBuffer,
read_buf: Vec<u8>,
}
}
impl<R> Debug for Rewriter<'_, R> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Rewriter")
.field("output_len", &self.output.0.lock().unwrap().len())
.field("done", &self.rewriter.is_none())
.finish()
}
}
pub fn rewrite<'h, R: AsyncRead>(source: R, settings: Settings<'h, '_>) -> Rewriter<'h, R> {
let output = OutputBuffer(Arc::new(Mutex::new(VecDeque::new())));
let rewriter = HtmlRewriter::new(settings, output.clone());
Rewriter {
source,
rewriter: Some(rewriter),
output,
read_buf: vec![0; 1024],
}
}
impl<R: AsyncRead> AsyncRead for Rewriter<'_, R> {
fn poll_read(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut [u8],
) -> Poll<io::Result<usize>> {
let mut this = self.project();
loop {
{
let mut output = this.output.0.lock().unwrap();
if !output.is_empty() {
let n = Read::read(&mut *output, buf)?;
return Poll::Ready(Ok(n));
}
}
let rewriter = match this.rewriter.as_mut() {
Some(r) => r,
None => return Poll::Ready(Ok(0)),
};
match ready!(this.source.as_mut().poll_read(cx, this.read_buf)) {
Ok(0) => {
this.rewriter
.take()
.unwrap()
.end()
.map_err(io::Error::other)?;
}
Ok(n) => {
rewriter
.write(&this.read_buf[..n])
.map_err(io::Error::other)?;
}
Err(e) => return Poll::Ready(Err(e)),
}
}
}
}