<commit_before><?php
declare(strict_types=1);

/**
 * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
 * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
 *
 * Licensed under The MIT License
 * For full copyright and license information, please see the LICENSE.txt
 * Redistributions of files must retain the above copyright notice.
 *
 * @copyright     Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
 * @link          https://cakephp.org CakePHP(tm) Project
 * @since         4.0.0
 * @license       https://opensource.org/licenses/mit-license.php MIT License
 */
namespace Cake\Form;

use Cake\Core\Configure;
use Cake\Utility\Hash;
use Cake\Utility\Security;

/**
 * Protects against form tampering. It ensures that:
 *
 * - Form's action (URL) is not modified.
 * - Unknown / extra fields are not added to the form.
 * - Existing fields have not been removed from the form.
 * - Values of hidden inputs have not been changed.
 *
 * @internal
 */
class FormProtector
{
    /**
     * Fields list.
     *
     * @var array
     */
    protected $fields = [];

    /**
     * Unlocked fields.
     *
     * @var array
     */
    protected $unlockedFields = [];

    /**
     * Error message providing detail for failed validation.
     *
     * @var string|null
     */
    protected $debugMessage;

    /**
     * Construct.
     *
     * @param array $data Data array, can contain key `unlockedFields` with list of unlocked fields.
     */
    public function __construct(array $data = [])
    {
        if (!empty($data['unlockedFields'])) {
            $this->unlockedFields = $data['unlockedFields'];
        }
    }

    /**
     * Validate submitted form data.
     *
     * @param mixed $formData Form data.
     * @param string $url URL form was POSTed to.
     * @param string $sessionId Session id for hash generation.
     * @return bool
     */
    public function validate($formData, string $url, string $sessionId): bool
    {
        $this->debugMessage = null;

        $extractedToken = $this->extractToken($formData);
        if (empty($extractedToken)) {
            return false;
        }

        $hashParts = $this->extractHashParts($formData);
        $generatedToken = $this->generateHash(
            $hashParts['fields'],
            $hashParts['unlockedFields'],
            $url,
            $sessionId
        );

        if (hash_equals($generatedToken, $extractedToken)) {
            return true;
        }

        if (Configure::read('debug')) {
            $debugMessage = $this->debugTokenNotMatching($formData, $hashParts + compact('url', 'sessionId'));
            if ($debugMessage) {
                $this->debugMessage = $debugMessage;
            }
        }

        return false;
    }

    /**
     * Determine which fields of a form should be used for hash.
     *
     * @param string|array $field Reference to field to be secured. Can be dot
     *   separated string to indicate nesting or array of fieldname parts.
     * @param bool $lock Whether this field should be part of the validation
     *   or excluded as part of the unlockedFields. Default `true`.
     * @param mixed $value Field value, if value should not be tampered with.
     * @return $this
     */
    public function addField($field, bool $lock = true, $value = null)
    {
        if (is_string($field)) {
            $field = $this->getFieldNameArray($field);
        }

        if (empty($field)) {
            return $this;
        }

        foreach ($this->unlockedFields as $unlockField) {
            $unlockParts = explode('.', $unlockField);
            if (array_values(array_intersect($field, $unlockParts)) === $unlockParts) {
                return $this;
            }
        }

        $field = implode('.', $field);
        $field = preg_replace('/(\.\d+)+$/', '', $field);

        if ($lock) {
            if (!in_array($field, $this->fields, true)) {
                if ($value !== null) {
                    $this->fields[$field] = $value;

                    return $this;
                }
                if (isset($this->fields[$field])) {
                    unset($this->fields[$field]);
                }
                $this->fields[] = $field;
            }
        } else {
            $this->unlockField($field);
        }

        return $this;
    }

