HEX
Server: Apache/2
System: Linux vps32496.sdns.vn 3.10.0-1160.99.1.el7.x86_64 #1 SMP Wed Sep 13 14:19:20 UTC 2023 x86_64
User: khuondaotc (1075)
PHP: 7.4.33
Disabled: exec,system,passthru,shell_exec,proc_close,proc_open,dl,popen,show_source,posix_kill,posix_mkfifo,posix_getpwuid,posix_setpgid,posix_setsid,posix_setuid,posix_setgid,posix_seteuid,posix_setegid,posix_uname
Upload Files
File: /home/khuondaotc/public_html/wp-content/plugins/unbounce/UBHTTP.php
<?php

class UBHTTP
{
    public static $form_confirmation_url_regex = '/(.+)\/[a-z]+-form_confirmation\.html/i';
    public static $lightbox_url_regex = '/(.+)\/[a-z]+-[0-9]+-lightbox\.html/i';
    public static $variant_url_regex = '/(.+)\/[a-z]+\.html/i';
    public static $pie_htc_url = '/PIE.htc';
    public static $location_header_regex = '/^(?:Location):/i';

    public static function is_public_ip_address($ip_address)
    {
        return filter_var(
            $ip_address,
            FILTER_VALIDATE_IP,
            FILTER_FLAG_NO_PRIV_RANGE + FILTER_FLAG_NO_RES_RANGE
        );
    }

    // Removes last public IP address from an array of IP addresses, along with any private IP
    // addresses that come after it, as long as there is at least one remaining public IP address.
    private static function remove_last_public_ip_address($ip_addresses)
    {
        $public_ip_address_indexes = array();
        foreach ($ip_addresses as $index => $ip_address) {
            if (UBHTTP::is_public_ip_address($ip_address)) {
                $public_ip_address_indexes[] = $index;
            }
        }

        if (count($public_ip_address_indexes) < 2) {
            return $ip_addresses;
        }

        return array_slice(
            $ip_addresses,
            0,
            $public_ip_address_indexes[count($public_ip_address_indexes) - 1]
        );
    }

    private static function get_last_public_ip_address($ip_addresses)
    {
        for ($i = count($ip_addresses) - 1; $i >= 0; $i--) {
            if (UBHTTP::is_public_ip_address($ip_addresses[$i])) {
                return $ip_addresses[$i];
            }
        }
        return null;
    }

    private static function split_header_values($values)
    {
        return array_filter(array_map('trim', explode(',', $values ?: '')));
    }

    public static function cookie_string_from_array($cookies)
    {
        $join_cookie_values = function ($k, $v) {
            return $k . '=' . $v;
        };
        $cookie_strings = array_map(
            $join_cookie_values,
            array_keys($cookies),
            $cookies
        );
        return join('; ', $cookie_strings);
    }

    public static function get_forwarded_headers(
        $domain,
        $current_protocol,
        $current_forwarded_for,
        $current_forwarded_proto,
        $current_remote_ip
    ) {
        // X-Forwarded-For: preserve existing values so Page Server etc can see the full chain and
        // choose whether to use the leftmost (closest to first client) or rightmost (trusted) IP.
        // If this plugin has been configured to trust a proxy in front of it with a public IP
        // address, remove the last public IP address from the right, so the new rightmost value
        // will become the rightmost one set by that proxy.
        $current_forwarded_for = UBHTTP::split_header_values($current_forwarded_for);
        $target_forwarded_for = $current_forwarded_for;
        // In case the last X-Forwarded-For value matches the remote IP address, avoid appending a
        // duplicate value. It seems that this shouldn't be possible but it was observed with
        // Bluehost.
        if (UBUtil::array_fetch($current_forwarded_for, count($current_forwarded_for) - 1) !== $current_remote_ip) {
            $target_forwarded_for = array_merge($current_forwarded_for, array($current_remote_ip));
        }
        if (UBConfig::allow_public_address_x_forwarded_for()) {
            $target_forwarded_for = UBHTTP::remove_last_public_ip_address($target_forwarded_for);
        }

        // X-Forwarded-Host: remove existing values so Page Server etc only see the configured
        // domain of this WordPress installation regardless of any proxies in front of it, because
        // this plugin should only be used to fetch content for that domain.
        $target_forwarded_host = $domain;

        // X-Forwarded-Proto: preserve existing values so Page Server etc can see entire chain and
        // likely choose to use the leftmost value (closest to first client).
        $target_forwarded_proto = ($current_forwarded_proto ? $current_forwarded_proto . ', ' : '') .
            $current_protocol;

        // X-Proxied-For: legacy header intended to contain a single trusted IP of the first client.
        $target_proxied_for = UBHTTP::get_last_public_ip_address($target_forwarded_for)
            ?: $target_forwarded_for[0];

        return array(
            'x-forwarded-for' => implode(', ', $target_forwarded_for),
            'x-forwarded-host' => $target_forwarded_host,
            'x-forwarded-proto' => $target_forwarded_proto,
            'x-proxied-for' => $target_proxied_for
        );
    }

