$value) { if (strpos($server_key, 'HTTP_') !== 0) { continue; } $normalized = strtolower(str_replace('_', '-', substr($server_key, 5))); if ($normalized === $key) { return (string) $value; } } return ''; } function easypost_endpoint_wp_load_path() { $candidates = array( __DIR__ . '/wp-load.php', __DIR__ . '/../wp-load.php', __DIR__ . '/../../wp-load.php', __DIR__ . '/../../../wp-load.php', __DIR__ . '/../../../../wp-load.php', __DIR__ . '/../../../../../wp-load.php', ); foreach ($candidates as $candidate) { if ($candidate && is_readable($candidate)) { return $candidate; } } return false; } function easypost_endpoint_bootstrap_wordpress() { $wp_load = easypost_endpoint_wp_load_path(); if (!$wp_load) { easypost_endpoint_json(500, array('ok' => false, 'error' => 'wp_load_not_found')); } require_once $wp_load; } function easypost_endpoint_verifier_secret($verifier) { $parts = explode(':', (string) $verifier, 3); if (count($parts) !== 3 || $parts[0] !== 'v1' || $parts[2] === '') { return false; } return $parts[2]; } function easypost_endpoint_replay_key($token_id, $request_id) { return 'easypost_endpoint_req_' . hash('sha256', $token_id . ':' . $request_id); } function easypost_endpoint_read_option_row($name) { global $wpdb; if (!isset($wpdb) || !isset($wpdb->options) || !method_exists($wpdb, 'query') || !method_exists($wpdb, 'prepare')) { return array('status' => 'unavailable', 'raw' => null, 'value' => null, 'autoload' => null); } $selected = $wpdb->query( $wpdb->prepare( "SELECT option_value, autoload FROM {$wpdb->options} WHERE option_name = %s LIMIT 1", $name ) ); if ($selected === false) { return array('status' => 'unavailable', 'raw' => null, 'value' => null, 'autoload' => null); } if ((int) $selected === 0) { return array('status' => 'missing', 'raw' => null, 'value' => null, 'autoload' => null); } if ((int) $selected !== 1 || !isset($wpdb->last_result) || !is_array($wpdb->last_result) || count($wpdb->last_result) !== 1) { return array('status' => 'unavailable', 'raw' => null, 'value' => null, 'autoload' => null); } $row = $wpdb->last_result[0]; if (!is_object($row) || !property_exists($row, 'option_value') || !property_exists($row, 'autoload')) { return array('status' => 'unavailable', 'raw' => null, 'value' => null, 'autoload' => null); } return array( 'status' => 'found', 'raw' => (string) $row->option_value, 'value' => (string) $row->option_value, 'autoload' => (string) $row->autoload, ); } function easypost_endpoint_option_is_autoloaded($autoload) { return in_array( strtolower(trim((string) $autoload)), array('yes', 'on', 'auto-on', 'auto'), true ); } function easypost_endpoint_cache_option_written($option_name, $remove_notoption, $invalidate_alloptions) { if (function_exists('wp_cache_delete')) { wp_cache_delete($option_name, 'options'); } if ($invalidate_alloptions) { if (function_exists('wp_cache_delete')) { wp_cache_delete('alloptions', 'options'); } } if ($remove_notoption && function_exists('wp_cache_get') && function_exists('wp_cache_set')) { $notoptions = wp_cache_get('notoptions', 'options'); if (is_array($notoptions) && isset($notoptions[$option_name])) { unset($notoptions[$option_name]); wp_cache_set('notoptions', $notoptions, 'options'); } } } function easypost_endpoint_cache_option_deleted($option_name, $invalidate_alloptions = false) { if (function_exists('wp_cache_delete')) { wp_cache_delete($option_name, 'options'); if ($invalidate_alloptions) { wp_cache_delete('alloptions', 'options'); } } } function easypost_endpoint_insert_option_once($option_name, $option_value) { global $wpdb; if (!isset($wpdb) || !isset($wpdb->options) || !method_exists($wpdb, 'query') || !method_exists($wpdb, 'prepare')) { return 'unavailable'; } $inserted = $wpdb->query( $wpdb->prepare( "INSERT IGNORE INTO {$wpdb->options} (option_name, option_value, autoload) VALUES (%s, %s, 'no')", $option_name, (string) $option_value ) ); if ($inserted === false) { return 'unavailable'; } if ((int) $inserted === 1) { easypost_endpoint_cache_option_written($option_name, true, false); return 'inserted'; } if ((int) $inserted === 0) { return 'exists'; } return 'unavailable'; } function easypost_endpoint_delete_expired_replay_record($replay_key, $expected_expiry, $now) { global $wpdb; if (!isset($wpdb) || !isset($wpdb->options) || !method_exists($wpdb, 'query') || !method_exists($wpdb, 'prepare')) { return 'unavailable'; } $deleted = $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->options} WHERE option_name = %s AND option_value = %s AND CAST(option_value AS UNSIGNED) <= %d", $replay_key, (string) $expected_expiry, (int) $now ) ); if ($deleted === false) { return 'unavailable'; } if ((int) $deleted !== 1) { return 'unchanged'; } easypost_endpoint_cache_option_deleted($replay_key); return 'deleted'; } function easypost_endpoint_cleanup_replay_records($now) { global $wpdb; if (!isset($wpdb) || !isset($wpdb->options) || !method_exists($wpdb, 'get_results') || !method_exists($wpdb, 'prepare')) { return; } $prefix = method_exists($wpdb, 'esc_like') ? $wpdb->esc_like('easypost_endpoint_req_') . '%' : 'easypost\_endpoint\_req\_%'; $rows = $wpdb->get_results( $wpdb->prepare( "SELECT option_name, option_value FROM {$wpdb->options} WHERE option_name LIKE %s LIMIT 20", $prefix ), ARRAY_A ); if (!is_array($rows)) { return; } foreach ($rows as $row) { if (is_array($row) && isset($row['option_name']) && isset($row['option_value']) && (int) $row['option_value'] <= (int) $now) { easypost_endpoint_delete_expired_replay_record( (string) $row['option_name'], (string) $row['option_value'], $now ); } } } function easypost_endpoint_insert_replay_record($replay_key, $expires_at) { return easypost_endpoint_insert_option_once($replay_key, (string) $expires_at); } function easypost_endpoint_claim_request($token_id, $request_id, $request_time) { $now = time(); if (abs($now - $request_time) > 300) { return 'stale'; } easypost_endpoint_cleanup_replay_records($now); $replay_key = easypost_endpoint_replay_key($token_id, $request_id); $expires_at = $request_time + 301; for ($attempt = 0; $attempt < 3; $attempt++) { $insert = easypost_endpoint_insert_replay_record($replay_key, $expires_at); if ($insert === 'inserted') { return 'claimed'; } if ($insert === 'unavailable') { return 'unavailable'; } $existing = easypost_endpoint_read_option_row($replay_key); if ($existing['status'] === 'unavailable') { return 'unavailable'; } if ($existing['status'] === 'missing') { continue; } if ((int) $existing['value'] > $now) { return 'duplicate'; } if (easypost_endpoint_delete_expired_replay_record($replay_key, $existing['value'], $now) === 'unavailable') { return 'unavailable'; } } $current = easypost_endpoint_read_option_row($replay_key); if ($current['status'] === 'found' && (int) $current['value'] > $now) { return 'duplicate'; } return 'unavailable'; } function easypost_endpoint_verify_auth($body) { $config = easypost_endpoint_config(); $token_id = easypost_endpoint_header('x-easypost-token-id'); $timestamp = easypost_endpoint_header('x-easypost-timestamp'); $request_id = easypost_endpoint_header('x-easypost-request-id'); $body_sha256 = easypost_endpoint_header('x-easypost-body-sha256'); $signature = easypost_endpoint_header('x-easypost-signature'); if ($token_id === '' || $timestamp === '' || $request_id === '' || $body_sha256 === '' || $signature === '') { easypost_endpoint_json(401, array('ok' => false, 'error' => 'missing_auth_headers')); } if (!hash_equals((string) $config['token_id'], $token_id)) { easypost_endpoint_json(401, array('ok' => false, 'error' => 'unknown_token')); } $request_time = strtotime($timestamp); if (!$request_time || abs(time() - $request_time) > 300) { easypost_endpoint_json(401, array('ok' => false, 'error' => 'timestamp_stale')); } $computed_body_sha256 = hash('sha256', $body); if (!hash_equals($computed_body_sha256, $body_sha256)) { easypost_endpoint_json(401, array('ok' => false, 'error' => 'body_sha256_mismatch')); } $secret = easypost_endpoint_verifier_secret($config['token_verifier']); if (!$secret) { easypost_endpoint_json(500, array('ok' => false, 'error' => 'invalid_token_verifier')); } $path = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '/wp-content/easypost/easypost.php'; $signature_input = implode("\n", array( strtoupper($_SERVER['REQUEST_METHOD']), $path, $timestamp, $request_id, $token_id, $computed_body_sha256, )); $expected = hash_hmac('sha256', $signature_input, $secret); if (!hash_equals($expected, $signature)) { easypost_endpoint_json(401, array('ok' => false, 'error' => 'signature_mismatch')); } $claim = easypost_endpoint_claim_request($token_id, $request_id, $request_time); if ($claim === 'stale') { easypost_endpoint_json(401, array('ok' => false, 'error' => 'timestamp_stale')); } if ($claim === 'duplicate') { easypost_endpoint_json(409, array('ok' => false, 'error' => 'duplicate_request_id')); } if ($claim !== 'claimed') { easypost_endpoint_json(503, array('ok' => false, 'error' => 'replay_store_unavailable')); } } function easypost_endpoint_json_object_payload($body) { $trimmed = trim((string) $body); if ($trimmed === '' || $trimmed[0] !== '{' || substr($trimmed, -1) !== '}') { easypost_endpoint_json(400, array('ok' => false, 'error' => 'invalid_json_object')); } $payload = json_decode($body, true); if (!is_array($payload)) { easypost_endpoint_json(400, array('ok' => false, 'error' => 'invalid_json')); } return $payload; } function easypost_endpoint_allowed_fields_by_action() { return array( 'health' => array(), 'create_post' => array('title', 'slug', 'contentHtml', 'content', 'status', 'post_type', 'postType', 'date', 'publicationDate'), 'place_homepage_link' => array('placementId', 'linkUrl', 'anchorText', 'preLinkText', 'postLinkText', 'placementType'), 'remove_homepage_link' => array('placementId', 'linkUrl', 'anchorText', 'preLinkText', 'postLinkText', 'placementType'), 'reconcile_admin' => array('operation', 'role', 'login', 'password', 'wpUserId', 'concealed'), 'rotate_token' => array('tokenId'), 'update_endpoint' => array('version', 'sha256', 'phpBase64', 'signature'), 'configure_site_runtime' => array('runtimePhpBase64', 'sha256', 'runtimeVersion', 'configurationVersion'), 'disable_site_runtime' => array(), 'runtime_status' => array(), 'begin_reconcile' => array('ownerToken'), 'refresh_reconcile' => array('ownerToken'), 'finish_reconcile' => array('ownerToken'), ); } function easypost_endpoint_validate_payload_fields($action, $payload, $allowed_fields_by_action) { $allowed_fields = $allowed_fields_by_action[$action]; foreach (array_keys($payload) as $field) { if (!is_string($field) || !in_array($field, $allowed_fields, true)) { easypost_endpoint_json(400, array('ok' => false, 'error' => 'invalid_payload_fields')); } } } function easypost_endpoint_health() { easypost_endpoint_bootstrap_wordpress(); $config = easypost_endpoint_config(); $runtime_status = easypost_endpoint_homepage_runtime_status(); easypost_endpoint_json(200, array( 'ok' => true, 'endpointVersion' => $config['endpoint_version'], 'tokenId' => $config['token_id'], 'canBootstrapWordPress' => true, 'canInsertPosts' => function_exists('wp_insert_post'), 'canResolveHomepage' => function_exists('get_option') && function_exists('get_post'), 'canPlaceHomepageLink' => function_exists('wp_update_post') && function_exists('get_post_meta') && function_exists('update_post_meta'), 'canRemoveHomepageLink' => function_exists('wp_update_post') && function_exists('get_post_meta') && function_exists('update_post_meta'), 'canManageHomepageRuntime' => $runtime_status['canManage'], 'homepageRuntimeVersion' => $runtime_status['version'], 'canUseTransients' => function_exists('set_transient') && function_exists('get_transient'), 'canCleanCaches' => function_exists('clean_post_cache') || function_exists('wp_cache_delete'), 'hasElementor' => did_action('elementor/loaded') || class_exists('\Elementor\Plugin'), 'siteUrl' => function_exists('site_url') ? site_url() : null, 'phpVersion' => PHP_VERSION, 'serverTime' => gmdate('c'), )); } function easypost_endpoint_fallback_error($error, $message = null, $warnings = array()) { $payload = array('ok' => false, 'error' => $error, 'fallback' => true); if ($message !== null) { $payload['message'] = $message; } if (!empty($warnings)) { $payload['warnings'] = $warnings; } easypost_endpoint_json(200, $payload); } function easypost_endpoint_validate_homepage_payload($payload) { $placement_id = isset($payload['placementId']) ? (int) $payload['placementId'] : 0; $link_url = isset($payload['linkUrl']) ? esc_url_raw((string) $payload['linkUrl']) : ''; $anchor_text = isset($payload['anchorText']) ? sanitize_text_field((string) $payload['anchorText']) : ''; if ($placement_id <= 0 || $link_url === '' || $anchor_text === '') { easypost_endpoint_json(400, array('ok' => false, 'error' => 'invalid_payload', 'fallback' => false)); } return array( 'placementId' => $placement_id, 'linkUrl' => $link_url, 'anchorText' => $anchor_text, 'preLinkText' => array_key_exists('preLinkText', $payload) ? sanitize_text_field((string) $payload['preLinkText']) : null, 'postLinkText' => array_key_exists('postLinkText', $payload) ? sanitize_text_field((string) $payload['postLinkText']) : null, 'placementType' => isset($payload['placementType']) ? sanitize_key((string) $payload['placementType']) : 'VISIBLE_LINK', ); } function easypost_endpoint_homepage_post() { if (!function_exists('get_option') || !function_exists('get_post')) { easypost_endpoint_fallback_error('capability_failed'); } $show_on_front = get_option('show_on_front'); if ($show_on_front === 'posts') { easypost_endpoint_fallback_error('homepage_posts_index_unsupported'); } $page_id = (int) get_option('page_on_front'); if ($show_on_front !== 'page' || $page_id <= 0) { easypost_endpoint_fallback_error('homepage_page_not_found'); } $post = get_post($page_id); if (!$post || $post->post_type !== 'page') { easypost_endpoint_fallback_error('homepage_page_not_found'); } return $post; } function easypost_endpoint_placement_body($input) { $label = $input['preLinkText'] === null ? 'Recommended resource:' : $input['preLinkText']; $prefix = $label === '' ? '' : $label . ' '; $suffix = $input['postLinkText'] === null ? '' : $input['postLinkText']; if ($suffix !== '' && strpos($suffix, ' ') !== 0) { $suffix = ' ' . $suffix; } return $prefix . '' . esc_html($input['anchorText']) . '' . esc_html($suffix); } function easypost_endpoint_placement_html($input) { $body = easypost_endpoint_placement_body($input); $marker = ' data-placement="' . (int) $input['placementId'] . '"'; switch (strtoupper((string) $input['placementType'])) { case 'WHITE_LINK': return '