| 1 |
<?php |
| 2 |
/** |
| 3 |
* Public request-header echo probe REST API controller. |
| 4 |
* |
| 5 |
* @package WCPOS\WooCommercePOS\API\V2 |
| 6 |
*/ |
| 7 |
|
| 8 |
namespace WCPOS\WooCommercePOS\API\V2; |
| 9 |
|
| 10 |
use WCPOS\WooCommercePOS\Sync\Cors; |
| 11 |
use WP_REST_Request; |
| 12 |
use WP_REST_Response; |
| 13 |
|
| 14 |
/** Reports which POS client headers reached the normal REST stack. */ |
| 15 |
final class Echo_Probe { |
| 16 |
/** Register the public echo probe route. */ |
| 17 |
public function register_routes(): void { |
| 18 |
register_rest_route( |
| 19 |
'wcpos/v2', |
| 20 |
'/echo', |
| 21 |
array( |
| 22 |
'methods' => 'GET', |
| 23 |
'callback' => array( $this, 'get_echo' ), |
| 24 |
'permission_callback' => '__return_true', |
| 25 |
) |
| 26 |
); |
| 27 |
} |
| 28 |
|
| 29 |
/** |
| 30 |
* Classify the probe as public for the central permission gate. |
| 31 |
* |
| 32 |
* @return array<string, string[]> |
| 33 |
*/ |
| 34 |
public function wcpos_route_classifications(): array { |
| 35 |
return array( 'public' => array( '/wcpos/v2/echo' ) ); |
| 36 |
} |
| 37 |
|
| 38 |
/** |
| 39 |
* Return header and fallback-parameter presence without exposing values. |
| 40 |
* |
| 41 |
* @param WP_REST_Request $request Request to inspect. |
| 42 |
*/ |
| 43 |
public function get_echo( WP_REST_Request $request ): WP_REST_Response { |
| 44 |
$headers = array(); |
| 45 |
|
| 46 |
foreach ( array_merge( array( 'Authorization', 'Content-Type', 'X-WCPOS' ), Cors::headers() ) as $name ) { |
| 47 |
$key = strtolower( $name ); |
| 48 |
// get_header() returns null when absent — normalize, or an absent |
| 49 |
// header reads as received (the exact case this probe detects). |
| 50 |
$value = (string) ( $request->get_header( $name ) ?? '' ); |
| 51 |
|
| 52 |
if ( 'authorization' === $key && '' === $value && ! empty( $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] ) && \is_string( $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] ) ) { |
| 53 |
$value = $_SERVER['REDIRECT_HTTP_AUTHORIZATION']; // phpcs:ignore -- Raw byte length only; the value is never returned. |
| 54 |
} |
| 55 |
|
| 56 |
$headers[ $key ] = array( |
| 57 |
'received' => '' !== $value, |
| 58 |
'length' => \strlen( $value ), |
| 59 |
); |
| 60 |
} |
| 61 |
$params = $request->get_query_params(); |
| 62 |
|
| 63 |
return new WP_REST_Response( |
| 64 |
array( |
| 65 |
// Stays 1: shipped clients hard-gate on `v === 1` and read a |
| 66 |
// mismatch as "not the echo route" (hydration-steps.ts), so an |
| 67 |
// additive field must not bump it. |
| 68 |
'v' => 1, |
| 69 |
'headers' => $headers, |
| 70 |
'params' => array( |
| 71 |
'authorization' => isset( $params['authorization'] ) && '' !== $params['authorization'], |
| 72 |
'wcpos' => isset( $params['wcpos'] ) && '' !== $params['wcpos'], |
| 73 |
'store_id' => isset( $params['store_id'] ) && '' !== $params['store_id'], |
| 74 |
'wcpos_protocol' => isset( $params['wcpos_protocol'] ) && '' !== $params['wcpos_protocol'], |
| 75 |
'wcpos_client' => isset( $params['wcpos_client'] ) && '' !== $params['wcpos_client'], |
| 76 |
), |
| 77 |
// Means: this SERVER reflects announced x-wcpos-* names at |
| 78 |
// preflight ({@see \WCPOS\WooCommercePOS\Rest_Cors}). It does |
| 79 |
// NOT prove this store's preflights reach PHP — an edge that |
| 80 |
// answers OPTIONS itself still blocks new headers, so a client |
| 81 |
// must confirm the path with one cross-origin request carrying |
| 82 |
// a throwaway x-wcpos-* header before trusting header transport. |
| 83 |
'cors' => array( 'reflects_request_headers' => true ), |
| 84 |
), |
| 85 |
200 |
| 86 |
); |
| 87 |
} |
| 88 |
} |
| 89 |
|