    public static function stream_headers_function($dynamic_config)
    {
        $header_filter = UBHTTP::create_curl_response_header_filter($dynamic_config);

        return function ($curl, $header_line) use ($header_filter) {
            if ($header_filter($header_line)) {
                // false means don't replace the exsisting header
                header($header_line, false);
            }

            // We must show curl that we've processed every byte of the input header
            return strlen($header_line);
        };
    }

    public static function stream_response_function()
    {
        return function ($curl, $string) {
            // Stream the body to the client
            echo $string;

            // We must show curl that we've processed every byte of the input string
            return strlen($string);
        };
    }

    // Get protocol of current request from client to WordPress
    public static function get_current_protocol($server_global, $wp_is_ssl)
    {
        // Wordpress' is_ssl() may return the correct boolean for http/https if the site was set up
        // properly.
        $https = UBUtil::array_fetch($server_global, 'HTTPS', 'off');
        if ($wp_is_ssl || !is_null($https) && $https !== 'off') {
            return 'https';
        }

        // Next use REQUEST_SCHEME, if it is available. This is the recommended way to get the
        // protocol, but it is not available on all hosts.
        $request_scheme = UBUtil::array_fetch($server_global, 'REQUEST_SCHEME');
        if (UBHTTP::is_valid_protocol($request_scheme)) {
            return $request_scheme;
        }

        // Next try to pull it out of the SCRIPT_URI. This is also not always available.
        $script_uri = UBUtil::array_fetch($server_global, 'SCRIPT_URI');
        $script_uri_scheme = $script_uri !== null ? parse_url($script_uri, PHP_URL_SCHEME) : null;
        if (UBHTTP::is_valid_protocol($script_uri_scheme)) {
            return $script_uri_scheme;
        }

        // We default to http as most HTTPS sites will also have HTTP available.
        return 'http';
    }

    // Determine protocol to use for request from WordPress to Page Server
    public static function determine_protocol($server_global, $wp_is_ssl)
    {
        $forwarded_proto = UBUtil::array_fetch($server_global, 'HTTP_X_FORWARDED_PROTO');
        $first_valid_forwarded_proto = UBUtil::array_fetch(
            array_values(
                array_filter(
                    UBHTTP::split_header_values($forwarded_proto),
                    array('UBHTTP', 'is_valid_protocol')
                )
            ),
            0
        );

        $request_scheme = UBUtil::array_fetch($server_global, 'REQUEST_SCHEME');
        $script_uri = UBUtil::array_fetch($server_global, 'SCRIPT_URI');
        $script_uri_scheme = $script_uri !== null ? parse_url($script_uri, PHP_URL_SCHEME) : null;
        $https = UBUtil::array_fetch($server_global, 'HTTPS', 'off');

        UBLogger::debug_var('UBHTTP::forwarded_proto', $forwarded_proto);
        UBLogger::debug_var('UBHTTP::first_valid_forwarded_proto', $first_valid_forwarded_proto);
        UBLogger::debug_var('UBHTTP::request_scheme', $request_scheme);
        UBLogger::debug_var('UBHTTP::script_uri', $script_uri);
        UBLogger::debug_var('UBHTTP::script_uri_scheme', $script_uri_scheme);
        UBLogger::debug_var('UBHTTP::https', $https);

        // X-Forwarded-Proto should be respected first, as it is what the end
        // user will see (if Wordpress is behind a load balancer).
        if ($first_valid_forwarded_proto) {
            return $first_valid_forwarded_proto;
        }

        return UBHTTP::get_current_protocol($server_global, $wp_is_ssl);
    }

    private static function is_valid_protocol($protocol)
    {
        return $protocol === 'http' || $protocol === 'https';
    }

    // taken from: http://stackoverflow.com/a/13036310/322727
    public static function convert_headers_to_curl($headers)
    {
        // map to curl-friendly format
        $req_headers = array();
        array_walk($headers, function (&$v, $k) use (&$req_headers) {
            $req_headers[] = $k . ": " . $v;
        });

        return $req_headers;
    }

