use std::{fs, thread};
use std::fs::OpenOptions;
use std::io::Write;
use std::str::Lines;
use std::time::{Duration, UNIX_EPOCH};
use chrono::{DateTime, Local};
use json::{array, JsonValue, object};
use log::{debug, error, info};
use crate::config::Config;
use crate::content_type::ContentType;
#[derive(Clone, Debug)]
pub struct Request {
pub config: Config,
pub protocol: Protocol,
pub method: Method,
pub uri: String,
pub app: String,
pub timestamp: f64,
pub datetime: String,
pub server_ip: String,
pub client_ip: String,
pub agent_ip: String,
pub origin: String,
pub host: String,
sec_fetch_dest: SecFetchDest,
sec_fetch_mode: SecFetchMode,
sec_fetch_site: SecFetchSite,
pub connection: Connection,
pub user_agent: String,
pub accept: String,
pub accept_encoding: String,
pub accept_language: String,
pub cache_control: String,
pub content_length: usize,
pub content_body_length: usize,
pub body: Vec<u8>,
pub referer: String,
pub content_type: RequestContentType,
pub boundary: String,
pub custom: JsonValue,
pub path: String,
pub query: String,
pub post: JsonValue,
pub text: String,
pub html: String,
pub get: JsonValue,
pub files: JsonValue,
pub authorization_mode: String,
pub authorization_user: String,
pub authorization_pass: String,
pub authorization_token: String,
pub authorization_data: String,
pub is_origin: bool,
pub upgrade: String,
pub sec_web_socket_key: String,
pub sec_web_socket_version: String,
}
impl Request {
pub fn new(config: Config, data: Vec<u8>) -> Result<Self, String> {
let (header, body) = if let Some(index) = data.windows(4).position(|window| window == [13, 10, 13, 10]) {
(data[..index].to_vec(), data[index + 4..].to_vec())
} else {
return Err("未找到消息头".to_string());
};
let header = unsafe { String::from_utf8_unchecked(header) };
if config.debug {
debug!("{:#}",header);
}
let mut data = header.lines();
let header_line = data.next().unwrap_or("");
let parts = header_line.split_whitespace().collect::<Vec<&str>>();
if parts.len() != 3 {
return Err(format!("非法请求: {}", header_line));
}
let method = Method::from(parts[0]);
let mut uri = br_crypto::encoding::urlencoding_decode(parts[1]);
if !uri.starts_with("/") {
uri = format!("/{}", uri.clone());
}
let protocol = Protocol::from(parts[2]);
let timestamp = Local::now().timestamp_millis() as f64 / 1000.0;
let d = UNIX_EPOCH + Duration::from_secs(timestamp as u64);
let datetime = DateTime::<Local>::from(d);
let datetime = datetime.format("%Y-%m-%d %H:%M:%S").to_string();
let mut request = Self {
config: config.clone(),
timestamp,
datetime,
protocol,
method,
uri,
server_ip: "".to_string(),
client_ip: "".to_string(),
agent_ip: "".to_string(),
origin: "".to_string(),
host: "".to_string(),
sec_fetch_dest: SecFetchDest::None,
sec_fetch_mode: SecFetchMode::None,
sec_fetch_site: SecFetchSite::None,
connection: Connection::None,
user_agent: "".to_string(),
accept: "".to_string(),
accept_encoding: "".to_string(),
accept_language: "".to_string(),
cache_control: "".to_string(),
content_length: 0,
content_body_length: body.len(),
body,
referer: "".to_string(),
content_type: RequestContentType::None,
boundary: "".to_string(),
custom: object! {},
path: "".to_string(),
query: "".to_string(),
post: object! {},
text: "".to_string(),
html: "".to_string(),
get: object! {},
files: object! {},
authorization_mode: "".to_string(),
authorization_user: "".to_string(),
authorization_pass: "".to_string(),
authorization_token: "".to_string(),
is_origin: false,
app: "".to_string(),
authorization_data: "".to_string(),
sec_web_socket_key: "".to_string(),
upgrade: "".to_string(),
sec_web_socket_version: "".to_string(),
};
request.header(data);
Ok(request)
}
pub fn json(&mut self) -> JsonValue {
let mut res = object! {};
res["protocol"] = self.protocol.str().into();
res["method"] = self.method.clone().str().into();
res["timestamp"] = self.timestamp.into();
res["datetime"] = self.datetime.clone().into();
res["schema"] = self.config.schema.clone().into();
res["domain"] = self.config.url.clone().into();
res["url"] = self.config.url.clone().into();
res["uri"] = self.uri.clone().into();
res["app"] = self.app.clone().into();
res["origin"] = self.origin.clone().into();
res["host"] = self.host.clone().into();
res["user_agent"] = self.user_agent.clone().into();
res["content_type"] = self.content_type.clone().str().into();
res["content_length"] = self.content_length.into();
res["server_ip"] = self.server_ip.clone().into();
res["client_ip"] = self.client_ip.clone().into();
res["agent_ip"] = self.agent_ip.clone().into();
res["path"] = self.path.clone().into();
res["query"] = self.query.clone().into();
res["custom"] = self.custom.clone();
res["post"] = self.post.clone();
res["get"] = self.get.clone();
res["text"] = self.text.clone().into();
res["html"] = self.html.clone().into();
res["files"] = self.files.clone();
res["authorization_mode"] = self.authorization_mode.clone().into();
res["authorization_user"] = self.authorization_user.clone().into();
res["authorization_pass"] = self.authorization_pass.clone().into();
res["authorization_token"] = self.authorization_token.clone().into();
res["authorization_data"] = self.authorization_data.clone().into();
res["public"] = self.config.public.clone().into();
res["runtime"] = self.config.runtime.clone().into();
if self.config.debug {
res["sec-fetch-dest"] = self.sec_fetch_dest.str().into();
res["sec-fetch-mode"] = self.sec_fetch_mode.str().into();
res["sec-fetch-site"] = self.sec_fetch_site.str().into();
res["accept-language"] = self.accept_language.clone().into();
res["connection"] = self.connection.str().into();
}
res
}
pub fn header(&mut self, data: Lines) -> &mut Self {
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() {
"host" => self.host = value,
"sec-fetch-site" => self.sec_fetch_site = SecFetchSite::from(&value),
"sec-fetch-mode" => self.sec_fetch_mode = SecFetchMode::from(&value),
"sec-fetch-dest" => self.sec_fetch_dest = SecFetchDest::from(&value),
"connection" => self.connection = Connection::from(&value),
"user-agent" => self.user_agent = value,
"accept" => self.accept = value,
"accept-language" => {
let language = value.split(",").collect::<Vec<&str>>();
if language.len() > 1 {
self.accept_language = language[0].to_string();
} else {
self.accept_language = value;
}
}
"accept-encoding" => self.accept_encoding = value.split(",").collect::<Vec<&str>>().join(";"),
"cache-control" => self.cache_control = value,
"content-type" => {
match value {
_ if value.contains("multipart/form-data") => {
let boundary: Vec<&str> = value.split("boundary=").collect();
self.boundary = boundary[1..].join("").to_string();
self.content_type = RequestContentType::from("multipart/form-data");
}
_ => {
self.content_type = RequestContentType::from(value.as_str());
}
}
}
"content-length" => self.content_length = value.parse::<usize>().unwrap_or(0),
"origin" => self.origin = value,
"referer" => self.referer = value,
"authorization" => {
let authorization = value.split_whitespace().collect::<Vec<&str>>();
self.authorization_mode = authorization[0].to_lowercase();
match self.authorization_mode.as_str() {
"basic" => {
let text = br_crypto::base64::decode(authorization[1]);
let text: Vec<&str> = text.split(":").collect();
self.authorization_user = text[0].to_string();
self.authorization_pass = text[1].to_string();
}
"bearer" => {
self.authorization_token = authorization[1].to_string();
}
_ => {
self.authorization_data = authorization[1..].concat();
}
}
}
"sec-websocket-key" => {
self.sec_web_socket_key = value;
}
"sec_websocket_version" => {
self.sec_web_socket_version = value;
}
"upgrade" => {
self.upgrade = value;
}
_ => {
if !key.starts_with("x-") {
continue;
}
match &key[2..] {
"real-ip" => {
self.client_ip = value;
}
"forwarded-for" => {
self.agent_ip = value;
}
_ => {
self.custom[&key[2..]] = value.into();
}
}
}
}
}
self.query();
self
}
fn query(&mut self) {
let mut uri = self.uri.clone();
match uri.find("?") {
None => {}
Some(e) => {
let query = uri[e + 1..].to_string();
uri = uri[..e].to_string();
self.query = query.clone();
let params = if query.contains("&") {
query.split("&").collect::<Vec<&str>>()
} else {
vec![query.as_str()]
};
for param in params {
let res = param.split("=").collect::<Vec<&str>>();
let key = res[0];
let value = res[1..].concat();
let key = br_crypto::encoding::urlencoding_decode(key);
let value = br_crypto::encoding::urlencoding_decode(value.as_str());
self.get[key] = value.into();
}
}
}
self.path = uri.clone();
let apps = self.path.split("/").collect::<Vec<&str>>();
self.app = if !apps.is_empty() {
apps[1].to_string()
} else {
"api".to_string()
};
}
pub(crate) fn record_log(&mut self) {
let text = format!("[{}] client-ip: {} agent_ip: {} content-type: {} content-length: {} {} {} {}\r\n",
self.datetime.clone(),
self.client_ip.clone(),
self.agent_ip.clone(),
self.content_type.clone().str(),
self.content_length.clone(),
self.method.clone().str(), self.protocol.str(), self.uri
);
let dir_path = format!("{}/log/{}", self.config.runtime, self.datetime[0..10].replace("-", "/"));
let path = format!("{}/{}.log", dir_path.clone(), &self.datetime[11..13]);
let debug = self.config.debug;
thread::spawn(move || {
let path_new = path.clone();
match fs::create_dir_all(dir_path.clone()) {
Ok(_) => {}
Err(e) => {
if debug {
return error!("创建日志目录失败: {} {}",dir_path.clone(),e.to_string());
}
return;
}
};
match OpenOptions::new()
.read(true)
.create(true)
.append(true)
.open(path_new.clone()) {
Ok(mut file) => {
let _ = file.write_all(text.as_bytes());
}
Err(_) => {
if debug {
error!("日志写入失败: {}",path_new.clone())
}
}
}
});
}
pub fn body(&mut self) -> Result<(), String> {
if self.content_length > self.content_body_length {
return Err(format!("消息长度错误: {}/{}", self.content_body_length, self.content_length));
}
match self.content_type {
RequestContentType::FormData => {
let request = unsafe { String::from_utf8_unchecked(self.body.clone()) };
let mut request = request.trim_end_matches(format!("--{}--\r\n", self.boundary).as_str()).split(format!("--{}\r\n", self.boundary).as_str()).collect::<Vec<&str>>();
request.remove(0);
for item in request.iter_mut() {
if item.contains("filename=") {
let text: Vec<&str> = item.split("\r\n\r\n").collect();
let temp = if text.len() > 2 {
text[1..].join("\r\n\r\n")
} else {
text[1].to_string()
};
let body = match temp.strip_suffix("\r\n") {
None => temp.as_str(),
Some(v) => v
};
let text: Vec<&str> = text[0].split("\r\n").collect();
let name: Vec<&str> = text[0].split("\"").collect();
let types: Vec<&str> = text[1].split(": ").collect();
let accept = types[1];
let field = name[1];
let filename = name[3];
let mut file_data = object! {};
file_data["accept"] = accept.into();
file_data["name"] = filename.into();
file_data["md5"] = br_crypto::hash::str_to_md5(body).into();
file_data["suffix"] = ContentType::from(accept).suffix.into();
file_data["types"] = ContentType::from_suffix(file_data["suffix"].as_str().unwrap()).suffix_type_name().into();
file_data["size"] = body.len().into();
file_data["data"] = br_crypto::base64::encode_file(body.as_bytes().into()).into();
if self.files[field].is_empty() {
self.files[field] = array![file_data.clone()];
} else {
self.files[field].push(file_data.clone()).unwrap();
}
} else {
let re = item.split("\r\n\r\n").collect::<Vec<&str>>();
let name = re[0].trim_end_matches("\"").split("name=\"").collect::<Vec<&str>>();
self.post[name[1]] = re[1].trim_end_matches("\r\n").into();
}
}
}
RequestContentType::FOrmUrlencoded => {
let request = unsafe { String::from_utf8_unchecked(self.body.clone()) };
let params = if request.contains("&") {
request.split("&").collect::<Vec<&str>>()
} else {
vec![request.as_str()]
};
for param in params {
let res = param.split("=").collect::<Vec<&str>>();
let key = res[0];
let value = res[1..].concat();
let key = br_crypto::encoding::urlencoding_decode(key);
let value = br_crypto::encoding::urlencoding_decode(value.as_str());
self.post[key] = value.into();
}
}
RequestContentType::Javascript => {
let request = unsafe { String::from_utf8_unchecked(self.body.clone()) };
if self.config.debug {
info!("Javascript: {}", request);
}
}
RequestContentType::Json => {
let request = unsafe { String::from_utf8_unchecked(self.body.clone()) };
match json::parse(request.as_str()) {
Ok(e) => {
self.post = e;
}
Err(_) => {
error!("解析json失败,请检查格式:{}", request);
self.post = object! {}
}
};
}
RequestContentType::Xml => {
let request = unsafe { String::from_utf8_unchecked(self.body.clone()) };
let res = br_xml::Xml::xml_json(request);
self.post = res;
}
RequestContentType::Text => {
let request = unsafe { String::from_utf8_unchecked(self.body.clone()) };
self.text = request;
}
RequestContentType::Html => {
let request = unsafe { String::from_utf8_unchecked(self.body.clone()) };
self.html = request;
}
RequestContentType::None => {
let request = unsafe { String::from_utf8_unchecked(self.body.clone()) };
if self.config.debug {
info!("RequestContentType 未知: {}", request);
}
}
}
Ok(())
}
pub(crate) fn allow_origin(&mut self) {
self.is_origin = false;
if !self.config.cors.allow_origin.is_empty() {
if self.origin.is_empty() {
for origin in &self.config.cors.allow_origin {
if self.client_ip.contains(origin) {
self.is_origin = true;
}
}
} else {
for origin in &self.config.cors.allow_origin {
if self.origin.contains(origin) {
self.is_origin = true;
}
}
}
} else {
self.is_origin = true;
}
}
}
#[derive(Clone, Debug)]
enum SecFetchSite {
CrossSite,
SameOrigin,
None,
}
impl SecFetchSite {
pub fn from(name: &str) -> Self {
match name.to_lowercase().as_str() {
"none" => Self::None,
"cross-site" => Self::CrossSite,
"same-origin" => Self::SameOrigin,
_ => Self::None,
}
}
pub fn str(&mut self) -> &'static str {
match self {
SecFetchSite::CrossSite => "cross-site",
SecFetchSite::SameOrigin => "same-origin",
SecFetchSite::None => "none"
}
}
}
#[derive(Clone, Debug)]
enum SecFetchMode {
Navigate,
SameOrigin,
NoCors,
Cors,
Websocket,
None,
}
impl SecFetchMode {
pub fn from(name: &str) -> Self {
match name.to_lowercase().as_str() {
"navigate" => Self::Navigate,
"same-origin" => Self::SameOrigin,
"no-cors" => Self::NoCors,
"cors" => Self::Cors,
"websocket" => Self::Websocket,
_ => Self::None,
}
}
pub fn str(&mut self) -> &'static str {
match self {
Self::Navigate => "navigate",
Self::SameOrigin => "same-origin",
Self::NoCors => "no-cors",
Self::Cors => "cors",
Self::Websocket => "websocket",
Self::None => ""
}
}
}
#[derive(Clone, Debug)]
enum SecFetchDest {
Document,
Image,
Media,
Font,
Script,
Css,
Manifest,
Serviceworker,
None,
}
impl SecFetchDest {
pub fn from(name: &str) -> Self {
match name.to_lowercase().as_str() {
"document" => Self::Document,
"image" => Self::Image,
"media" => Self::Media,
"font" => Self::Font,
"script" => Self::Script,
"css" => Self::Css,
"manifest" => Self::Manifest,
"serviceworker" => Self::Serviceworker,
_ => Self::None,
}
}
pub fn str(&mut self) -> &'static str {
match self {
Self::Document => "document",
Self::Image => "image",
Self::Media => "media",
Self::Font => "font",
Self::Script => "script",
Self::Css => "css",
Self::Manifest => "manifest",
Self::Serviceworker => "serviceworker",
Self::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) -> &'static str {
match self {
Protocol::HTTP1_1 => "HTTP/1.1",
Protocol::HTTP2 => "HTTP/2",
Protocol::None => ""
}
}
}
#[derive(Clone, Debug)]
pub enum Connection {
KeepAlive,
Close,
Upgrade,
None,
}
impl Connection {
pub fn from(name: &str) -> Self {
match name.to_lowercase().as_str() {
"keep-alive" => Self::KeepAlive,
"close" => Self::Close,
"upgrade" => Self::Upgrade,
_ => Self::None,
}
}
pub fn str(&mut self) -> &'static str {
match self {
Self::KeepAlive => "keep-alive",
Self::Close => "close",
Self::Upgrade => "upgrade",
Self::None => ""
}
}
}
#[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(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 enum RequestContentType {
FormData,
FOrmUrlencoded,
Json,
Xml,
Javascript,
Text,
Html,
None,
}
impl RequestContentType {
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,
_ => {
info!("未知请求类型: {}", name);
Self::None
}
}
}
pub fn str(self) -> &'static str {
match self {
RequestContentType::FormData => "multipart/form-data",
RequestContentType::FOrmUrlencoded => "application/x-www-form-urlencoded",
RequestContentType::Json => "application/json",
RequestContentType::Xml => "application/xml",
RequestContentType::Javascript => "application/javascript",
RequestContentType::Text => "text/plain",
RequestContentType::Html => "text/html",
RequestContentType::None => ""
}
}
}