Skip to content

Commit

Permalink
Merge pull request #528 from Stremio/feature/request-headers-stream-p…
Browse files Browse the repository at this point in the history
…roxying

Resolve stream proxied streaming link when url has request headers
  • Loading branch information
elpiel authored Oct 12, 2023
2 parents 97bd700 + 696c22d commit b6cc8c8
Show file tree
Hide file tree
Showing 3 changed files with 191 additions and 8 deletions.
4 changes: 2 additions & 2 deletions src/deep_links/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ impl From<(&LibraryItem, Option<&StreamsItem>, Option<&Url>, &Settings)> for Lib
utf8_percent_encode(video_id, URI_COMPONENT_ENCODE_SET)
)
}),
// We have the steam so use the same logic as in StreamDeepLinks
// We have the stream so use the same logic as in StreamDeepLinks
player: streams_item.map(|streams_item| match streams_item.stream.encode() {
Ok(encoded_stream) => format!(
"stremio:///player/{}/{}/{}/{}/{}/{}",
Expand All @@ -204,7 +204,7 @@ impl From<(&LibraryItem, Option<&StreamsItem>, Option<&Url>, &Settings)> for Lib
),
Err(error) => ErrorLink::from(error).into(),
}),
// We have the steams bucket item so use the same logic as in StreamDeepLinks
// We have the streams bucket item so use the same logic as in StreamDeepLinks
external_player: streams_item.map(|item| {
ExternalPlayerLink::from((&item.stream, streaming_server_url, settings))
}),
Expand Down
43 changes: 38 additions & 5 deletions src/types/resource/stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use serde_with::{serde_as, DefaultOnNull};
use std::collections::HashMap;
use std::io::Write;
use stremio_serde_hex::{SerHex, Strict};
use url::Url;
use url::{form_urlencoded, Url};