    /**
     * Parses the field name to create a dot separated name value for use in
     * field hash. If filename is of form Model[field] or Model.field an array of
     * fieldname parts like ['Model', 'field'] is returned.
     *
     * @param string $name The form inputs name attribute.
     * @return string[] Array of field name params like ['Model.field'] or
     *   ['Model', 'field'] for array fields or empty array if $name is empty.
     */
    protected function getFieldNameArray(string $name): array
    {
        if (empty($name) && $name !== '0') {
            return [];
        }

        if (strpos($name, '[') === false) {
            return Hash::filter(explode('.', $name));
        }
        $parts = explode('[', $name);<commit_msg>Fix word in comment<commit_after><?php
declare(strict_types=1);

/**
 * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
 * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
 *
 * Licensed under The MIT License
 * For full copyright and license information, please see the LICENSE.txt
 * Redistributions of files must retain the above copyright notice.
 *
 * @copyright     Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
 * @link          https://cakephp.org CakePHP(tm) Project
 * @since         4.0.0
 * @license       https://opensource.org/licenses/mit-license.php MIT License
 */
namespace Cake\Form;

use Cake\Core\Configure;
use Cake\Utility\Hash;
use Cake\Utility\Security;

/**
 * Protects against form tampering. It ensures that:
 *
 * - Form's action (URL) is not modified.
 * - Unknown / extra fields are not added to the form.
 * - Existing fields have not been removed from the form.
 * - Values of hidden inputs have not been changed.
 *
 * @internal
 */
class FormProtector
{
    /**
     * Fields list.
     *
     * @var array
     */
    protected $fields = [];

    /**
     * Unlocked fields.
     *
     * @var array
     */
    protected $unlockedFields = [];

    /**
     * Error message providing detail for failed validation.
     *
     * @var string|null
     */
    protected $debugMessage;

    /**
     * Construct.
     *
     * @param array $data Data array, can contain key `unlockedFields` with list of unlocked fields.
     */
    public function __construct(array $data = [])
    {
        if (!empty($data['unlockedFields'])) {
            $this->unlockedFields = $data['unlockedFields'];
        }
    }

    /**
     * Validate submitted form data.
     *
     * @param mixed $formData Form data.
     * @param string $url URL form was POSTed to.
     * @param string $sessionId Session id for hash generation.
     * @return bool
     */
    public function validate($formData, string $url, string $sessionId): bool
    {
        $this->debugMessage = null;

        $extractedToken = $this->extractToken($formData);
        if (empty($extractedToken)) {
            return false;
        }

        $hashParts = $this->extractHashParts($formData);
        $generatedToken = $this->generateHash(
            $hashParts['fields'],
            $hashParts['unlockedFields'],
            $url,
            $sessionId
        );

        if (hash_equals($generatedToken, $extractedToken)) {
            return true;
        }

        if (Configure::read('debug')) {
            $debugMessage = $this->debugTokenNotMatching($formData, $hashParts + compact('url', 'sessionId'));
            if ($debugMessage) {
                $this->debugMessage = $debugMessage;
            }
        }

        return false;
    }

    /**
     * Determine which fields of a form should be used for hash.
     *
     * @param string|array $field Reference to field to be secured. Can be dot
     *   separated string to indicate nesting or array of fieldname parts.
     * @param bool $lock Whether this field should be part of the validation
     *   or excluded as part of the unlockedFields. Default `true`.
     * @param mixed $value Field value, if value should not be tampered with.
     * @return $this
     */
    public function addField($field, bool $lock = true, $value = null)
    {
        if (is_string($field)) {
            $field = $this->getFieldNameArray($field);
        }

        if (empty($field)) {
            return $this;
        }

        foreach ($this->unlockedFields as $unlockField) {
            $unlockParts = explode('.', $unlockField);
            if (array_values(array_intersect($field, $unlockParts)) === $unlockParts) {
                return $this;
            }
        }

        $field = implode('.', $field);
        $field = preg_replace('/(\.\d+)+$/', '', $field);

        if ($lock) {
            if (!in_array($field, $this->fields, true)) {
                if ($value !== null) {
                    $this->fields[$field] = $value;

                    return $this;
                }
                if (isset($this->fields[$field])) {
                    unset($this->fields[$field]);
                }
                $this->fields[] = $field;
            }
        } else {
            $this->unlockField($field);
        }

        return $this;
    }

    /**
     * Parses the field name to create a dot separated name value for use in
     * field hash. If fieldname is of form Model[field] or Model.field an array of
     * fieldname parts like ['Model', 'field'] is returned.
     *
     * @param string $name The form inputs name attribute.
     * @return string[] Array of field name params like ['Model.field'] or
     *   ['Model', 'field'] for array fields or empty array if $name is empty.
     */
    protected function getFieldNameArray(string $name): array
    {
        if (empty($name) && $name !== '0') {
            return [];
        }

        if (strpos($name, '[') === false) {
            return Hash::filter(explode('.', $name));
        }
        $parts = explode('[', $name);
{'fraction_non_alphanumeric': 0.12753961310391757, 'fraction_numerical': 0.000972100709633518, 'mean_word_length': 2.024103468547913, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 33, 'https://': 10, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5739485', 'n_tokens_mistral': 2885, 'n_tokens_neox': 2778, 'n_words': 860}