    public static function stream_request(
        $target_method,
        $target_url,
        $target_user_agent,
        $current_headers,
        $current_protocol,
        $domain
    ) {
        // Always add this header to responses to show it comes from our plugin.
        header("X-Unbounce-Plugin: 1", false);
        $dynamic_config = UBConfig::read_unbounce_dynamic_config($domain);

        if (UBConfig::use_curl()) {
            return UBHTTP::stream_request_curl(
                $target_method,
                $target_url,
                $target_user_agent,
                $current_headers,
                $current_protocol,
                $domain,
                $dynamic_config
            );
        } else {
            return UBHTTP::stream_request_wp_remote(
                $target_method,
                $target_url,
                $target_user_agent,
                $current_headers,
                $current_protocol,
                $domain,
                $dynamic_config
            );
        }
    }

    private static function stream_request_wp_remote(
        $target_method,
        $target_url,
        $target_user_agent,
        $current_headers,
        $current_protocol,
        $domain,
        $dynamic_config
    ) {
        $args = array(
            'method' => $target_method,
            'user-agent' => $target_user_agent,
            'redirection' => 0,
            'timeout' => 30,
            'headers' => array_merge(
                UBHTTP::prepare_request_headers($current_headers, $current_protocol, $domain, $dynamic_config),
                array(
                    'x-ub-wordpress-remote-request' => '1',
                    'accept-encoding' => null
                )
            )
        );
        if ($target_method == 'POST') {
            $args['body'] = file_get_contents('php://input');
        }

        $resp = wp_remote_request($target_url, $args);
        if (is_wp_error($resp)) {
            $message = "Error proxying to '" . $target_url . "': " . $resp->get_error_message();
            UBLogger::warning($message);
            http_response_code(500);
            return array(false, $message);
        } else {
            http_response_code($resp['response']['code']);
            $response_headers = $resp['headers'];
            UBHTTP::set_response_headers($response_headers, $dynamic_config);
            echo $resp['body'];
            return array(true, null);
        }
    }

    public static function prepare_request_headers($current_headers, $current_protocol, $domain, $dynamic_config)
    {
        
        $request_header_allow = UBUtil::array_fetch($dynamic_config, 'request_header_allow', UBConfig::UB_DEFAULT_REQUEST_HEADER_ALLOW);
        $request_header_add = UBUtil::array_fetch($dynamic_config, 'request_header_add', UBConfig::UB_DEFAULT_REQUEST_HEADER_ADD);
        $request_cookie_allow = UBUtil::array_fetch($dynamic_config, 'request_cookie_allow', UBConfig::UB_DEFAULT_REQUEST_COOKIE_ALLOW);

        $current_forwarded_for = UBUtil::array_fetch($current_headers, 'x-forwarded-for');
        $current_forwarded_proto = UBUtil::array_fetch($current_headers, 'x-forwarded-proto');
        $current_remote_ip = UBUtil::array_fetch($_SERVER, 'REMOTE_ADDR');

        // remove current host header as we will be setting it to the target domain
        unset($current_headers['host']);
    
        $merged_headers = array_merge(
            UBHTTP::sanitize_cookies($current_headers, $request_cookie_allow),
            UBHTTP::get_forwarded_headers(
                $domain,
                $current_protocol,
                $current_forwarded_for,
                $current_forwarded_proto,
                $current_remote_ip
            ),
            UBHTTP::get_common_headers()
        );
        
        $target_headers = array();
        array_walk($merged_headers, function ($v, $k) use (&$target_headers, $request_header_allow) {
            if (preg_match($request_header_allow, $k)) {
                $target_headers[$k] = $v;
            }
        });

        foreach ($request_header_add as $key => $value) {
            $target_headers[$key] = $value;
        }
        return $target_headers;
    }

    private static function get_common_headers()
    {
        $headers = array(
            'host' => UBConfig::page_server_domain(),
            'x-ub-wordpress-plugin-version' => '1.1.4'
        );

        try {
            // OS info:
            // - 's': Operating system name. eg. Linux
            // - 'r': Release name. eg. 5.4.39-linuxkit
            // - 'm': Machine type. eg. x86_64
            $os_info = implode(' ', array_map('php_uname', array('s', 'r', 'm')));
            $curl_version = curl_version();
            $headers = array_merge($headers, array(
                'x-ub-wordpress-wordpress-version' => UBDiagnostics::wordpress_version(),
                'x-ub-wordpress-php-version' => phpversion(),
                'x-ub-wordpress-curl-version' => $curl_version['version'],
                'x-ub-wordpress-ssl-version' => $curl_version['ssl_version'],
                'x-ub-wordpress-allow-public-addr-xff' => UBConfig::allow_public_address_x_forwarded_for() ? '1' : '0',
                'x-ub-wordpress-os' => $os_info,
            ));
        } catch (Throwable $e) {
            UBLogger::warning('Failed to build diagnostic headers: ' . $e);
        }

        try {
            $headers['x-ub-wordpress-sni-support'] = UBDiagnostics::has_sni() ? '1' : '0';
        } catch (Throwable $e) {
            UBLogger::warning('Failed to build SNI diagnostic header: ' . $e);
        }

        return $headers;
    }

