10,
'redirection' => 5,
'user-agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36',
'cookies' => array(),
)
);
$code = is_wp_error( $html_response ) ? 0 : (int) wp_remote_retrieve_response_code( $html_response );
if ( 429 === $code || 403 === $code ) {
self::record_failure();
return $url;
}
if ( ! is_wp_error( $html_response ) && 200 === $code ) {
$html = wp_remote_retrieve_body( $html_response );
$parsed_url = self::parse_redirect_from_html( $html );
if ( $parsed_url && ! self::is_google_domain( $parsed_url ) ) {
self::reset_circuit_breaker();
return $parsed_url;
}
// Tier 3: Google batchexecute RPC (Fbv4je) resolution for CBMi URLs
$rpc_url = self::resolve_via_batchexecute( $url, $html );
if ( $rpc_url && ! self::is_google_domain( $rpc_url ) ) {
self::reset_circuit_breaker();
return $rpc_url;
}
}
return $url;
}
/**
* Attempt resolution via HTTP HEAD Location header.
*
* @param string $url Target URL.
* @return string|null Resolved URL or null.
*/
private static function resolve_via_http_header( string $url ): ?string {
$response = wp_remote_head(
$url,
array(
'timeout' => 8,
'redirection' => 5,
'user-agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36',
)
);
if ( is_wp_error( $response ) ) {
return null;
}
$code = (int) wp_remote_retrieve_response_code( $response );
if ( 429 === $code || 403 === $code ) {
self::record_failure();
return null;
}
$final_url = wp_remote_retrieve_header( $response, 'location' );
if ( empty( $final_url ) && isset( $response['http_response'] ) && method_exists( $response['http_response'], 'get_response_object' ) ) {
$raw = $response['http_response']->get_response_object();
if ( isset( $raw->url ) ) {
$final_url = (string) $raw->url;
}
}
if ( empty( $final_url ) || ! is_string( $final_url ) ) {
return null;
}
// Allow cross-domain redirect if original host is an aggregator.
$original_host = wp_parse_url( $url, PHP_URL_HOST );
$final_host = wp_parse_url( $final_url, PHP_URL_HOST );
if (
$original_host !== $final_host &&
in_array( preg_replace( '/^www\./', '', (string) $original_host ), self::AGGREGATOR_DOMAINS, true )
) {
return $final_url;
}
return $final_url;
}
/**
* Parse redirect destination from Google consent/interstitial HTML.
*
* @param string $html Page body.
* @return string|null Parsed destination URL.
*/
private static function parse_redirect_from_html( string $html ): ?string {
if ( preg_match( '/]+http-equiv=["\']?refresh["\']?[^>]+content=["\'][0-9]+;\s*url=([^"\']+)["\']/i', $html, $matches ) ) {
return esc_url_raw( trim( $matches[1] ) );
}
if ( preg_match( '/window\.location\.(?:replace|href)\s*=\s*[\'"](https?:\/\/[^\'"]+)[\'"]/i', $html, $matches ) ) {
return esc_url_raw( trim( $matches[1] ) );
}
if ( preg_match( '/data-n-au=["\'](https?:\/\/(?!news\.google\.com|www\.google\.com)[^"\']+)["\']/i', $html, $matches ) ) {
return esc_url_raw( trim( $matches[1] ) );
}
if ( preg_match( '/]+href=["\'](https?:\/\/(?!news\.google\.com|www\.google\.com|support\.google\.com|accounts\.google\.com)[^"\']+)["\']/i', $html, $matches ) ) {
return esc_url_raw( trim( $matches[1] ) );
}
return null;
}
/**
* Invokes Google batchexecute RPC (Fbv4je) to decode CBMi URLs.
*
* @param string $url Raw Google URL.
* @param string $html Page body for signature extraction.
* @return string|null
*/
private static function resolve_via_batchexecute( string $url, string $html ): ?string {
if ( ! preg_match( '/\/articles\/([a-zA-Z0-9_-]+)/', $url, $id_match ) ) {
return null;
}
$gn_art_id = $id_match[1];
$sig = '';
$ts = '';
if ( preg_match( '/data-n-a-sg=["\']([^"\']+)["\']/', $html, $m_sig ) ) {
$sig = $m_sig[1];
}
if ( preg_match( '/data-n-a-ts=["\']([^"\']+)["\']/', $html, $m_ts ) ) {
$ts = $m_ts[1];
}
$rpc_payload = wp_json_encode(
array(
array(
array( 'Fbv4je', wp_json_encode( array( $gn_art_id, $sig, $ts ) ), null, 'generic' ),
),
)
);
$response = wp_remote_post(
'https://news.google.com/_/DotsSplashUi/data/batchexecute?rpcids=Fbv4je',
array(
'timeout' => 8,
'headers' => array(
'Content-Type' => 'application/x-www-form-urlencoded;charset=utf-8',
'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36',
'Referer' => 'https://news.google.com/',
),
'body' => 'f.req=' . urlencode( (string) $rpc_payload ),
)
);
if ( is_wp_error( $response ) || 200 !== (int) wp_remote_retrieve_response_code( $response ) ) {
return null;
}
$body = wp_remote_retrieve_body( $response );
$body = preg_replace( '/^\)]\}\'\s*/', '', $body );
$data = json_decode( (string) $body, true );
$resolved_url = null;
if ( is_array( $data ) ) {
array_walk_recursive(
$data,
function ( $item ) use ( &$resolved_url ) {
if ( is_string( $item ) && preg_match( '/^https?:\/\/(?!news\.google\.com|www\.google\.com)/i', $item ) ) {
$resolved_url = $item;
}
}
);
if ( ! empty( $resolved_url ) ) {
return esc_url_raw( (string) $resolved_url );
}
}
return null;
}
/**
* Record an HTTP 429/403 failure and trigger circuit breaker if threshold exceeded.
*/
private static function record_failure(): void {
$fails = (int) get_option( self::FAIL_COUNT_OPTION, 0 ) + 1;
update_option( self::FAIL_COUNT_OPTION, $fails );
if ( $fails >= self::CIRCUIT_BREAKER_MAX ) {
set_transient( self::BACKOFF_TRANSIENT, 1, self::BACKOFF_DURATION );
Peptide_News_Logger::warning( 'Google URL Resolver hit ' . $fails . ' failures. Circuit breaker tripped for 30m.', 'fetcher' );
}
}
/**
* Reset failure counter upon successful resolution.
*/
private static function reset_circuit_breaker(): void {
if ( (int) get_option( self::FAIL_COUNT_OPTION, 0 ) > 0 ) {
update_option( self::FAIL_COUNT_OPTION, 0 );
}
delete_transient( self::BACKOFF_TRANSIENT );
}
/**
* Check if URL host is a known aggregator.
*
* @param string $url URL string.
* @return bool
*/
public static function is_aggregator_url( string $url ): bool {
$host = wp_parse_url( $url, PHP_URL_HOST );
return $host && in_array( preg_replace( '/^www\./', '', (string) $host ), self::AGGREGATOR_DOMAINS, true );
}
/**
* Check if URL points to a Google domain.
*
* @param string $url URL string.
* @return bool
*/
public static function is_google_domain( string $url ): bool {
$host = wp_parse_url( $url, PHP_URL_HOST );
return $host && ( strpos( (string) $host, 'google.com' ) !== false || strpos( (string) $host, 'googleusercontent.com' ) !== false );
}
}