<?php
/**
 * Shared Token Reader
 *
 * Extracts a captcha response token (g-recaptcha-response, cf-turnstile-response,
 * etc.) from the current request. Enforces a safe upper-bound on token size to
 * stop overly large bodies from reaching the external siteverify call, and
 * refuses GET so tokens are never reflected through URLs or logs.
 *
 * @package WP_PowerSuite\Captcha\Shared
 * @since 1.1.0
 */

namespace WP_PowerSuite\Captcha\Shared;

/**
 * Token reader utility.
 *
 * Stateless — each call inspects the current `$_POST` superglobal.
 */
final class TokenReader {

	/**
	 * Default maximum token length (bytes).
	 *
	 * Real tokens from Google/Cloudflare fit comfortably below 4 KB; this cap
	 * exists to protect the siteverify HTTP call and any logging layer.
	 */
	public const DEFAULT_MAX_LEN = 4096;

	/**
	 * Read a POST field, return an empty string if missing or oversized.
	 *
	 * @param string $field    Form field name (e.g. `g-recaptcha-response`).
	 * @param int    $max_len  Max allowed length in bytes.
	 */
	public static function fromPost( string $field, int $max_len = self::DEFAULT_MAX_LEN ): string {
		if ( ! isset( $_POST[ $field ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
			return '';
		}
		$raw = wp_unslash( $_POST[ $field ] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
		if ( ! is_string( $raw ) ) {
			return '';
		}
		// CAPTCHA tokens are opaque; strip null bytes / controls without sanitize_text_field()
		// which can alter valid provider tokens.
		$raw = str_replace( "\0", '', $raw );
		$raw = preg_replace( '/[\r\n\t]+/', '', $raw ) ?? '';
		$raw = trim( $raw );
		if ( $raw === '' || strlen( $raw ) > $max_len ) {
			return '';
		}
		return $raw;
	}
}
