Files
tuliprox/src/utils/request_utils.rs
T

247 lines
11 KiB
Rust
Raw Normal View History

use std::collections::{HashMap, HashSet};
2021-12-21 11:06:51 +01:00
use std::fs;
2024-05-27 09:51:35 +02:00
use std::io::{Error, ErrorKind, Read};
use std::path::PathBuf;
use flate2::read::{GzDecoder, ZlibDecoder};
2024-03-26 16:07:31 +01:00
use log::{debug, error, Level, log_enabled};
2023-11-06 21:33:48 +01:00
use reqwest::header::{HeaderMap, HeaderName, HeaderValue};
2024-05-25 18:55:28 +02:00
use reqwest::header::CONTENT_ENCODING;
use url::Url;
use crate::create_m3u_filter_error_result;
use crate::m3u_filter_error::{M3uFilterError, M3uFilterErrorKind};
use crate::model::config::ConfigInput;
use crate::utils::file_utils::{get_file_path, persist_file};
2024-08-15 16:45:44 +02:00
fn is_gzip(bytes: &[u8]) -> bool {
// Gzip files start with the bytes 0x1F 0x8B
bytes.len() >= 2 && bytes[0] == 0x1F && bytes[1] == 0x8B
}
pub(crate) fn bytes_to_megabytes(bytes: u64) -> u64 {
bytes / 1_048_576
}
pub(crate) async fn get_input_text_content(input: &ConfigInput, working_dir: &String, url_str: &str, persist_filepath: Option<PathBuf>) -> Result<String, M3uFilterError> {
2024-03-26 16:07:31 +01:00
if log_enabled!(Level::Debug) {
debug!("getting input text content working_dir: {}, url: {}", working_dir, url_str);
}
2024-05-10 12:01:48 +02:00
2024-05-27 09:51:35 +02:00
if url_str.parse::<url::Url>().is_ok() {
match download_text_content(input, url_str, persist_filepath).await {
2023-10-13 13:59:22 +02:00
Ok(content) => Ok(content),
Err(e) => {
2023-10-08 19:54:59 +02:00
error!("cant download input url: {} => {}", url_str, e);
2023-10-13 13:59:22 +02:00
create_m3u_filter_error_result!(M3uFilterErrorKind::Notify, "Failed to download")
}
}
2024-05-10 12:01:48 +02:00
} else {
let result = match get_file_path(working_dir, Some(PathBuf::from(url_str))) {
Some(filepath) => {
if filepath.exists() {
if let Some(persist_file_value) = persist_filepath {
let to_file = &persist_file_value;
match fs::copy(&filepath, to_file) {
Ok(_) => {}
Err(e) => {
error!("cant persist to: {} => {}", to_file.to_str().unwrap_or("?"), e);
return create_m3u_filter_error_result!(M3uFilterErrorKind::Notify, "Failed to persist: {} => {}", to_file.to_str().unwrap_or("?"), e);
2021-10-15 16:59:20 +02:00
}
2024-05-10 12:01:48 +02:00
}
};
match get_local_file_content(&filepath) {
Ok(content) => Some(content),
2024-05-10 12:01:48 +02:00
Err(err) => {
return create_m3u_filter_error_result!(M3uFilterErrorKind::Notify, "Failed : {}", err);
2024-05-10 12:01:48 +02:00
}
}
2024-05-10 12:01:48 +02:00
} else {
None
2021-10-15 16:59:20 +02:00
}
}
2024-05-10 12:01:48 +02:00
None => None
};
if let Some(content) = result {
Ok(content)
} else {
let msg = format!("cant read input url: {url_str:?}");
error!("{}", msg);
create_m3u_filter_error_result!(M3uFilterErrorKind::Notify, "{}", msg)
}
}
}
pub(crate) fn get_client_request(input: Option<&ConfigInput>, url: &Url, custom_headers: Option<&HashMap<&str, &[u8]>>) -> reqwest::RequestBuilder {
let mut request = reqwest::Client::new().get(url.clone());
let headers = get_request_headers(input.map_or(&HashMap::new(), |i| &i.headers), custom_headers);
2023-12-05 17:54:42 +01:00
request = request.headers(headers);
2023-10-27 19:03:29 +02:00
request
}
2023-12-01 21:38:23 +01:00
pub(crate) fn get_request_headers(defined_headers: &HashMap<String, String>, custom_headers: Option<&HashMap<&str, &[u8]>>) -> HeaderMap {
let mut headers = HeaderMap::new();
2023-11-06 21:33:48 +01:00
for (key, value) in defined_headers {
headers.insert(
HeaderName::from_bytes(key.as_bytes()).unwrap(),
2023-12-01 21:38:23 +01:00
HeaderValue::from_bytes(value.as_bytes()).unwrap());
}
if let Some(custom) = custom_headers {
let header_keys: HashSet<String> = headers.keys().map(|k| k.as_str().to_lowercase()).collect();
2023-12-01 21:38:23 +01:00
for (key, value) in custom {
let key_lc = key.to_lowercase();
if !("host" == key_lc || header_keys.contains(key_lc.as_str())) {
2023-12-01 21:38:23 +01:00
headers.insert(
HeaderName::from_bytes(key.as_bytes()).unwrap(),
HeaderValue::from_bytes(value).unwrap());
2024-03-26 16:07:31 +01:00
} else if log_enabled!(Level::Debug) {
2023-12-05 17:54:42 +01:00
debug!("Ignoring request header {}={}", key_lc, String::from_utf8_lossy(value));
2023-12-01 21:38:23 +01:00
}
}
2023-11-06 21:33:48 +01:00
}
2024-03-26 16:07:31 +01:00
if log_enabled!(Level::Debug) {
2023-12-05 17:54:42 +01:00
let he: HashMap<String, String> = headers.iter().map(|(k, v)| (k.to_string(), String::from_utf8_lossy(v.as_bytes()).to_string())).collect();
debug!("Request headers {:?}", he);
}
2023-11-06 21:33:48 +01:00
headers
}
fn get_local_file_content(file_path: &PathBuf) -> Result<String, Error> {
// Check if the file is accessible
if file_path.exists() && file_path.is_file() {
if let Ok(content) = fs::read(&file_path) {
if content.len() >= 2 && is_gzip(&content[0..2]) {
let mut decoder = GzDecoder::new(&content[..]);
let mut decode_buffer = String::new();
match decoder.read_to_string(&mut decode_buffer) {
Ok(_) => return Ok(decode_buffer),
Err(err) => return Err(Error::new(ErrorKind::Other, format!("failed to decode gzip content {err}")))
};
}
return Ok(String::from_utf8_lossy(&content).parse().unwrap());
}
}
let file_str = file_path.to_str().unwrap_or("?");
Err(Error::new(ErrorKind::InvalidData, format!("Cant find file {file_str}")))
}
async fn get_remote_content(input: &ConfigInput, url: &Url) -> Result<String, Error> {
let request = get_client_request(Some(input), url, None);
match request.send().await {
Ok(response) => {
let is_success = response.status().is_success();
if is_success {
let header_value = response.headers().get(CONTENT_ENCODING);
let mut encoding = if let Some(encoding_header) = header_value {
match encoding_header.to_str() {
Ok(value) => Some(value.to_string()),
Err(_) => None,
}
} else {
None
};
match response.bytes().await {
Ok(bytes) => {
if bytes.len() >= 2 && is_gzip(&bytes[0..2]) {
encoding = Some("gzip".to_string());
}
let mut decode_buffer = String::new();
if let Some(encoding_type) = encoding {
match encoding_type.as_str() {
"gzip" => {
let mut decoder = GzDecoder::new(&bytes[..]);
match decoder.read_to_string(&mut decode_buffer) {
Ok(_) => {}
Err(err) => return Err(std::io::Error::new(ErrorKind::Other, format!("failed to decode gzip content {err}")))
};
}
"deflate" => {
let mut decoder = ZlibDecoder::new(&bytes[..]);
match decoder.read_to_string(&mut decode_buffer) {
Ok(_) => {}
Err(err) => return Err(std::io::Error::new(ErrorKind::Other, format!("failed to decode zlib content {err}")))
2024-05-27 09:51:35 +02:00
}
}
_ => {}
};
}
if decode_buffer.is_empty() {
match String::from_utf8(bytes.to_vec()) {
Ok(decoded_content) => Ok(decoded_content),
Err(err) => Err(std::io::Error::new(ErrorKind::Other, format!("failed to plain text content {err}")))
2024-05-25 18:55:28 +02:00
}
} else {
Ok(decode_buffer)
2023-10-27 19:03:29 +02:00
}
}
Err(err) => Err(std::io::Error::new(ErrorKind::Other, format!("failed to read response {err}")))
2023-10-27 19:03:29 +02:00
}
} else {
Err(std::io::Error::new(ErrorKind::Other, format!("Request failed with status {}", response.status())))
}
}
Err(err) => Err(std::io::Error::new(ErrorKind::Other, format!("Request failed {err}")))
}
}
pub(crate) async fn download_text_content(input: &ConfigInput, url_str: &str, persist_filepath: Option<PathBuf>) -> Result<String, Error> {
if let Ok(url) = url_str.parse::<url::Url>() {
let result = if url.scheme() == "file" {
if let Ok(file_path) = url.to_file_path() {
get_local_file_content(&file_path)
} else {
Err(Error::new(ErrorKind::Other, format!("Unknown file {url}")))
2023-10-27 19:03:29 +02:00
}
} else {
get_remote_content(input, &url).await
2024-05-27 09:51:35 +02:00
};
match result {
Ok(content) => {
if persist_filepath.is_some() {
persist_file(persist_filepath, &content);
}
Ok(content)
}
Err(err) => Err(err)
2023-10-27 19:03:29 +02:00
}
2024-05-27 09:51:35 +02:00
} else {
Err(std::io::Error::new(ErrorKind::Other, format!("Malformed URL {url_str}")))
}
}
async fn download_json_content(input: &ConfigInput, url: &str, persist_filepath: Option<PathBuf>) -> Result<serde_json::Value, Error> {
if log_enabled!(Level::Debug) {
debug!("downloading json content from {url}");
}
match download_text_content(input, url, persist_filepath).await {
2024-05-25 18:55:28 +02:00
Ok(content) => {
2024-05-27 09:51:35 +02:00
match serde_json::from_str::<serde_json::Value>(&content) {
Ok(value) => Ok(value),
Err(err) => Err(Error::new(ErrorKind::Other, format!("Failed to parse json {err}")))
2024-05-25 18:55:28 +02:00
}
}
Err(err) => Err(err)
2023-10-27 19:03:29 +02:00
}
2024-05-27 09:51:35 +02:00
}
pub(crate) async fn get_input_json_content(input: &ConfigInput, url: &str, persist_filepath: Option<PathBuf>) -> Result<serde_json::Value, M3uFilterError> {
match download_json_content(input, url, persist_filepath).await {
Ok(content) => Ok(content),
Err(e) => create_m3u_filter_error_result!(M3uFilterErrorKind::Notify, "cant download input url: {url} => {}", e)
}
}
pub(crate) fn get_base_url(url: &str) -> Option<String> {
2024-10-16 12:59:53 +02:00
if let Some((scheme_end, rest)) = url.split_once("://") {
let scheme = scheme_end;
if let Some(authority_end) = rest.find('/') {
let authority = &rest[..authority_end];
return Some(format!("{}://{}", scheme, authority));
}
return Some(format!("{}://{}", scheme, rest));
}
2024-10-16 12:59:53 +02:00
None
}