1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
// Generic Resolver
use std::time::Duration;
use super::{custom_redirect_policy, get_client_builder};
use futures::future::{ready, TryFutureExt};
use crate::Result;
pub(crate) async fn unshort(url: &str, timeout: Option<Duration>) -> Result<String> {
//! Expands URLs using standard HTTP redirect following.
//!
//! This resolver handles the majority of URL shorteners that rely on
//! standard HTTP 3xx redirects. It follows redirect chains while applying
//! a custom redirect policy to prevent infinite loops and cross-domain
//! redirects that might be malicious.
//!
//! # Arguments
//!
//! * `url` - The shortened URL to expand
//! * `timeout` - Optional timeout for HTTP requests
//!
//! # Returns
//!
//! Returns `Ok(String)` with the final destination URL after following
//! redirects, or `Err(Error)` if the expansion fails.
//!
//! # Behavior
//!
//! - Uses a custom redirect policy that stops when domains change
//! - Follows standard HTTP 3xx redirects automatically
//! - Returns the final URL from the last response
//! - Handles network errors and timeouts appropriately
let custom = custom_redirect_policy();
ready(get_client_builder(timeout).redirect(custom).build())
.and_then(|client| async move { client.get(url).send().await })
.map_ok(|response| response.url().as_str().into())
.err_into()
.await
}
pub(crate) async fn unshort_with_browser_headers(url: &str, timeout: Option<Duration>) -> Result<String> {
//! Expands URLs using browser-like headers and full redirect following.
//!
//! This function mimics a browser request with proper headers to ensure
//! that services like 2cm.es return proper redirects.
//!
//! # Arguments
//!
//! * `url` - The shortened URL to expand
//! * `timeout` - Optional timeout for HTTP requests
//!
//! # Returns
//!
//! Returns `Ok(String)` with the final destination URL after following
//! all redirects, or `Err(Error)` if the expansion fails.
//!
//! # Behavior
//!
//! - Uses browser-like headers (Accept, Accept-Language, etc.)
//! - Uses default redirect policy (allows cross-domain redirects)
//! - Follows standard HTTP 3xx redirects automatically
ready(get_client_builder(timeout).build())
.and_then(|client| async move {
client
.get(url)
.header("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8")
.header("Accept-Language", "en-US,en;q=0.5")
.header("Accept-Encoding", "gzip, deflate, br")
.header("DNT", "1")
.header("Connection", "keep-alive")
.header("Upgrade-Insecure-Requests", "1")
.send()
.await
})
.map_ok(|response| response.url().as_str().into())
.err_into()
.await
}
pub(crate) async fn unshort_with_curl_ua(url: &str, timeout: Option<Duration>) -> Result<String> {
//! Expands URLs using curl user agent and full redirect following.
//!
//! This function uses curl's user agent string to ensure that services
//! like t.co return proper HTTP redirects instead of HTML pages.
//!
//! # Arguments
//!
//! * `url` - The shortened URL to expand
//! * `timeout` - Optional timeout for HTTP requests
//!
//! # Returns
//!
//! Returns `Ok(String)` with the final destination URL after following
//! all redirects, or `Err(Error)` if the expansion fails.
//!
//! # Behavior
//!
//! - Uses custom user agent string
//! - Uses default redirect policy (allows cross-domain redirects)
//! - Follows standard HTTP 3xx redirects automatically
ready(get_client_builder(timeout)
.user_agent("URLEXPANDER/0.3")
.build())
.and_then(|client| async move { client.get(url).send().await })
.map_ok(|response| response.url().as_str().into())
.err_into()
.await
}