| @@ -27,8 +27,9 @@ | ||
| 27 | 27 | * @since 3.5 |
| 28 | 28 | */ |
| 29 | 29 | class c_ws_plugin__s2member_paypal_notify_in |
| 30 | 30 | { |
| 31 | + //260907.2110 TO-DO: Before final PayPal Standard deprecation, review IPN/return replay protection against the shared fulfillment/dedupe invariants; provider-hosted button creation itself does not need Gateway Checkout creation orchestration. | |
| 31 | 32 | /** |
| 32 | 33 | * Handles PayPal IPN processing. |
| 33 | 34 | * |
| 34 | 35 | * These same routines also handle s2Member Pro/PayPal Pro operations; |
| @@ -51,8 +52,14 @@ | ||
| 51 | 52 | global $current_site, $current_blog; |
| 52 | 53 | |
| 53 | 54 | do_action('ws_plugin__s2member_before_paypal_notify', get_defined_vars()); |
| 54 | 55 | |
| 56 | + //260831.0135 Normalize optional s2Member proxy fields once; direct PayPal IPNs omit them, while proxied gateway integrations provide strings. | |
| 57 | + if(!isset($_REQUEST['s2member_paypal_proxy']) || !is_string($_REQUEST['s2member_paypal_proxy'])) | |
| 58 | + $_REQUEST['s2member_paypal_proxy'] = ''; | |
| 59 | + if(!isset($_REQUEST['s2member_paypal_proxy_use']) || !is_string($_REQUEST['s2member_paypal_proxy_use'])) | |
| 60 | + $_REQUEST['s2member_paypal_proxy_use'] = ''; | |
| 61 | + | |
| 55 | 62 | if(!empty($_GET['s2member_paypal_notify']) && ($GLOBALS['WS_PLUGIN__']['s2member']['o']['paypal_business'] || !empty($_REQUEST['s2member_paypal_proxy']))) |
| 56 | 63 | { |
| 57 | 64 | @ignore_user_abort(TRUE); // Important. Continue processing even if/when the connection is broken by the sending party. |
| 58 | 65 | |
| @@ -75,8 +82,9 @@ | ||
| 75 | 82 | $paypal['subscr_gateway'] = (!empty($_REQUEST['s2member_paypal_proxy'])) ? esc_html(trim(stripslashes($_REQUEST['s2member_paypal_proxy']))) : 'paypal'; |
| 76 | 83 | |
| 77 | 84 | $coupon = (!empty($_REQUEST['s2member_paypal_proxy_coupon']) && is_array($_REQUEST['s2member_paypal_proxy_coupon'])) ? stripslashes_deep($_REQUEST['s2member_paypal_proxy_coupon']) : array(); |
| 78 | 85 | $coupon = (isset($coupon['full_coupon_code'], $coupon['coupon_code'], $coupon['affiliate_id']) && is_string($coupon['full_coupon_code']) && is_string($coupon['coupon_code']) && is_string($coupon['affiliate_id'])) ? $coupon : array('full_coupon_code' => '', 'coupon_code' => '', 'affiliate_id' => ''); |
| 86 | + $coupon = c_ws_plugin__s2member_utils_strings::strip_php_tags_deep($coupon); //260910.2249 Normalize proxy coupon data consistently with other notification values. | |
| 79 | 87 | |
| 80 | 88 | if(!empty($paypal['txn_type']) && $paypal['txn_type'] === 'merch_pmt') |
| 81 | 89 | // This is mostly irrelevant, but it helps to keep the logs cleaner. |
| 82 | 90 | sleep(15); // Wait for Pro-Form procesing to complete. |
| @@ -90,11 +98,14 @@ | ||
| 90 | 98 | //250522 At least set it to an empty string. |
| 91 | 99 | else if(empty($paypal['custom'])) |
| 92 | 100 | $paypal['custom'] = ''; |
| 93 | 101 | |
| 102 | + //260910.0356 Resolve the expected host consistently across different web-server request environments. | |
| 103 | + $ipn_host = (!empty($_SERVER['HTTP_HOST']) && is_string($_SERVER['HTTP_HOST'])) ? preg_replace('/\:([0-9]+)$/', '', $_SERVER['HTTP_HOST']) : (string)parse_url(home_url('/'), PHP_URL_HOST); | |
| 104 | + | |
| 94 | 105 | //250606 Added option to skip domain validation. |
| 95 | 106 | if ($GLOBALS['WS_PLUGIN__']['s2member']['o']['skip_ipn_domain_validation'] |
| 96 | - || (!empty($paypal['custom']) && preg_match('/^'.preg_quote(preg_replace('/\:([0-9]+)$/', '', $_SERVER['HTTP_HOST']), '/').'/i', $paypal['custom']))) | |
| 107 | + || ($ipn_host !== '' && !empty($paypal['custom']) && preg_match('/^'.preg_quote($ipn_host, '/').'/i', $paypal['custom']))) | |
| 97 | 108 | { |
| 98 | 109 | if ($GLOBALS['WS_PLUGIN__']['s2member']['o']['skip_ipn_domain_validation']) |
| 99 | 110 | $paypal['s2member_log'][] = 's2Member originating domain validation was skipped.'; |
| 100 | 111 | else |