blob: b62aa73ec81613aff787ea988458a46ac26b2b1a [file] [log] [blame]
// Copyright 2020 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use std::collections::HashMap;
use std::fmt;
use std::io::{self, BufRead, BufReader, BufWriter, Cursor, Read, Write};
use std::num::ParseIntError;
use std::str::FromStr;
use sys_util::{debug, error};
use tiny_http::{Header, Method};
use crate::io_adapters::{ChunkedWriter, CompleteReader, LoggingReader};
use crate::usb_connector::UsbConnection;
use crate::util::read_until_delimiter;
// Minimum Request body size, in bytes, before we switch to forwarding requests
// using a chunked Transfer-Encoding.
const CHUNKED_THRESHOLD: usize = 1 << 15;
const VERSION: Option<&'static str> = option_env!("CARGO_PKG_VERSION");
#[derive(Debug)]
pub enum Error {
DuplicateBodyReader,
EmptyField(String),
ForwardRequestBody(io::Error),
MalformedRequest,
MalformedContentLength(String, ParseIntError),
ParseResponse(httparse::Error),
ReadResponseHeader(io::Error),
WriteRequestHeader(io::Error),
WriteResponse(io::Error),
}
impl std::error::Error for Error {}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use Error::*;
match self {
DuplicateBodyReader => write!(f, "Attempted to call body_reader() multiple times."),
EmptyField(field) => write!(f, "HTTP Response field {} was unexpectedly empty", field),
ForwardRequestBody(err) => write!(f, "Forwarding request body failed: {}", err),
MalformedRequest => write!(f, "HTTP request is malformed"),
MalformedContentLength(value, err) => write!(
f,
"Failed to parse response Content-Length '{}': {}",
value, err
),
ParseResponse(err) => write!(f, "Failed to parse HTTP Response header: {}", err),
ReadResponseHeader(err) => write!(f, "Reading response header failed: {}", err),
WriteRequestHeader(err) => write!(f, "Writing request header failed: {}", err),
WriteResponse(err) => write!(f, "Responding to request failed: {}", err),
}
}
}
type Result<T> = std::result::Result<T, Error>;
#[derive(Copy, Clone, PartialEq)]
enum BodyLength {
Chunked,
Exactly(usize),
}
struct ResponseReader<R: BufRead + Sized> {
verbose_log: bool,
reader: R,
body_length: BodyLength,
header_was_read: bool,
created_body_reader: bool,
}
impl<R> ResponseReader<R>
where
R: BufRead + Sized,
{
fn new(verbose_log: bool, reader: R) -> ResponseReader<R> {
ResponseReader {
verbose_log,
reader,
// Assume body is empty unless we see a header to the contrary.
body_length: BodyLength::Exactly(0),
header_was_read: false,
created_body_reader: false,
}
}
fn read_header(&mut self) -> Result<(tiny_http::StatusCode, Vec<Header>)> {
self.header_was_read = true;
let buf = read_until_delimiter(&mut self.reader, b"\r\n\r\n")
.map_err(Error::ReadResponseHeader)?;
let mut headers = [httparse::EMPTY_HEADER; 32];
let mut response = httparse::Response::new(&mut headers);
let (status, headers) = match response.parse(&buf).map_err(Error::ParseResponse)? {
httparse::Status::Complete(i) if i == buf.len() => {
let code = response
.code
.ok_or_else(|| Error::EmptyField("code".to_owned()))?;
let status = tiny_http::StatusCode::from(code);
let version = response
.version
.ok_or_else(|| Error::EmptyField("version".to_owned()))?;
debug!(
"> HTTP/1.{} {} {}",
version,
code,
status.default_reason_phrase()
);
let mut parsed_headers = Vec::new();
for header in headers.iter().take_while(|&&h| h != httparse::EMPTY_HEADER) {
if let Ok(h) = Header::from_bytes(header.name, header.value) {
if self.verbose_log {
debug!(" {}: {}", h.field, h.value);
}
parsed_headers.push(h);
} else {
error!(
"Ignoring malformed header {}:{:#?}",
header.name, header.value
);
}
}
(status, parsed_headers)
}
_ => return Err(Error::MalformedRequest),
};
// Determine the size of the body content.
for header in headers.iter() {
if header.field.equiv("Content-Length") {
let length = usize::from_str(header.value.as_str()).map_err(|e| {
Error::MalformedContentLength(header.value.as_str().to_string(), e)
})?;
self.body_length = BodyLength::Exactly(length);
break;
}
if header.field.equiv("Transfer-Encoding") {
self.body_length = BodyLength::Chunked;
break;
}
}
Ok((status, headers))
}
fn body_reader<'r>(&'r mut self) -> Result<Box<dyn Read + 'r>> {
if self.created_body_reader {
return Err(Error::DuplicateBodyReader);
}
self.created_body_reader = true;
match self.body_length {
BodyLength::Exactly(length) => {
let reader = (&mut self.reader).take(length as u64);
Ok(Box::new(CompleteReader::new(reader)))
}
BodyLength::Chunked => {
let reader = chunked_transfer::Decoder::new(&mut self.reader);
Ok(Box::new(CompleteReader::new(reader)))
}
}
}
}
impl<R> Drop for ResponseReader<R>
where
R: BufRead,
{
fn drop(&mut self) {
if !self.created_body_reader {
debug!("Draining in drop");
if !self.header_was_read {
// Read header to figure out how long the body is.
let _ = self.read_header();
}
// Create a body reader which will totally read the response on drop.
let _ = self.body_reader();
}
}
}
fn is_end_to_end(header: &Header) -> bool {
match header.field.as_str().as_str() {
"Connection"
| "Expect" // Technically end-to-end, but we want to filter it.
| "Keep-Alive"
| "Proxy-Authenticate"
| "Proxy-Authorization"
| "TE"
| "Trailers"
| "Transfer-Encoding"
| "Upgrade" => false,
_ => true,
}
}
fn supports_request_body(method: &Method) -> bool {
match method {
Method::Get | Method::Head | Method::Delete | Method::Options | Method::Trace => false,
_ => true,
}
}
#[derive(Eq)]
struct HeaderField {
value: String,
}
impl HeaderField {
fn new(value: &str) -> Self {
Self {
value: value.to_string(),
}
}
}
impl std::cmp::PartialEq for HeaderField {
fn eq(&self, other: &Self) -> bool {
self.value.eq_ignore_ascii_case(&other.value)
}
}
impl std::hash::Hash for HeaderField {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.value.to_ascii_lowercase().hash(state);
}
}
impl fmt::Display for HeaderField {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.value)
}
}
struct Headers {
values: HashMap<HeaderField, Vec<String>>,
}
impl Headers {
pub fn new() -> Self {
Headers {
values: HashMap::new(),
}
}
pub fn delete_header(&mut self, k: &str) -> Option<Vec<String>> {
self.values.remove(&HeaderField::new(k))
}
pub fn add_header(&mut self, k: &str, v: &str) {
self.values
.entry(HeaderField::new(k))
.or_default()
.push(v.to_string());
}
pub fn has_header(&self, k: &str) -> bool {
self.values.contains_key(&HeaderField::new(k))
}
}
struct Request {
method: String,
url: String,
headers: Headers,
body_length: BodyLength,
forwarded_body_length: BodyLength,
}
// Converts a tiny_http::Request into our internal Request format.
// Filter out Hop-by-hop headers and add Content-Length or Transfer-Encoding
// headers as needed.
fn rewrite_request(request: &tiny_http::Request) -> Request {
let mut headers = Headers::new();
// If the incoming request specifies a Transfer-Encoding, it must be chunked.
let request_is_chunked = request
.headers()
.iter()
.any(|h| h.field.equiv("Transfer-Encoding"));
for header in request.headers().iter().filter(|&h| is_end_to_end(h)) {
// Call as_str() twice for conversion from header name to &AsciiStr to &str.
headers.add_header(header.field.as_str().as_str(), header.value.as_str());
}
let body_length = if !supports_request_body(request.method()) {
BodyLength::Exactly(0)
} else if request_is_chunked {
BodyLength::Chunked
} else if let Some(length) = request.body_length() {
BodyLength::Exactly(length)
} else {
BodyLength::Exactly(0)
};
headers.delete_header("User-Agent");
let user_agent = format!("ippusb_bridge/{}", VERSION.unwrap_or("unknown"));
headers.add_header("User-Agent", &user_agent);
// If the request body is relatively small, don't use a chunked encoding for
// the proxied request.
let forwarded_body_length = match body_length {
BodyLength::Exactly(length) if length < CHUNKED_THRESHOLD => body_length,
_ => BodyLength::Chunked,
};
if forwarded_body_length == BodyLength::Chunked {
// Content-Length and chunked encoding are mutually exclusive.
// We don't need to delete any existing Transfer-Encoding since it's a
// Hop-by-hop header and is already filtered out above.
headers.delete_header("Content-Length");
headers.add_header("Transfer-Encoding", "chunked");
} else if !headers.has_header("Content-Length") {
headers.add_header("Content-Length", "0");
}
Request {
method: request.method().to_string(),
url: request.url().to_string(),
headers,
body_length,
forwarded_body_length,
}
}
fn serialize_request_header(
verbose_log: bool,
request: &Request,
writer: &mut dyn Write,
) -> io::Result<()> {
write!(writer, "{} {} HTTP/1.1\r\n", request.method, request.url)?;
if verbose_log {
debug!("{} {} HTTP/1.1\\r\n", request.method, request.url);
}
for (field, values) in request.headers.values.iter() {
for value in values.iter() {
write!(writer, "{}: {}\r\n", field, value)?;
if verbose_log {
debug!(" {}: {}\\r", field, value);
}
}
}
write!(writer, "\r\n")?;
if verbose_log {
debug!("\\r");
}
writer.flush()
}
pub fn handle_request(
verbose_log: bool,
usb: UsbConnection,
mut request: tiny_http::Request,
) -> Result<()> {
debug!(
"< {} {} HTTP/1.{}",
request.method(),
request.url(),
request.http_version().1
);
// Filter out headers that should not be forwarded, and update Content-Length and
// Transfer-Encoding headers based on how the body (if any) will be transferred.
let new_request = rewrite_request(&request);
let mut logging_reader = LoggingReader::new(request.as_reader(), "client");
let mut request_body: Box<dyn Read> = match new_request.forwarded_body_length {
BodyLength::Exactly(length) => {
// If we're not using chunked, we must have the entire request body before beginning to
// forward the request. If we didn't and the client were to drop in the middle of
// forwarding a request, we would have no way of cleanly terminating the connection.
let mut buf = Vec::with_capacity(length);
io::copy(&mut logging_reader, &mut buf).map_err(Error::ForwardRequestBody)?;
Box::new(Cursor::new(buf))
}
_ => Box::new(logging_reader),
};
let mut usb_writer = BufWriter::new(&usb);
// Write the modified request header to the printer.
serialize_request_header(verbose_log, &new_request, &mut usb_writer)
.map_err(Error::WriteRequestHeader)?;
// Now that we have written data to the printer, we must ensure that we read
// a complete HTTP response from the printer. Otherwise, that data may
// remain in the printer's buffers and be sent to some other client.
// ResponseReader ensures that this happens internally.
let usb_reader = BufReader::new(LoggingReader::new(&usb, "printer"));
let mut response_reader = ResponseReader::new(verbose_log, usb_reader);
if new_request.body_length != BodyLength::Exactly(0) {
debug!("* Forwarding client request body");
let mut writer: Box<dyn Write> = match new_request.forwarded_body_length {
BodyLength::Chunked => Box::new(ChunkedWriter::new(usb_writer)),
_ => Box::new(usb_writer),
};
io::copy(&mut request_body, &mut writer).map_err(Error::ForwardRequestBody)?;
writer.flush().map_err(Error::ForwardRequestBody)?;
}
drop(request_body);
debug!("* Reading printer response header");
let (status, headers) = response_reader.read_header()?;
debug!("* Forwarding printer response body");
let body_reader = response_reader.body_reader()?;
let response = tiny_http::Response::new(status, headers, body_reader, None, None);
request.respond(response).map_err(Error::WriteResponse)?;
debug!("* Finished processing request");
Ok(())
}