<commit_before><?php
declare(strict_types=1);
namespace ParagonIE\AntiCSRF;

use ParagonIE\ConstantTime\{
    Base64UrlSafe,
    Binary
};
use Error;
use ParagonIE\AntiCSRF\Exception\AntiCSRFException;
use ParagonIE\AntiCSRF\Exception\FormLockException;
use ParagonIE\AntiCSRF\Exception\FormPostException;
use ParagonIE\AntiCSRF\Exception\TokenHashException;
use ParagonIE\AntiCSRF\Exception\TokenIndexNotInSessionException;
use ParagonIE\AntiCSRF\Exception\TokenNotInSessionException;

/**
 * Copyright (c) 2015 - 2018 Paragon Initiative Enterprises <https://paragonie.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published
 * by the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *******************************************************************************
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2015 - 2018 Paragon Initiative Enterprises
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 *
 * If you would like to use this library under different terms, please
 * contact Paragon Initiative Enterprises to inquire about a license exemption.
 */
class AntiCSRF
{
    /**
     * @var string
     */
    protected $formIndex = '_CSRF_INDEX';

    /**
     * @var string
     */
    protected $formToken = <PASSWORD>';

    /**
     * @var string
     */
    protected $sessionIndex = 'CSRF';

    /**
     * @var string
     */
    protected $hashAlgo = 'sha256';

    /**
     * @var int
     */
    protected $recycle_after = 65535;

    /**
     * @var bool
     */
    protected $hmac_ip = true;

    /**
     * @var bool
     */
    protected $expire_old = false;

    /**
     * @var string
     */
    protected $lock_type = 'REQUEST_URI';

    // Injected; defaults to references to superglobals

    /**
     * @var array
     */
    public $post = [];

    /**
     * @var array
     */
    public $session = [];

    /**
     * @var bool
     */
    public $useNativeSession = false;

    /**
     * @var array
     */
    public $server = [];

    /**
     * NULL is not a valid array type
     *
     * @param array $post
     * @param array $session
     * @param array $server
     * @throws Error
     */
    public function __construct(
        &$post = null,
        &$session = null,
        &$server = null
    ) {
        if (!\is_null($post)) {
            $this->post =& $post;
        } else {
            $this->post =& $_POST;
        }

        if (!\is_null($server)) {
            $this->server =& $server;
        } else {
            $this->server =& $_SERVER;
        }

        if (!\is_null($session)) {
            $this->session =& $session;
        } elseif (isset($_SESSION)) {
            if (\is_array($_SESSION)) {
                $this->session =& $_SESSION;
                $this->useNativeSession = true;
            }
        } else {
            throw new Error('No session available for persistence');
        }
    }

    /**
     * Allow derived classes to inject arguments.
     *
     * @param array $args
     * @return array
     */
    protected function buildBasicToken(array $args = []): array
    {
        return $args;
    }

    /**
     * @param array $token
     * @return bool
     */
    public function deleteToken(array $token): bool
    {
        return true;
    }

    /**
     * Insert a CSRF token to a form
     *
     * @param string $lockTo This CSRF token is only valid for this HTTP request endpoint
     * @param bool $echo if true, echo instead of returning
     * @return string
     * @throws \Exception
     * @throws \TypeError
     */
    public function insertToken(string $lockTo = '', bool $echo = true): string
    {
        $token_array = $this->getTokenArray($lockTo);
        $ret = \implode(
            \array_map(
                function(string $key, string $value): string {
                    return "<!--\n-->".
                        "<input type=\"hidden\"" .
                        " name=\"" . $key . "\"" .
                        " value=\"" . self::noHTML($value) . "\"" .
                        " />";
                },
                \array_keys($token_array),
                $token_array
            )
        );
        if ($echo) {
            echo $ret;
            return '';
        }
        return $ret;
    }

    /**
     * @return string
     */
    public function getSessionIndex(): string
    {
        return $this->sessionIndex;
    }

    /**
     * @return string
     */
    public function getFormIndex(): string
    {
        return $this->formIndex;
    }

    /**
     * @return string
     */
    public function getFormToken(): string
    {
        return $this->formToken;
    }

    /**
     * @return string
     */
    public function getLockType(): string
    {
        return $this->lock_type;
    }

    /**
     * Retrieve a token array for unit testing endpoints
     *
     * @param string $lockTo
     * @return array
     *
     * @throws \Exception
     * @throws \TypeError
     */
    public function getTokenArray(string $lockTo = ''): array
    {
        if ($this->useNativeSession) {
            if (!isset($_SESSION[$this->sessionIndex])) {
                $_SESSION[$this->sessionIndex] = [];
            }
        } elseif (!isset($this->session[$this->sessionIndex])) {
            $this->session[$this->sessionIndex] = [];
        }

        if (empty($lockTo)) {
            /** @var string $lockTo */
            $lockTo = isset($this->server['REQUEST_URI'])
                ? $this->server['REQUEST_URI']
                : '/';
        }

        if (\preg_match('#/$#', $lockTo)) {
            $lockTo = Binary::safeSubstr($lockTo, 0, Binary::safeStrlen($lockTo) - 1);
        }

        list($index, $token) = $this->generateToken($lockTo);

        if ($this->hmac_ip !== false) {
            // Use HMAC to only allow this particular IP to send this request
            $token = Base64UrlSafe::encode(
                \hash_hmac(
                    $this->hashAlgo,
                    isset($this->server['REMOTE_ADDR'])
                        ? (string) $this->server['REMOTE_ADDR']
                        : '127.0.0.1',
                    (string) Base64UrlSafe::decode($token),
                    true
                )
            );
        }

        return [
            $this->formIndex => $index,
            $this->formToken => $token,
        ];
    }


