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
// LinkedIn (lnkd.in) Resolver
// LinkedIn is aggressive with anti-scraping and has two behaviors:
// 1. Direct HTTP redirect (most common)
// 2. Interstitial warning page with URL in HTML (when flagged/rate-limited)
// We try both approaches for robustness
use crate::resolvers::{from_url, generic};
use futures::future::{ready, TryFutureExt};
use std::time::Duration;
use crate::{Error, Result};
pub(crate) async fn unshort(url: &str, timeout: Option<Duration>) -> Result<String> {
//! Expands LinkedIn shortened URLs (lnkd.in).
//!
//! LinkedIn uses two different redirect mechanisms:
//! 1. Standard HTTP redirects (most common)
//! 2. Interstitial warning pages with JavaScript redirects (when flagged/rate-limited)
//!
//! This resolver tries both approaches for robustness.
//!
//! # Arguments
//!
//! * `url` - The LinkedIn shortened URL to expand
//! * `timeout` - Optional timeout for HTTP requests
//!
//! # Returns
//!
//! Returns `Ok(String)` with the final destination URL on success,
//! or `Err(Error)` if the URL cannot be expanded.
//!
//! # Behavior
//!
//! - First attempts standard HTTP redirect following
//! - If still on LinkedIn domain, parses the interstitial page
//! - Extracts the final URL from the warning page HTML
//! - Returns the expanded URL or falls back to the redirect result
// First try standard HTTP redirect (most common LinkedIn behavior)
let expanded_url = generic::unshort(url, timeout).await?;
// If we're still on LinkedIn domain, try parsing the interstitial page
Ok(
if expanded_url.contains("linkedin.com") || expanded_url.contains("lnkd.in") {
get_from_html(url, timeout).await.unwrap_or_else(|_| expanded_url)
} else {
expanded_url
},
)
}
async fn get_from_html(url: &str, timeout: Option<Duration>) -> Result<String> {
//! Extracts the final URL from LinkedIn's interstitial warning page.
//!
//! This function parses the HTML content of LinkedIn's warning page
//! to find the external link that the user intended to visit.
//!
//! # Arguments
//!
//! * `url` - The LinkedIn URL that may show an interstitial page
//! * `timeout` - Optional timeout for HTTP requests
//!
//! # Returns
//!
//! Returns `Ok(String)` with the extracted destination URL, or
//! `Err(Error)` if the URL cannot be extracted from the page.
//!
//! # Behavior
//!
//! - Fetches the HTML content of the URL
//! - Searches for the external URL link in the warning page
//! - Extracts the href attribute from the tracking link
from_url(url, timeout)
.and_then(|html| {
ready(
// Parse the interstitial warning page
html.split("data-tracking-control-name=\"external_url_click\"")
.nth(1)
.and_then(|r| r.split("href=\"").nth(1))
.and_then(|r| r.split("\">").next())
.map(|r| r.to_string())
.ok_or(Error::NoString),
)
})
.await
}