mirror of
https://github.com/euzu/tuliprox.git
synced 2026-10-20 14:52:22 +02:00
Hls request headers added for fringerprinting.
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
use crate::api::api_utils::try_option_bad_request;
|
||||
use crate::api::api_utils::{get_headers_from_request, try_option_bad_request};
|
||||
use crate::api::api_utils::try_unwrap_body;
|
||||
use crate::api::api_utils::{
|
||||
force_provider_stream_response, get_stream_alternative_url, is_seek_request,
|
||||
};
|
||||
use crate::api::model::AppState;
|
||||
use crate::api::model::{AppState};
|
||||
use crate::api::model::UserSession;
|
||||
use crate::api::model::{create_custom_video_stream_response, CustomVideoStreamType};
|
||||
use crate::auth::Fingerprint;
|
||||
@@ -19,6 +19,7 @@ use serde::Deserialize;
|
||||
use shared::model::{PlaylistItemType, UserConnectionPermission, XtreamCluster};
|
||||
use shared::utils::{is_hls_url, replace_url_extension, sanitize_sensitive_info, HLS_EXT};
|
||||
use std::sync::Arc;
|
||||
use axum::http::HeaderMap;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct HlsApiPathParams {
|
||||
@@ -46,6 +47,7 @@ pub(in crate::api) async fn handle_hls_stream_request(
|
||||
hls_url: &str,
|
||||
virtual_id: u32,
|
||||
input: &ConfigInput,
|
||||
req_headers: &HeaderMap,
|
||||
connection_permission: UserConnectionPermission,
|
||||
) -> impl IntoResponse + Send {
|
||||
let url = replace_url_extension(hls_url, HLS_EXT);
|
||||
@@ -91,10 +93,15 @@ pub(in crate::api) async fn handle_hls_stream_request(
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
let filter_header = None;
|
||||
let req_headers = get_headers_from_request(req_headers, &filter_header);
|
||||
let headers = request::get_request_headers(None, Some(&req_headers));
|
||||
let input_source = InputSource::from(input).with_url(request_url);
|
||||
match request::download_text_content(
|
||||
Arc::clone(&app_state.http_client.load()),
|
||||
&input_source,
|
||||
Some(&headers),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
@@ -232,6 +239,7 @@ async fn hls_api_stream(
|
||||
&session.stream_url,
|
||||
virtual_id,
|
||||
&input,
|
||||
&req_headers,
|
||||
connection_permission,
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -217,6 +217,7 @@ async fn m3u_api_stream(
|
||||
&pli.url,
|
||||
pli.virtual_id,
|
||||
&input,
|
||||
&req_headers,
|
||||
connection_permission,
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -118,7 +118,7 @@ async fn config_batch_content(
|
||||
// The url is changed at this point, we need the raw url for the batch file
|
||||
if let Some(batch_url) = config_input.t_batch_url.as_ref() {
|
||||
let input_source = InputSource::from(&*config_input).with_url(batch_url.to_owned());
|
||||
return match download_text_content(Arc::clone(&app_state.http_client.load()), &input_source, None).await {
|
||||
return match download_text_content(Arc::clone(&app_state.http_client.load()), &input_source, None, None).await {
|
||||
Ok((content, _path)) => {
|
||||
// Return CSV with explicit content-type
|
||||
try_unwrap_body!(axum::response::Response::builder()
|
||||
|
||||
@@ -381,6 +381,7 @@ async fn xtream_player_api_stream(
|
||||
&stream_url,
|
||||
pli.virtual_id,
|
||||
&input,
|
||||
&req_headers,
|
||||
connection_permission,
|
||||
)
|
||||
.await
|
||||
@@ -483,6 +484,7 @@ async fn xtream_player_api_stream_with_token(
|
||||
&pli.url,
|
||||
pli.virtual_id,
|
||||
&input,
|
||||
&req_headers,
|
||||
UserConnectionPermission::Allowed,
|
||||
)
|
||||
.await
|
||||
@@ -1083,6 +1085,7 @@ async fn xtream_get_short_epg(
|
||||
Arc::clone(&app_state.http_client.load()),
|
||||
&input_source,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
{
|
||||
|
||||
@@ -104,7 +104,7 @@ pub async fn get_input_text_content(client: Arc<reqwest::Client>, input: &InputS
|
||||
debug_if_enabled!("getting input text content working_dir: {}, url: {}", working_dir, sanitize_sensitive_info(&input.url));
|
||||
|
||||
if input.url.parse::<url::Url>().is_ok() {
|
||||
match download_text_content(client, input, persist_filepath).await {
|
||||
match download_text_content(client, input, None, persist_filepath).await {
|
||||
Ok((content, _response_url)) => Ok(content),
|
||||
Err(e) => {
|
||||
error!("cant download input url: {} => {}", sanitize_sensitive_info(&input.url), sanitize_sensitive_info(e.to_string().as_str()));
|
||||
@@ -275,7 +275,6 @@ pub async fn get_remote_content_as_stream(
|
||||
|
||||
let response_url = response.url().to_string();
|
||||
let headers = response.headers();
|
||||
debug!("{headers:?}");
|
||||
let header_value = headers.get(CONTENT_ENCODING);
|
||||
let mut encoding = header_value.and_then(|encoding_header| encoding_header.to_str().map_or(None, |value| Some(value.to_string())));
|
||||
let stream_reader = StreamReader::new(
|
||||
@@ -303,9 +302,13 @@ pub async fn get_remote_content_as_stream(
|
||||
Ok((reader, response_url))
|
||||
}
|
||||
|
||||
async fn get_remote_content(client: Arc<reqwest::Client>, input: &InputSource, url: &Url) -> Result<(String, String), Error> {
|
||||
async fn get_remote_content(client: Arc<reqwest::Client>, input: &InputSource, headers: Option<&HeaderMap>, url: &Url) -> Result<(String, String), Error> {
|
||||
let start_time = Instant::now();
|
||||
let (mut stream, response_url) = get_remote_content_as_stream(client.clone(), url, input.method, Some(&input.headers)).await.map_err(|e| str_to_io_error(&format!("Failed to read content: {e}")))?;
|
||||
let custom_headers: Option<HashMap<String, Vec<u8>>> = headers.map(|h| h.iter().map(|(k, v)| (k.as_str().to_string(), v.as_bytes().to_vec())).collect());
|
||||
let headers = get_request_headers(Some(&input.headers), custom_headers.as_ref());
|
||||
let headers: HashMap<String, String> = headers.iter().map(|(k, v)| (k.as_str().to_string(), String::from_utf8_lossy(&*v.as_bytes().to_vec()).to_string())).collect();
|
||||
|
||||
let (mut stream, response_url) = get_remote_content_as_stream(client.clone(), url, input.method, Some(&headers)).await.map_err(|e| str_to_io_error(&format!("Failed to read content: {e}")))?;
|
||||
let mut content = String::new();
|
||||
stream.read_to_string(&mut content).await.map_err(|e| str_to_io_error(&format!("Failed to read content: {e}")))?;
|
||||
debug_if_enabled!("Request took:{} {}", format_elapsed_time(start_time.elapsed().as_secs()), sanitize_sensitive_info(url.as_str()));
|
||||
@@ -337,14 +340,14 @@ async fn download_epg_content_as_file(client: Arc<reqwest::Client>, input: &Conf
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn download_text_content(client: Arc<reqwest::Client>, input: &InputSource, persist_filepath: Option<PathBuf>) -> Result<(String, String), Error> {
|
||||
pub async fn download_text_content(client: Arc<reqwest::Client>, input: &InputSource, headers: Option<&HeaderMap>, persist_filepath: Option<PathBuf>) -> Result<(String, String), Error> {
|
||||
if let Ok(url) = input.url.parse::<url::Url>() {
|
||||
let result = if url.scheme() == "file" {
|
||||
url.to_file_path().map_or_else(|()| Err(str_to_io_error(&format!("Unknown file {}", sanitize_sensitive_info(&input.url)))), |file_path|
|
||||
get_local_file_content(&file_path).map(|c| (c, url.to_string())),
|
||||
)
|
||||
} else {
|
||||
get_remote_content(client, input, &url).await
|
||||
get_remote_content(client, input, headers, &url).await
|
||||
};
|
||||
match result {
|
||||
Ok((content, response_url)) => {
|
||||
@@ -360,9 +363,9 @@ pub async fn download_text_content(client: Arc<reqwest::Client>, input: &InputSo
|
||||
}
|
||||
}
|
||||
|
||||
async fn download_json_content(client: Arc<reqwest::Client>, input: &InputSource,persist_filepath: Option<PathBuf>) -> Result<serde_json::Value, Error> {
|
||||
async fn download_json_content(client: Arc<reqwest::Client>, input: &InputSource, persist_filepath: Option<PathBuf>) -> Result<serde_json::Value, Error> {
|
||||
debug_if_enabled!("downloading json content from {}", sanitize_sensitive_info(&input.url));
|
||||
match download_text_content(client, input, persist_filepath).await {
|
||||
match download_text_content(client, input, None, persist_filepath).await {
|
||||
Ok((content, _response_url)) => {
|
||||
match serde_json::from_str::<serde_json::Value>(&content) {
|
||||
Ok(value) => Ok(value),
|
||||
|
||||
@@ -46,7 +46,7 @@ pub fn get_xtream_player_api_info_url(input: &ConfigInput, cluster: XtreamCluste
|
||||
|
||||
|
||||
pub async fn get_xtream_stream_info_content(client: Arc<reqwest::Client>, input: &InputSource) -> Result<String, Error> {
|
||||
match request::download_text_content(client, input, None).await {
|
||||
match request::download_text_content(client, input, None, None).await {
|
||||
Ok((content, _response_url)) => Ok(content),
|
||||
Err(err) => Err(err)
|
||||
}
|
||||
|
||||
@@ -157,9 +157,9 @@ pub fn ProxyUserCredentialsForm(props: &ProxyUserCredentialsFormProps) -> Html {
|
||||
html! {
|
||||
<div class="tp__proxy-user-credentials-form tp__form-page">
|
||||
<div class="tp__proxy-user-credentials-form__body tp__form-page__body">
|
||||
{ if *update {
|
||||
{ /*if *update {
|
||||
config_field_custom!(translate.t("LABEL.PLAYLIST"), (*set_selected_target).as_ref().map_or_else(String::new, |t| t.clone()))
|
||||
} else { config_field_child!(translate.t("LABEL.PLAYLIST"), {
|
||||
} else {*/ config_field_child!(translate.t("LABEL.PLAYLIST"), {
|
||||
html! { <Select name="target"
|
||||
multi_select={false}
|
||||
onselect={Callback::from(move |(_name, selections):(String, Vec<Rc<DropDownOption>>)| {
|
||||
@@ -171,7 +171,7 @@ pub fn ProxyUserCredentialsForm(props: &ProxyUserCredentialsFormProps) -> Html {
|
||||
})}
|
||||
options={(*targets).clone()}
|
||||
/>
|
||||
}})}}
|
||||
}})} //}
|
||||
{ config_field_child!(translate.t("LABEL.STATUS"), {
|
||||
html! { <Select name="status"
|
||||
multi_select={false}
|
||||
|
||||
Reference in New Issue
Block a user