    public static function sanitize_cookies($headers, $request_cookie_allow)
    {
        $cookie_key = "Cookie";
        if (!array_key_exists($cookie_key, $headers)) {
            $cookie_key = "cookie"; // fallback to trying lowercase
            if (!array_key_exists($cookie_key, $headers)) {
                return $headers;
            }
        }

        $cookies_to_forward = UBUtil::array_select_by_key(
            UBHTTP::cookie_array_from_string($headers[$cookie_key]),
            $request_cookie_allow
        );

        $headers[$cookie_key] = UBHTTP::cookie_string_from_array($cookies_to_forward);
        return $headers;
    }

    public static function cookie_array_from_string($cookie_string)
    {
        $cookie_kv_array = array();
        $cookie_flat_array = explode('; ', $cookie_string);
        foreach ($cookie_flat_array as $itm) {
            list($key, $val) = explode('=', $itm, 2);
            $cookie_kv_array[$key] = $val;
        }
        return $cookie_kv_array;
    }

    public static function set_response_headers($headers, $dynamic_config)
    {
        $header_filter = UBHTTP::create_response_header_filter($dynamic_config);

        foreach ($headers as $h_key => $h_value) {
            if ($header_filter($h_key)) {
                if (is_array($h_value)) {
                    foreach ($h_value as $header_item) {
                        header($h_key . ': ' . $header_item, false);
                    }
                } else {
                    header($h_key . ': ' . $h_value, false);
                }
            }
        }
    }

    private static function stream_request_curl(
        $target_method,
        $target_url,
        $target_user_agent,
        $current_headers,
        $current_protocol,
        $domain,
        $dynamic_config
    ) {
        $base_response_headers = headers_list();

        $target_headers = UBHTTP::prepare_request_headers($current_headers, $current_protocol, $domain, $dynamic_config);
        $target_headers = UBHTTP::convert_headers_to_curl($target_headers);

        UBLogger::debug_var('target_url', $target_url);
        UBLogger::debug_var('current_headers', print_r($current_headers, true));
        UBLogger::debug_var('target_headers', print_r($target_headers, true));

        $stream_headers = UBHTTP::stream_headers_function($dynamic_config);
        $stream_body = UBHTTP::stream_response_function();
        $curl = curl_init();
        // http://php.net/manual/en/function.curl-setopt.php
        $curl_options = array(
        CURLOPT_URL => $target_url,
        CURLOPT_POST => $target_method == "POST",
        CURLOPT_CUSTOMREQUEST => $target_method,
        CURLOPT_USERAGENT => $target_user_agent,
        CURLOPT_HTTPHEADER => $target_headers,
        CURLOPT_HEADERFUNCTION => $stream_headers,
        CURLOPT_WRITEFUNCTION => $stream_body,
        CURLOPT_FOLLOWLOCATION => false,
        CURLOPT_TIMEOUT => 30
        );

        if ($target_method == "POST") {
            // Use raw post body to allow the same post key to occur more than once
            $curl_options[CURLOPT_POSTFIELDS] = file_get_contents('php://input');
        }

        curl_setopt_array($curl, $curl_options);
        $resp = curl_exec($curl);
        if (!$resp) {
            $message = "Error proxying to '" . $target_url . "': " . curl_error($curl) . " - Code: " . curl_errno($curl);
            UBLogger::warning($message);
            if (UBHTTP::is_location_response_header_set()) {
                UBLogger::debug("The location header was set despite the cURL error. Assuming it's safe to let the response flow back");
                $result = array(true, null);
            } else {
                http_response_code(500);
                $result = array(false, $message);
            }
        } else {
            $http_status_code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
            http_response_code($http_status_code);
            $result = array(true, null);
        }

        curl_close($curl);

        return $result;
    }

    private static function is_location_response_header_set()
    {
        $resp_headers = headers_list();
        foreach ($resp_headers as $value) { //headers at this point are raw strings, not K -> V
            if (preg_match(UBHTTP::$location_header_regex, $value)) {
                return true;
            }
        }
        return false;
    }

