use std::io::prelude::*;
use super::error::HttpError;
use super::mimetype;
use super::response::Response;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum ContentEncoding {
Brotli,
Gzip,
Deflate,
Identity,
}
impl ContentEncoding {
pub fn parse(s: &str) -> Result<ContentEncoding, HttpError> {
match s {
"br" => Ok(ContentEncoding::Brotli),
"gzip" => Ok(ContentEncoding::Gzip),
"deflate" => Ok(ContentEncoding::Deflate),
"identity" => Ok(ContentEncoding::Identity),
v => Err(HttpError::UnsupportedContentEncoding {
description: v.to_string(),
}),
}
}
pub fn decode(&self, data: &[u8]) -> Result<Vec<u8>, HttpError> {
match self {
ContentEncoding::Identity => Ok(data.to_vec()),
ContentEncoding::Gzip => uncompress_gzip(data),
ContentEncoding::Deflate => uncompress_zlib(data),
ContentEncoding::Brotli => uncompress_brotli(data),
}
}
}
impl Response {
pub fn text(&self) -> Result<String, HttpError> {
let content_encodings = self.headers.content_encoding()?;
let body = if content_encodings.is_empty() {
&self.body
} else {
&self.uncompress_body()?
};
let character_encoding = self.headers.character_encoding()?;
match character_encoding.decode_without_bom_handling_and_without_replacement(body) {
Some(s) => Ok(s.to_string()),
None => Err(HttpError::InvalidDecoding {
charset: character_encoding.name().to_string(),
}),
}
}
pub fn is_html(&self) -> bool {
self.headers.content_type().is_some_and(mimetype::is_html)
}
pub fn is_json(&self) -> bool {
self.headers.content_type().is_some_and(mimetype::is_json)
}
pub fn is_xml(&self) -> bool {
self.headers.content_type().is_some_and(mimetype::is_xml)
}
pub fn uncompress_body(&self) -> Result<Vec<u8>, HttpError> {
let encodings = self.headers.content_encoding()?;
let mut data = self.body.clone();
for encoding in &encodings {
data = encoding.decode(&data)?;
}
Ok(data)
}
}
fn uncompress_brotli(data: &[u8]) -> Result<Vec<u8>, HttpError> {
let buffer_size = 4096;
let mut reader = brotli::Decompressor::new(data, buffer_size);
let mut buf = Vec::new();
match reader.read_to_end(&mut buf) {
Ok(_) => Ok(buf),
Err(_) => Err(HttpError::CouldNotUncompressResponse {
description: "brotli".to_string(),
}),
}
}
fn uncompress_gzip(data: &[u8]) -> Result<Vec<u8>, HttpError> {
let mut decoder = match libflate::gzip::Decoder::new(data) {
Ok(v) => v,
Err(_) => {
return Err(HttpError::CouldNotUncompressResponse {
description: "gzip".to_string(),
});
}
};
let mut buf = Vec::new();
match decoder.read_to_end(&mut buf) {
Ok(_) => Ok(buf),
Err(_) => Err(HttpError::CouldNotUncompressResponse {
description: "gzip".to_string(),
}),
}
}
fn uncompress_zlib(data: &[u8]) -> Result<Vec<u8>, HttpError> {
let mut decoder = match libflate::zlib::Decoder::new(data) {
Ok(v) => v,
Err(_) => {
return Err(HttpError::CouldNotUncompressResponse {
description: "zlib".to_string(),
});
}
};
let mut buf = Vec::new();
match decoder.read_to_end(&mut buf) {
Ok(_) => Ok(buf),
Err(_) => Err(HttpError::CouldNotUncompressResponse {
description: "zlib".to_string(),
}),
}
}
#[cfg(test)]
pub mod tests {
use super::*;
use crate::http::{Header, HeaderVec, HttpVersion, Response};
fn default_response() -> Response {
Response {
version: HttpVersion::Http10,
status: 200,
headers: HeaderVec::new(),
body: vec![],
duration: Default::default(),
url: "http://localhost".parse().unwrap(),
certificate: None,
ip_addr: Default::default(),
}
}
#[test]
fn test_parse_content_encoding() {
assert_eq!(
ContentEncoding::parse("br").unwrap(),
ContentEncoding::Brotli
);
assert_eq!(
ContentEncoding::parse("xx").err().unwrap(),
HttpError::UnsupportedContentEncoding {
description: "xx".to_string()
}
);
}
#[test]
fn test_content_encoding() {
let response = default_response();
assert_eq!(response.headers.content_encoding().unwrap(), vec![]);
let mut headers = HeaderVec::new();
headers.push(Header::new("Content-Encoding", "xx"));
let response = Response {
headers,
..default_response()
};
assert_eq!(
response.headers.content_encoding().err().unwrap(),
HttpError::UnsupportedContentEncoding {
description: "xx".to_string()
}
);
let mut headers = HeaderVec::new();
headers.push(Header::new("Content-Encoding", "br"));
let response = Response {
headers,
..default_response()
};
assert_eq!(
response.headers.content_encoding().unwrap(),
vec![ContentEncoding::Brotli]
);
}
#[test]
fn test_multiple_content_encoding() {
let mut headers = HeaderVec::new();
headers.push(Header::new("Content-Encoding", "br, identity"));
let response = Response {
headers,
..default_response()
};
assert_eq!(
response.headers.content_encoding().unwrap(),
vec![ContentEncoding::Brotli, ContentEncoding::Identity]
);
}
#[test]
fn test_uncompress_body() {
let mut headers = HeaderVec::new();
headers.push(Header::new("Content-Encoding", "br"));
let response = Response {
headers,
body: vec![
0x21, 0x2c, 0x00, 0x04, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72, 0x6c,
0x64, 0x21, 0x03,
],
..default_response()
};
assert_eq!(response.uncompress_body().unwrap(), b"Hello World!");
let mut headers = HeaderVec::new();
headers.push(Header::new("Content-Encoding", "br, identity"));
let response = Response {
headers,
body: vec![
0x21, 0x2c, 0x00, 0x04, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72, 0x6c,
0x64, 0x21, 0x03,
],
..default_response()
};
assert_eq!(response.uncompress_body().unwrap(), b"Hello World!");
let response = Response {
body: b"Hello World!".to_vec(),
..default_response()
};
assert_eq!(response.uncompress_body().unwrap(), b"Hello World!");
}
#[test]
fn test_uncompress_brotli() {
let data = [
0x21, 0x2c, 0x00, 0x04, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72, 0x6c,
0x64, 0x21, 0x03,
];
assert_eq!(uncompress_brotli(&data[..]).unwrap(), b"Hello World!");
}
#[test]
fn test_uncompress_gzip() {
let data = [
0x1f, 0x8b, 0x08, 0x08, 0xa7, 0x52, 0x85, 0x5f, 0x00, 0x03, 0x64, 0x61, 0x74, 0x61,
0x2e, 0x74, 0x78, 0x74, 0x00, 0xf3, 0x48, 0xcd, 0xc9, 0xc9, 0x57, 0x08, 0xcf, 0x2f,
0xca, 0x49, 0x51, 0x04, 0x00, 0xa3, 0x1c, 0x29, 0x1c, 0x0c, 0x00, 0x00, 0x00,
];
assert_eq!(uncompress_gzip(&data[..]).unwrap(), b"Hello World!");
}
#[test]
fn test_uncompress_zlib() {
let data = [
0x78, 0x9c, 0xf3, 0x48, 0xcd, 0xc9, 0xc9, 0x57, 0x08, 0xcf, 0x2f, 0xca, 0x49, 0x51,
0x04, 0x00, 0x1c, 0x49, 0x04, 0x3e,
];
assert_eq!(uncompress_zlib(&data[..]).unwrap(), b"Hello World!");
}
#[test]
fn test_uncompress_error() {
let data = [0x21];
assert_eq!(
uncompress_brotli(&data[..]).err().unwrap(),
HttpError::CouldNotUncompressResponse {
description: "brotli".to_string()
}
);
assert_eq!(
uncompress_gzip(&data[..]).err().unwrap(),
HttpError::CouldNotUncompressResponse {
description: "gzip".to_string()
}
);
}
fn hello_response() -> Response {
Response {
body: b"Hello World!".to_vec(),
..default_response()
}
}
fn utf8_encoding_response() -> Response {
let mut headers = HeaderVec::new();
headers.push(Header::new("Content-Type", "text/plain; charset=utf-8"));
Response {
headers,
body: vec![0x63, 0x61, 0x66, 0xc3, 0xa9],
..default_response()
}
}
fn latin1_encoding_response() -> Response {
let mut headers = HeaderVec::new();
headers.push(Header::new(
"Content-Type",
"text/plain; charset=ISO-8859-1",
));
Response {
headers,
body: vec![0x63, 0x61, 0x66, 0xe9],
..default_response()
}
}
#[test]
pub fn test_content_type() {
assert_eq!(hello_response().headers.content_type(), None);
assert_eq!(
utf8_encoding_response().headers.content_type(),
Some("text/plain; charset=utf-8")
);
assert_eq!(
latin1_encoding_response().headers.content_type(),
Some("text/plain; charset=ISO-8859-1")
);
}
#[test]
pub fn test_character_encoding() {
assert_eq!(
hello_response().headers.character_encoding().unwrap(),
encoding_rs::UTF_8
);
assert_eq!(
utf8_encoding_response()
.headers
.character_encoding()
.unwrap(),
encoding_rs::UTF_8
);
assert_eq!(
latin1_encoding_response()
.headers
.character_encoding()
.unwrap(),
encoding_rs::WINDOWS_1252
);
}
#[test]
pub fn test_text() {
assert_eq!(hello_response().text().unwrap(), "Hello World!".to_string());
assert_eq!(utf8_encoding_response().text().unwrap(), "café".to_string());
assert_eq!(
latin1_encoding_response().text().unwrap(),
"café".to_string()
);
}
#[test]
pub fn test_invalid_charset() {
let mut headers = HeaderVec::new();
headers.push(Header::new("Content-Type", "test/plain; charset=xxx"));
assert_eq!(
Response {
headers,
body: b"Hello World!".to_vec(),
..default_response()
}
.headers
.character_encoding()
.err()
.unwrap(),
HttpError::InvalidCharset {
charset: "xxx".to_string()
}
);
}
#[test]
pub fn test_invalid_decoding() {
assert_eq!(
Response {
body: vec![0x63, 0x61, 0x66, 0xe9],
..default_response()
}
.text()
.err()
.unwrap(),
HttpError::InvalidDecoding {
charset: "UTF-8".to_string()
}
);
let mut headers = HeaderVec::new();
headers.push(Header::new(
"Content-Type",
"text/plain; charset=ISO-8859-1",
));
assert_eq!(
Response {
headers,
body: vec![0x63, 0x61, 0x66, 0xc3, 0xa9],
..default_response()
}
.text()
.unwrap(),
"café".to_string()
);
}
}