use crate::base64::decode;
use crate::config::Config;
use crate::encoding::Encoding;
use chrono::{DateTime, Local};
use json::{array, object, JsonValue};
use log::{debug, error, warn};
use std::collections::{BTreeMap, HashMap};
use std::io::{Error, ErrorKind, Write};
use std::path::PathBuf;
use std::str::Lines;
use std::{env, fs};
#[derive(Clone, Debug)]
pub struct Request {
pub config: Config,
pub protocol: Protocol,
pub method: Method,
pub uri: Uri,
pub header: BTreeMap<String, JsonValue>,
pub cookie: BTreeMap<String, JsonValue>,
pub custom: BTreeMap<String, JsonValue>,
pub body: Body,
pub authorization: Authorization,
pub handle_time: f64,
pub datetime: String,
pub client_ip: String,
pub server_ip: String,
pub upgrade: Upgrade,
pub connection: Connection,
pub accept_encoding: Encoding,
}
impl Request {
pub fn new(data: Vec<u8>, config: Config) -> Result<Request, Error> {
let text = unsafe { String::from_utf8_unchecked(data) };
if config.is_debug {
debug!("\r\n=================请求信息=================\r\n{}\r\n========================================", text);
}
let lines = text.lines();
let mut request = Request::default();
request.set_config(config.clone());
request.get_request_line(lines.clone().next().unwrap())?;
request.get_header(lines.clone())?;
request.uri.handle(request.header.clone());
request.uri.scheme = if config.https { "https" } else { "http" }.to_string();
let local: DateTime<Local> = Local::now();
request.datetime = local.format("%Y-%m-%d %H:%M:%S").to_string();
Ok(request)
}
pub fn get_request_line(&mut self, line: &str) -> Result<(), Error> {
let lines = line.split_whitespace().collect::<Vec<&str>>();
if lines.len() != 3 {
return Err(Error::new(ErrorKind::Other, "请求行错误"));
}
self.method = Method::from(lines[0]);
self.uri = Uri::from(lines[1]);
self.protocol = Protocol::from(lines[2]);
Ok(())
}
fn get_header(&mut self, data: Lines) -> Result<(), Error> {
let mut header: BTreeMap<String, JsonValue> = BTreeMap::new();
let mut cookie: BTreeMap<String, JsonValue> = BTreeMap::new();
let mut body = Body::default();
for text in data {
let (key, value) = match text.trim().find(":") {
None => continue,
Some(e) => {
let key = text[..e].trim().to_lowercase().clone();
let value = text[e + 1..].trim().to_string();
(key, value)
}
};
match key.as_str() {
"content-type" => match value {
_ if value.contains("multipart/form-data") => {
let boundarys = value.split("boundary=").collect::<Vec<&str>>();
body.boundary = boundarys[1..].join("");
body.content_type = ContentType::from("multipart/form-data");
header.insert(key, "multipart/form-data".into());
}
_ => {
let value = match value.find(";") {
None => value,
Some(e) => value[..e].trim().to_string(),
};
body.content_type = ContentType::from(value.as_str());
header.insert(key, body.content_type.str().into());
}
},
"content-length" => {
body.content_length = value.to_string().parse::<usize>().unwrap_or(0);
}
"authorization" => {
self.authorization = Authorization::from(&value);
}
"cookie" => {
let _ = value
.split(";")
.collect::<Vec<&str>>()
.iter()
.map(|&x| {
match x.find("=") {
None => {}
Some(index) => {
let key = x[..index].trim().to_string();
let val = x[index + 1..].trim().to_string();
cookie.insert(key, val.into());
}
};
""
})
.collect::<Vec<&str>>();
}
"upgrade" => {
self.upgrade = Upgrade::from(&value);
}
"connection" => {
self.connection = Connection::from(&value);
}
"accept-encoding" => {
self.accept_encoding = Encoding::from(&value);
}
_ => {
header.insert(key, value.into());
}
};
}
self.header = header.clone();
self.cookie = cookie.clone();
self.body = body.clone();
Ok(())
}
fn set_config(&mut self, config: Config) {
self.config = config;
}
}
impl Default for Request {
fn default() -> Self {
Self {
config: Default::default(),
protocol: Protocol::None,
method: Method::None,
uri: Default::default(),
header: Default::default(),
cookie: Default::default(),
custom: Default::default(),
body: Default::default(),
authorization: Authorization::None,
handle_time: 0.0,
datetime: "".to_string(),
client_ip: "".to_string(),
server_ip: "".to_string(),
upgrade: Upgrade::Http,
connection: Connection::None,
accept_encoding: Encoding::None,
}
}
}
#[derive(Clone, Debug)]
pub enum Protocol {
HTTP1_1,
HTTP2,
None,
}
impl Protocol {
pub fn from(name: &str) -> Self {
match name.to_lowercase().as_str() {
"http/1.1" => Self::HTTP1_1,
"http/2" => Self::HTTP2,
_ => Self::None,
}
}
pub fn str(&mut self) -> String {
match self {
Protocol::HTTP1_1 => "HTTP/1.1",
Protocol::HTTP2 => "HTTP/2",
Protocol::None => "",
}
.to_string()
}
}
#[derive(Clone, Debug)]
pub enum Method {
POST,
GET,
HEAD,
PUT,
DELETE,
OPTIONS,
PATCH,
TRACE,
None,
}
impl Method {
pub fn from(name: &str) -> Self {
match name.to_lowercase().as_str() {
"post" => Self::POST,
"get" => Self::GET,
"head" => Self::HEAD,
"put" => Self::PUT,
"delete" => Self::DELETE,
"options" => Self::OPTIONS,
"patch" => Self::PATCH,
"trace" => Self::TRACE,
_ => Self::None,
}
}
pub fn str(&mut self) -> &'static str {
match self {
Method::POST => "POST",
Method::GET => "GET",
Method::HEAD => "HEAD",
Method::PUT => "PUT",
Method::DELETE => "DELETE",
Method::OPTIONS => "OPTIONS",
Method::PATCH => "PATCH",
Method::TRACE => "TRACE",
Method::None => "",
}
}
}
#[derive(Clone, Debug)]
pub struct Uri {
pub url: String,
pub query: String,
pub query_params: BTreeMap<String, String>,
pub fragment: String,
pub path: String,
pub path_segments: Vec<String>,
pub scheme: String,
pub host: String,
pub port: String,
}
impl Uri {
pub fn from(url: &str) -> Self {
let mut uri = Uri::decode(url).unwrap_or(url.to_string());
let fragment = match uri.rfind("#") {
None => "".to_string(),
Some(index) => uri.drain(index..).collect::<String>(),
};
let mut query = "".to_string();
let query_params = match uri.rfind("?") {
None => BTreeMap::new(),
Some(index) => {
let text = uri.drain(index..).collect::<String>();
query = text.trim_start_matches("?").parse().unwrap();
let text = query.split("&").collect::<Vec<&str>>();
let mut params = BTreeMap::new();
for &item in text.iter() {
match item.find("=") {
None => continue,
Some(e) => {
let key = item[..e].to_string();
let value = item[e + 1..].to_string();
params.insert(
Uri::decode(&key).unwrap_or(key.to_string()),
Uri::decode(&value).unwrap_or(key.to_string()),
);
}
};
}
params
}
};
let path_segments = uri.split("/").collect::<Vec<&str>>();
let path_segments = path_segments
.into_iter()
.filter(|&x| !x.is_empty())
.collect::<Vec<&str>>()
.iter()
.map(|&x| x.to_string())
.collect::<Vec<String>>();
Self {
url: url.to_string(),
query,
query_params,
fragment,
path: uri.clone(),
path_segments,
scheme: "".to_string(),
host: "".to_string(),
port: "".to_string(),
}
}
pub fn decode(input: &str) -> Result<String, String> {
let mut decoded = String::new();
let bytes = input.as_bytes();
let mut i = 0;
while i < bytes.len() {
if bytes[i] == b'%' {
if i + 2 >= bytes.len() {
return Err("Incomplete percent-encoding".into());
}
let hex = &input[i + 1..i + 3];
match u8::from_str_radix(hex, 16) {
Ok(byte) => decoded.push(byte as char),
Err(_) => return Err(format!("Invalid percent-encoding: %{}", hex)),
}
i += 3;
} else if bytes[i] == b'+' {
decoded.push(' ');
i += 1;
} else {
decoded.push(bytes[i] as char);
i += 1;
}
}
Ok(decoded)
}
pub fn handle(&mut self, header: BTreeMap<String, JsonValue>) {
let host = header.get("host").unwrap_or(&"".into()).clone();
if !host.is_empty() {
let hostport = host
.as_str()
.unwrap_or("")
.split(":")
.collect::<Vec<&str>>();
if hostport.len() > 1 {
self.host = hostport[0].to_string();
self.port = hostport[1].to_string();
} else {
self.host = hostport[0].to_string();
}
}
}
pub fn to_json(&self) -> JsonValue {
object! {
url: self.url.clone(),
query: self.query.clone(),
query_params: self.query_params.clone(),
fragment: self.fragment.clone(),
path: self.path.clone(),
path_segments: self.path_segments.clone(),
scheme: self.scheme.clone(),
host: self.host.clone(),
port: self.port.clone(),
}
}
}
impl Default for Uri {
fn default() -> Self {
Self {
url: "".to_string(),
query: "".to_string(),
query_params: Default::default(),
fragment: "".to_string(),
path: "".to_string(),
scheme: "".to_string(),
path_segments: Vec::new(),
host: "".to_string(),
port: "".to_string(),
}
}
}
#[derive(Debug, Clone)]
pub enum ContentType {
FormData,
FormUrlencoded,
Json,
Xml,
Javascript,
Text,
Html,
Other(String),
}
impl ContentType {
pub fn from(name: &str) -> Self {
match name {
"multipart/form-data" => Self::FormData,
"application/x-www-form-urlencoded" => Self::FormUrlencoded,
"application/json" => Self::Json,
"application/xml" | "text/xml" => Self::Xml,
"application/javascript" => Self::Javascript,
"text/html" => Self::Html,
"text/plain" => Self::Text,
_ => Self::Other(name.to_string()),
}
}
pub fn str(&mut self) -> String {
match self {
Self::FormData => "multipart/form-data",
Self::FormUrlencoded => "application/x-www-form-urlencoded",
Self::Json => "application/json",
Self::Xml => "application/xml",
Self::Javascript => "application/javascript",
Self::Text => "text/plain",
Self::Html => "text/html",
Self::Other(name) => name,
}
.to_string()
}
}
#[derive(Clone, Debug)]
pub enum Authorization {
Basic(String, String),
Bearer(String),
Digest(HashMap<String, String>),
None,
}
impl Authorization {
pub fn from(data: &str) -> Self {
let authorization = data.split_whitespace().collect::<Vec<&str>>();
let mode = authorization[0].to_lowercase();
match mode.as_str() {
"basic" => match decode(&authorization[1].to_string().clone()) {
Ok(decoded) => {
let text = String::from_utf8(decoded.clone()).unwrap();
let text: Vec<&str> = text.split(":").collect();
Self::Basic(text[0].to_string(), text[1].to_string())
}
Err(e) => {
error!("{}basic认证解码错误: {}", line!(), e);
Self::Basic("".to_string(), "".to_string())
}
},
"bearer" => Self::Bearer(authorization[1].to_string()),
"digest" => {
let text = authorization[1..].concat().clone();
let text = text.split(",").collect::<Vec<&str>>();
let mut params = HashMap::new();
for item in text.iter() {
let index = match item.find("=") {
None => continue,
Some(e) => e,
};
let key = item[..index].to_string();
let value = item[index + 2..item.len() - 1].to_string();
params.insert(key, value);
}
Self::Digest(params)
}
_ => {
warn!("未知认证模式: {}", mode);
Self::None
}
}
}
pub fn str(&mut self) -> JsonValue {
match self {
Authorization::Basic(key, value) => {
let mut data = object! {};
data[key.as_str()] = value.clone().into();
data
}
Authorization::Bearer(e) => e.clone().into(),
Authorization::Digest(e) => e.clone().into(),
Authorization::None => "".into(),
}
}
}
#[derive(Debug, Clone)]
pub struct Body {
pub content_type: ContentType,
pub boundary: String,
pub content_length: usize,
pub content: JsonValue,
}
impl Body {
pub fn set_content(&mut self, data: Vec<u8>) {
match self.content_type.clone() {
ContentType::FormData => {
let mut fields = object! {};
let boundary_marker = format!("--{}", self.boundary);
let text = unsafe { String::from_utf8_unchecked(data) };
let parts = text.split(&boundary_marker).collect::<Vec<&str>>();
for part in parts {
let part = part.trim();
if part.is_empty() || part == "--" {
continue; }
let mut headers_and_body = part.splitn(2, "\r\n\r\n");
if let (Some(headers), Some(body)) =
(headers_and_body.next(), headers_and_body.next())
{
let headers = headers.split("\r\n");
let mut field_name = "";
let mut filename = "";
let mut content_type = ContentType::Text;
for header in headers {
if header.to_lowercase().starts_with("content-disposition:") {
match header.find("filename=\"") {
None => {}
Some(filename_start) => {
let filename_len = filename_start + 10;
let filename_end =
header[filename_len..].find('"').unwrap()
+ filename_len;
filename = &header[filename_len..filename_end];
}
}
match header.find("name=\"") {
None => {}
Some(name_start) => {
let name_start = name_start + 6;
let name_end =
header[name_start..].find('"').unwrap() + name_start;
field_name = &header[name_start..name_end];
}
}
}
if header.to_lowercase().starts_with("content-type:") {
content_type = ContentType::from(
header
.to_lowercase()
.trim_start_matches("content-type:")
.trim(),
);
}
}
if filename.is_empty() {
fields[field_name.to_string()] = JsonValue::from(body);
} else {
let mut temp_dir = env::temp_dir();
temp_dir.push(filename);
let mut temp_file = match fs::File::create(&temp_dir) {
Ok(e) => e,
Err(_) => continue,
};
if temp_file.write(body.as_bytes()).is_ok() {
if fields[field_name.to_string()].is_empty() {
fields[field_name.to_string()] = array![]
}
fields[field_name.to_string()]
.push(object! {
name:filename,
"type":content_type.str(),
file:temp_dir.to_str()
})
.unwrap();
};
}
}
}
self.content = fields;
}
ContentType::FormUrlencoded => {
let text = unsafe { String::from_utf8_unchecked(data) };
let params = text.split("&").collect::<Vec<&str>>();
let mut list = object! {};
for param in params.iter() {
let t = param
.split("=")
.collect::<Vec<&str>>()
.iter()
.map(|&x| Uri::decode(x).unwrap_or(x.to_string()))
.collect::<Vec<String>>();
list[t[0].to_string()] = t[1].clone().into();
}
self.content = list;
}
ContentType::Json => {
let text = unsafe { String::from_utf8_unchecked(data) };
self.content = json::parse(text.as_str()).unwrap_or(object! {});
}
ContentType::Xml => {
let text = unsafe { String::from_utf8_unchecked(data) };
self.content = text.into();
}
ContentType::Html | ContentType::Text | ContentType::Javascript => {
let text = unsafe { String::from_utf8_unchecked(data) };
self.content = text.into();
}
ContentType::Other(name) => match name.as_str() {
"application/pdf" => {
let text = unsafe { String::from_utf8_unchecked(data) };
self.content = text.into();
}
_ => {
let text = unsafe { String::from_utf8_unchecked(data) };
self.content = text.into();
}
},
}
}
}
impl Default for Body {
fn default() -> Self {
Self {
content_type: ContentType::Other("text/plain".to_string()),
boundary: "".to_string(),
content_length: 0,
content: object! {},
}
}
}
#[derive(Clone, Debug)]
pub enum Content {
FormUrlencoded(HashMap<String, String>),
FormData(HashMap<String, FormData>),
Json(JsonValue),
Text(JsonValue),
Xml(JsonValue),
None,
}
impl Content {}
#[derive(Clone, Debug)]
pub enum FormData {
File(String, PathBuf),
Field(JsonValue),
}
#[derive(Clone, Debug)]
pub enum Upgrade {
#[cfg(feature = "ws")]
Websocket,
Http,
None,
}
impl Upgrade {
pub fn from(name: &str) -> Self {
match name.to_lowercase().as_str() {
#[cfg(feature = "ws")]
"websocket" => Self::Websocket,
"http" => Self::Http,
_ => Self::None,
}
}
pub fn str(&mut self) -> String {
match self {
#[cfg(feature = "ws")]
Self::Websocket => "websocket",
Self::Http => "http",
Self::None => "",
}
.to_string()
}
}
#[derive(Clone, Debug)]
pub enum Connection {
KeepAlive,
Close,
Upgrade,
None,
}
impl Connection {
pub fn from(value: &str) -> Self {
match value.to_lowercase().as_str() {
"upgrade" => Self::Upgrade,
"keep-alive" => Self::KeepAlive,
"close" => Self::Close,
_ => Self::None,
}
}
}