    /**
     * Validate a request based on $this->session and $this->post data
     *
     * @return bool
     * @throws \TypeError
     */
    public function validateRequest(string $lockTo = ''): bool
    {
        try {
            return true;
        } catch (AntiCSRFException $ex) {
        }

        return false;
    }

    /**
     * Validate a request based on $this->session and $this->post data
     * Throws on validation failure
     *
     * @return void
     * @throws \TypeError
     * @throws TokenIndexNotInSessionException
     * @throws FormPostException
     * @throws TokenNotInSessionException
     * @throws FormLockException
     * @throws TokenHashException
     */
    public function validateRequestOrThrow()
    {
        if ($this->useNativeSession) {
            if (!isset($_SESSION[$this->sessionIndex])) {
                throw TokenIndexNotInSessionException::fromNative($this->sessionIndex);
            }
            /** @var array<string, array<string, mixed>> $sess */
            $sess =& $_SESSION[$this->sessionIndex];
        } else {
            if (!isset($this->session[$this->sessionIndex])) {
                throw TokenIndexNotInSessionException::fromConstructor($this->sessionIndex);
            }
            /** @var array<string, array<string, mixed>> $sess */
            $sess =& $this->session[$this->sessionIndex];
        }

        if (empty($this->post[$this->formIndex])) {
            // User must transmit a complete index/token pair
            throw FormPostException::missingIndex($this->formIndex);
        }

        if (empty($this->post[$this->formToken])) {
            // User must transmit a complete index/token pair
            throw FormPostException::missingToken($this->formToken);<commit_msg>validateRequest() method must call validateRequestOrThrow() or else it is a no-op.<commit_after><?php
declare(strict_types=1);
namespace ParagonIE\AntiCSRF;

use ParagonIE\ConstantTime\{
    Base64UrlSafe,
    Binary
};
use Error;
use ParagonIE\AntiCSRF\Exception\AntiCSRFException;
use ParagonIE\AntiCSRF\Exception\FormLockException;
use ParagonIE\AntiCSRF\Exception\FormPostException;
use ParagonIE\AntiCSRF\Exception\TokenHashException;
use ParagonIE\AntiCSRF\Exception\TokenIndexNotInSessionException;
use ParagonIE\AntiCSRF\Exception\TokenNotInSessionException;

/**
 * Copyright (c) 2015 - 2018 Paragon Initiative Enterprises <https://paragonie.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published
 * by the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *******************************************************************************
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2015 - 2018 Paragon Initiative Enterprises
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 *
 * If you would like to use this library under different terms, please
 * contact Paragon Initiative Enterprises to inquire about a license exemption.
 */
class AntiCSRF
{
    /**
     * @var string
     */
    protected $formIndex = '_CSRF_INDEX';

    /**
     * @var string
     */
    protected $formToken = '_CSRF_TOKEN';

    /**
     * @var string
     */
    protected $sessionIndex = 'CSRF';

    /**
     * @var string
     */
    protected $hashAlgo = 'sha256';

    /**
     * @var int
     */
    protected $recycle_after = 65535;

    /**
     * @var bool
     */
    protected $hmac_ip = true;

    /**
     * @var bool
     */
    protected $expire_old = false;

    /**
     * @var string
     */
    protected $lock_type = 'REQUEST_URI';

    // Injected; defaults to references to superglobals

    /**
     * @var array
     */
    public $post = [];

    /**
     * @var array
     */
    public $session = [];

    /**
     * @var bool
     */
    public $useNativeSession = false;

    /**
     * @var array
     */
    public $server = [];

    /**
     * NULL is not a valid array type
     *
     * @param array $post
     * @param array $session
     * @param array $server
     * @throws Error
     */
    public function __construct(
        &$post = null,
        &$session = null,
        &$server = null
    ) {
        if (!\is_null($post)) {
            $this->post =& $post;
        } else {
            $this->post =& $_POST;
        }

        if (!\is_null($server)) {
            $this->server =& $server;
        } else {
            $this->server =& $_SERVER;
        }

        if (!\is_null($session)) {
            $this->session =& $session;
        } elseif (isset($_SESSION)) {
            if (\is_array($_SESSION)) {
                $this->session =& $_SESSION;
                $this->useNativeSession = true;
            }
        } else {
            throw new Error('No session available for persistence');
        }
    }

    /**
     * Allow derived classes to inject arguments.
     *
     * @param array $args
     * @return array
     */
    protected function buildBasicToken(array $args = []): array
    {
        return $args;
    }

    /**
     * @param array $token
     * @return bool
     */
    public function deleteToken(array $token): bool
    {
        return true;
    }

    /**
     * Insert a CSRF token to a form
     *
     * @param string $lockTo This CSRF token is only valid for this HTTP request endpoint
     * @param bool $echo if true, echo instead of returning
     * @return string
     * @throws \Exception
     * @throws \TypeError
     */
    public function insertToken(string $lockTo = '', bool $echo = true): string
    {
        $token_array = $this->getTokenArray($lockTo);
        $ret = \implode(
            \array_map(
                function(string $key, string $value): string {
                    return "<!--\n-->".
                        "<input type=\"hidden\"" .
                        " name=\"" . $key . "\"" .
                        " value=\"" . self::noHTML($value) . "\"" .
                        " />";
                },
                \array_keys($token_array),
                $token_array
            )
        );
        if ($echo) {
            echo $ret;
            return '';
        }
        return $ret;
    }

    /**
     * @return string
     */
    public function getSessionIndex(): string
    {
        return $this->sessionIndex;
    }

    /**
     * @return string
     */
    public function getFormIndex(): string
    {
        return $this->formIndex;
    }

    /**
     * @return string
     */
    public function getFormToken(): string
    {
        return $this->formToken;
    }

    /**
     * @return string
     */
    public function getLockType(): string
    {
        return $this->lock_type;
    }

    /**
     * Retrieve a token array for unit testing endpoints
     *
     * @param string $lockTo
     * @return array
     *
     * @throws \Exception
     * @throws \TypeError
     */
    public function getTokenArray(string $lockTo = ''): array
    {
        if ($this->useNativeSession) {
            if (!isset($_SESSION[$this->sessionIndex])) {
                $_SESSION[$this->sessionIndex] = [];
            }
        } elseif (!isset($this->session[$this->sessionIndex])) {
            $this->session[$this->sessionIndex] = [];
        }

        if (empty($lockTo)) {
            /** @var string $lockTo */
            $lockTo = isset($this->server['REQUEST_URI'])
                ? $this->server['REQUEST_URI']
                : '/';
        }

        if (\preg_match('#/$#', $lockTo)) {
            $lockTo = Binary::safeSubstr($lockTo, 0, Binary::safeStrlen($lockTo) - 1);
        }

        list($index, $token) = $this->generateToken($lockTo);

        if ($this->hmac_ip !== false) {
            // Use HMAC to only allow this particular IP to send this request
            $token = Base64UrlSafe::encode(
                \hash_hmac(
                    $this->hashAlgo,
                    isset($this->server['REMOTE_ADDR'])
                        ? (string) $this->server['REMOTE_ADDR']
                        : '127.0.0.1',
                    (string) Base64UrlSafe::decode($token),
                    true
                )
            );
        }

        return [
            $this->formIndex => $index,
            $this->formToken => $token,
        ];
    }


    /**
     * Validate a request based on $this->session and $this->post data
     *
     * @return bool
     * @throws \TypeError
     */
    public function validateRequest(string $lockTo = ''): bool
    {
        try {
            $this->validateRequestOrThrow($lockTo);
            return true;
        } catch (AntiCSRFException $ex) {
        }

        return false;
    }

    /**
     * Validate a request based on $this->session and $this->post data
     * Throws on validation failure
     *
     * @return void
     * @throws \TypeError
     * @throws TokenIndexNotInSessionException
     * @throws FormPostException
     * @throws TokenNotInSessionException
     * @throws FormLockException
     * @throws TokenHashException
     */
    public function validateRequestOrThrow(string $lockTo = '')
    {
        if ($this->useNativeSession) {
            if (!isset($_SESSION[$this->sessionIndex])) {
                throw TokenIndexNotInSessionException::fromNative($this->sessionIndex);
            }
            /** @var array<string, array<string, mixed>> $sess */
            $sess =& $_SESSION[$this->sessionIndex];
        } else {
            if (!isset($this->session[$this->sessionIndex])) {
                throw TokenIndexNotInSessionException::fromConstructor($this->sessionIndex);
            }
            /** @var array<string, array<string, mixed>> $sess */
            $sess =& $this->session[$this->sessionIndex];
        }

        if (empty($this->post[$this->formIndex])) {
            // User must transmit a complete index/token pair
            throw FormPostException::missingIndex($this->formIndex);
        }

        if (empty($this->post[$this->formToken])) {
            // User must transmit a complete index/token pair
            throw FormPostException::missingToken($this->formToken);
{'fraction_non_alphanumeric': 0.12478300827121414, 'fraction_numerical': 0.0040845501889104465, 'mean_word_length': 1.9686268566232192, 'pattern_counts': {'":': 0, '<': 22, '<?xml version=': 0, '>': 119, 'https://': 2, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 2, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3257381', 'n_tokens_mistral': 5460, 'n_tokens_neox': 5178, 'n_words': 1549}