PluginProbe
WCPOS – Point of Sale (POS) plugin for WooCommerce / trunk
WCPOS – Point of Sale (POS) plugin for WooCommerce vtrunk
1.10.15 1.10.13 1.10.14 1.10.12 1.10.11 1.10.10 1.10.9 1.10.8 untagged-3d9b7ccddc54df87c672 1.10.7 1.10.6 1.10.5 1.10.3 1.10.4 1.10.2 1.10.1 1.10.0 1.9.17 1.9.15 1.9.16 1.9.14 1.9.13 1.9.12 1.9.11 1.9.10 All 159 releases
woocommerce-pos / includes / Sync / Header_Mirror.php

Header_Mirror.php in WCPOS – Point of Sale (POS) plugin for WooCommerce trunk, at includes/Sync/Header_Mirror.php

68 lines 2.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * WCPOS sync write header mirror.
4 *
5 * @package WCPOS\WooCommercePOS\Sync
6 */
7
8 namespace WCPOS\WooCommercePOS\Sync;
9
10 use WP_Error;
11 use WP_REST_Request;
12
13 // phpcs:disable Squiz.Commenting, Generic.Commenting -- Ported lab documentation is preserved verbatim.
14
15 /**
16 * Standard-header MIRROR cross-check (ADR 0011) for the write path — the generic
17 * per-collection push (Woo_RxDB_Sync_Write_Controller, /push/{collection}).
18 *
19 * The JSON body stays CANONICAL. The client MAY also send the sync-control signals as standard HTTP
20 * headers — Idempotency-Key (= mutationId) and If-Match (= baseRevision) — for a standards-shaped wire
21 * surface (proxies/observability). These headers are only a cross-check: a mangled-but-parseable header
22 * (a proxy requoting, a Cloudflare-weakened ETag) is worse than a missing one, so when BOTH a header and
23 * its body field are present and DISAGREE we reject (422) rather than trust either side.
24 */
25 final class Header_Mirror {
26 /** The header names this contract adds, in their canonical (sent) casing. */
27 public const HEADERS = array( 'Idempotency-Key', 'If-Match' );
28
29 /**
30 * @return WP_Error|null A 422 WP_Error on header/body divergence, or null when they agree or are absent.
31 */
32 public static function assert( WP_REST_Request $request, string $body_mutation_id, $body_base_revision ) {
33 $header_mutation_id = self::header_value( $request, 'idempotency-key' );
34 if ( null !== $header_mutation_id && $header_mutation_id !== $body_mutation_id ) {
35 return new WP_Error( 'woo_rxdb_sync_header_body_mismatch', 'Idempotency-Key header disagrees with body mutationId.', array( 'status' => 422 ) );
36 }
37 $header_base_revision = self::unquote_entity_tag( self::header_value( $request, 'if-match' ) );
38 // A non-string baseRevision (a malformed body) is treated as absent, not cast to "Array" with a notice.
39 $body_revision = is_string( $body_base_revision ) ? $body_base_revision : '';
40 if ( null !== $header_base_revision && $header_base_revision !== $body_revision ) {
41 return new WP_Error( 'woo_rxdb_sync_header_body_mismatch', 'If-Match header disagrees with body baseRevision.', array( 'status' => 422 ) );
42 }
43 return null;
44 }
45
46 private static function header_value( WP_REST_Request $request, string $name ): ?string {
47 $value = $request->get_header( $name );
48 if ( null === $value ) {
49 return null;
50 }
51 $value = trim( (string) $value );
52 return '' === $value ? null : $value;
53 }
54
55 /** Strip an optional weak indicator (W/) and surrounding double quotes from an entity-tag, so the bare
56 * revision compares equal even if an intermediary weakened a strong tag (RFC 9110 §8.8.3). */
57 private static function unquote_entity_tag( ?string $value ): ?string {
58 if ( null === $value ) {
59 return null;
60 }
61 $value = preg_replace( '/^W\//', '', trim( $value ) );
62 if ( strlen( $value ) >= 2 && '"' === $value[0] && '"' === substr( $value, -1 ) ) {
63 $value = substr( $value, 1, -1 );
64 }
65 return $value;
66 }
67 }
68