    public static function is_extract_url_proxyable(
        $proxyable_url_set,
        $extract_regex,
        $match_position,
        $url
    ) {
        $matches = array();
        $does_match = preg_match(
            $extract_regex,
            $url,
            $matches
        );

        return $does_match && in_array($matches[1], $proxyable_url_set);
    }

    public static function is_confirmation_dialog($proxyable_url_set, $url_without_protocol)
    {
        return UBHTTP::is_extract_url_proxyable(
            $proxyable_url_set,
            UBHTTP::$form_confirmation_url_regex,
            1,
            $url_without_protocol
        );
    }

    public static function is_lightbox($proxyable_url_set, $url_without_protocol)
    {
        return UBHTTP::is_extract_url_proxyable(
            $proxyable_url_set,
            UBHTTP::$lightbox_url_regex,
            1,
            $url_without_protocol
        );
    }

    public static function is_variant($proxyable_url_set, $url_without_protocol)
    {
        return UBHTTP::is_extract_url_proxyable(
            $proxyable_url_set,
            UBHTTP::$variant_url_regex,
            1,
            $url_without_protocol
        );
    }

    public static function is_tracking_link($proxyable_url_set, $url_without_protocol)
    {
        return UBHTTP::is_extract_url_proxyable(
            $proxyable_url_set,
            "/^(.+)?\/(clkn|clkg)\/?/",
            1,
            $url_without_protocol
        );
    }

    public static function get_url_purpose($proxyable_url_set, $http_method, $url)
    {
        $host = parse_url($url, PHP_URL_HOST);
        $path = rtrim(parse_url($url, PHP_URL_PATH), '/');
        $url_without_protocol = $host . $path;

        UBLogger::debug_var('get_url_purpose $host', $host);
        UBLogger::debug_var('get_url_purpose $path', $path);
        UBLogger::debug_var('get_url_purpose $url_without_protocol', $url_without_protocol);

        if ($http_method == 'GET' && $path == '/_ubhc') {
            return 'HealthCheck';
        } elseif (preg_match("/^\/_ub\/[\w.-]/", $path)) {
            return "GenericProxyableRequest";
        } elseif ($http_method == "POST" &&
        preg_match("/^\/(fsn|fsg|fs)\/?$/", $path)) {
            return "SubmitLead";
        } elseif ($http_method == "GET" &&
              UBHTTP::is_tracking_link($proxyable_url_set, $url_without_protocol)) {
            return "TrackClick";
        } elseif (($http_method == "GET" || $http_method == "POST") &&
               (in_array($url_without_protocol, $proxyable_url_set) ||
                UBHTTP::is_confirmation_dialog($proxyable_url_set, $url_without_protocol) ||
                UBHTTP::is_lightbox($proxyable_url_set, $url_without_protocol) ||
                UBHTTP::is_variant($proxyable_url_set, $url_without_protocol))) {
            return "ViewLandingPage";
        } elseif ($http_method == "GET" && $path == UBHTTP::$pie_htc_url) {
            // proxy PIE.htc
            return "ViewLandingPage";
        } else {
            return null;
        }
    }

    private static function create_curl_response_header_filter($dynamic_config)
    {
        $blocklist_regex = '/^connection:/i';
        $config_headers_forwarded = UBConfig::response_headers_forwarded();

        if ($config_headers_forwarded === array('*')) {
            return function ($header) use ($blocklist_regex) {
                return !preg_match($blocklist_regex, $header);
            };
        }

        $allowlist = array_merge($config_headers_forwarded, UBUtil::array_fetch($dynamic_config, 'response_header_allow', array()));
        $allowlist_regex = '/^('.implode('|', $allowlist).'):/i';
        return function ($header) use ($blocklist_regex, $allowlist_regex) {
            return preg_match($allowlist_regex, $header) && !preg_match($blocklist_regex, $header);
        };
    }

    private static function create_response_header_filter($dynamic_config)
    {
        $config_headers_forwarded = UBConfig::response_headers_forwarded();

        if ($config_headers_forwarded === array('*')) {
            return function ($header) {
                return strcasecmp($header, 'connection') !== 0;
            };
        }

        $allowlist = array_merge($config_headers_forwarded, UBUtil::array_fetch($dynamic_config, 'response_header_allow', array()));

        return function ($header) use ($allowlist) {
            // headers in the allow list are lowercase
            $header = strtolower($header);
            return $header !== 'connection' && in_array($header, $allowlist);
        };
    }
}