#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
Expand Down Expand Up @@ -136,7 +136,38 @@ impl Stream {
}
pub fn streaming_url(&self, streaming_server_url: Option<&Url>) -> Option<String> {
match (&self.source, streaming_server_url) {
(StreamSource::Url { url }, _) if url.scheme() != "magnet" => Some(url.to_string()),
(StreamSource::Url { url }, _) if url.scheme() != "magnet" => {
match (streaming_server_url, &self.behavior_hints.proxy_headers) {
(
Some(streaming_server_url),
Some(StreamProxyHeaders { request, response }),
) => {
let mut streaming_url = streaming_server_url.to_owned();
let mut proxy_query = form_urlencoded::Serializer::new(String::new());
let origin = format!("{}://{}", url.scheme(), url.authority());
proxy_query.append_pair("d", origin.as_str());
proxy_query.extend_pairs(
request
.iter()
.map(|header| ("h", format!("{}:{}", header.0, header.1))),
);
proxy_query.extend_pairs(
response
.iter()
.map(|header| ("r", format!("{}:{}", header.0, header.1))),
);
streaming_url
.path_segments_mut()
.ok()?
.push("proxy")
.push(proxy_query.finish().as_str())
.push(&url.path()[1..]);
streaming_url.set_query(url.query());
Some(streaming_url.to_string())
}
_ => Some(url.to_string()),
}
}
(
StreamSource::Torrent {
info_hash,
Expand All @@ -149,9 +180,11 @@ impl Stream {
match url.path_segments_mut() {
Ok(mut path) => {
path.push(&hex::encode(info_hash));
if let Some(file_idx) = file_idx {
path.push(&file_idx.to_string());
}
// When fileIndex is not provided use -1, which will tell the
// streaming server to choose the file with the largest size from the torrent
path.push(
&file_idx.map_or_else(|| "-1".to_string(), |idx| idx.to_string()),
);
}
_ => return None,
};
Expand Down
152 changes: 151 additions & 1 deletion src/unit_tests/deep_links/stream_deep_links.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@ use crate::constants::{BASE64, URI_COMPONENT_ENCODE_SET};
use crate::deep_links::StreamDeepLinks;
use crate::types::addon::{ResourcePath, ResourceRequest};
use crate::types::profile::Settings;
use crate::types::resource::{Stream, StreamSource};
use crate::types::resource::{Stream, StreamBehaviorHints, StreamProxyHeaders, StreamSource};
use base64::Engine;
use percent_encoding::utf8_percent_encode;
use std::collections::HashMap;
use std::convert::TryFrom;
use std::str::FromStr;
use url::Url;

const MAGNET_STR_URL: &str = "magnet:?xt=urn:btih:dd8255ecdc7ca55fb0bbf81323d87062db1f6d1c";
const HTTP_STR_URL: &str = "http://domain.root/path";
const HTTP_WITH_QUERY_STR_URL: &str = "http://domain.root/path?param=some&foo=bar";
const BASE64_HTTP_URL: &str = "data:application/octet-stream;charset=utf-8;base64,I0VYVE0zVQojRVhUSU5GOjAKaHR0cDovL2RvbWFpbi5yb290L3BhdGg=";
const STREAMING_SERVER_URL: &str = "http://127.0.0.1:11471";
const YT_ID: &str = "aqz-KE-bpKQ";
Expand Down Expand Up @@ -56,12 +58,86 @@ fn stream_deep_links_http() {
.to_string()
);
assert_eq!(sdl.external_player.href, Some(BASE64_HTTP_URL.to_owned()));
assert_eq!(sdl.external_player.streaming, Some(HTTP_STR_URL.to_owned()));
assert_eq!(
sdl.external_player.file_name,
Some("playlist.m3u".to_string())
);
}

#[test]
fn stream_deep_links_http_with_request_headers() {
let stream = Stream {
source: StreamSource::Url {
url: Url::from_str(HTTP_STR_URL).unwrap(),
},
name: None,
description: None,
thumbnail: None,
subtitles: vec![],
behavior_hints: StreamBehaviorHints {
not_web_ready: false,
binge_group: None,
country_whitelist: None,
proxy_headers: Some(StreamProxyHeaders {
request: HashMap::from([("Authorization".to_string(), "my+token".to_string())]),
response: Default::default(),
}),
other: Default::default(),
},
};
let streaming_server_url = Some(Url::parse(STREAMING_SERVER_URL).unwrap());
let settings = Settings::default();
let sdl = StreamDeepLinks::try_from((&stream, &streaming_server_url, &settings)).unwrap();
assert_eq!(sdl.player, "stremio:///player/eAEBawCU%2F3sidXJsIjoiaHR0cDovL2RvbWFpbi5yb290L3BhdGgiLCJiZWhhdmlvckhpbnRzIjp7InByb3h5SGVhZGVycyI6eyJyZXF1ZXN0Ijp7IkF1dGhvcml6YXRpb24iOiJteSt0b2tlbiJ9fX19DNkm%2FA%3D%3D".to_string());
assert_eq!(
sdl.external_player.streaming,
Some(format!(
"{}/proxy/{}",
STREAMING_SERVER_URL,
"d=http%253A%252F%252Fdomain.root&h=Authorization%253Amy%252Btoken/path"
))
);
}

#[test]
fn stream_deep_links_http_with_request_response_headers_and_query_params() {
let stream = Stream {
source: StreamSource::Url {
url: Url::from_str(HTTP_WITH_QUERY_STR_URL).unwrap(),
},
name: None,
description: None,
thumbnail: None,
subtitles: vec![],
behavior_hints: StreamBehaviorHints {
not_web_ready: false,
binge_group: None,
country_whitelist: None,
proxy_headers: Some(StreamProxyHeaders {
request: HashMap::from([("Authorization".to_string(), "my+token".to_string())]),
response: HashMap::from([(
"Content-Type".to_string(),
"application/xml".to_string(),
)]),
}),
other: Default::default(),
},
};
let streaming_server_url = Some(Url::parse(STREAMING_SERVER_URL).unwrap());
let settings = Settings::default();
let sdl = StreamDeepLinks::try_from((&stream, &streaming_server_url, &settings)).unwrap();
assert_eq!(sdl.player, "stremio:///player/eAEBrABT%2F3sidXJsIjoiaHR0cDovL2RvbWFpbi5yb290L3BhdGg%2FcGFyYW09c29tZSZmb289YmFyIiwiYmVoYXZpb3JIaW50cyI6eyJwcm94eUhlYWRlcnMiOnsicmVxdWVzdCI6eyJBdXRob3JpemF0aW9uIjoibXkrdG9rZW4ifSwicmVzcG9uc2UiOnsiQ29udGVudC1UeXBlIjoiYXBwbGljYXRpb24veG1sIn19fX322z6q".to_string());
assert_eq!(
sdl.external_player.streaming,
Some(format!(
"{}/proxy/{}",
STREAMING_SERVER_URL,
"d=http%253A%252F%252Fdomain.root&h=Authorization%253Amy%252Btoken&r=Content-Type%253Aapplication%252Fxml/path?param=some&foo=bar"
))
);
}

#[test]
fn stream_deep_links_torrent() {
let info_hash = [
Expand Down Expand Up @@ -105,6 +181,80 @@ fn stream_deep_links_torrent() {
))
))
);
assert_eq!(
sdl.external_player.streaming,
Some(format!(
"{}/{}/{}?tr={}",
STREAMING_SERVER_URL,
hex::encode(info_hash),
file_idx,
utf8_percent_encode(
"http://bt1.archive.org:6969/announce",
URI_COMPONENT_ENCODE_SET
),
))
);
assert_eq!(
sdl.external_player.file_name,
Some("playlist.m3u".to_string())
);
}

#[test]
fn stream_deep_links_torrent_without_file_index() {
let info_hash = [
0xdd, 0x82, 0x55, 0xec, 0xdc, 0x7c, 0xa5, 0x5f, 0xb0, 0xbb, 0xf8, 0x13, 0x23, 0xd8, 0x70,
0x62, 0xdb, 0x1f, 0x6d, 0x1c,
];
let announce = vec!["http://bt1.archive.org:6969/announce".to_string()];
let stream = Stream {
source: StreamSource::Torrent {
info_hash,
file_idx: None,
announce,
},
name: None,
description: None,
thumbnail: None,
subtitles: vec![],
behavior_hints: Default::default(),
};
let streaming_server_url = Some(Url::parse(STREAMING_SERVER_URL).unwrap());
let settings = Settings::default();
let sdl = StreamDeepLinks::try_from((&stream, &streaming_server_url, &settings)).unwrap();
assert_eq!(sdl.player, "stremio:///player/eAEBegCF%2F3siaW5mb0hhc2giOiJkZDgyNTVlY2RjN2NhNTVmYjBiYmY4MTMyM2Q4NzA2MmRiMWY2ZDFjIiwiZmlsZUlkeCI6bnVsbCwiYW5ub3VuY2UiOlsiaHR0cDovL2J0MS5hcmNoaXZlLm9yZzo2OTY5L2Fubm91bmNlIl19LmMnPg%3D%3D".to_string());
assert_eq!(
sdl.external_player.href,
Some(format!(
"data:application/octet-stream;charset=utf-8;base64,{}",
BASE64.encode(format!(
"#EXTM3U\n#EXTINF:0\n{}",
format_args!(
"{}/{}/{}?tr={}",
STREAMING_SERVER_URL,
hex::encode(info_hash),
-1,
utf8_percent_encode(
"http://bt1.archive.org:6969/announce",
URI_COMPONENT_ENCODE_SET
),
)
))
))
);
assert_eq!(
sdl.external_player.streaming,
Some(format!(
"{}/{}/{}?tr={}",
STREAMING_SERVER_URL,
hex::encode(info_hash),
-1,
utf8_percent_encode(
"http://bt1.archive.org:6969/announce",
URI_COMPONENT_ENCODE_SET
),
))
);
assert_eq!(
sdl.external_player.file_name,
Some("playlist.m3u".to_string())
Expand Down

0 comments on commit b6cc8c8

Please sign in to comment.