<?php

declare(strict_types=1);

/*
 * Copyright (c) 2017-2023 François Kooman <fkooman@tuxed.net>
 *
 * 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.
 */

namespace fkooman\OAuth\Server\Http;

use fkooman\OAuth\Server\Exception\InvalidTokenException;

class Request
{
    private array $serverData;
    private array $getData;
    private array $postData;

    public function __construct(array $serverData, array $getData, array $postData)
    {
        $this->serverData = $serverData;
        $this->getData = $getData;
        $this->postData = $postData;
    }

    public static function fromServerVariables(): self
    {
        return new self($_SERVER, $_GET, $_POST);
    }

    public function query(): Query
    {
        return new Query($this->getData);
    }

    public function post(): Post
    {
        return new Post($this->postData);
    }

    public function requestMethod(): string
    {
        if (!array_key_exists('REQUEST_METHOD', $this->serverData)) {
            return '?';
        }
        if (!is_string($this->serverData['REQUEST_METHOD'])) {
            return '?';
        }

        return $this->serverData['REQUEST_METHOD'];
    }

    public function pathInfo(): string
    {
        if (!array_key_exists('PATH_INFO', $this->serverData)) {
            return '/';
        }
        if (!is_string($this->serverData['PATH_INFO'])) {
            return '/';
        }

        return $this->serverData['PATH_INFO'];
    }

    /**
     * Determine whether the caller is a browser or a simple (JSON) HTTP
     * client.
     */
    public function isBrowser(): bool
    {
        if (!array_key_exists('HTTP_ACCEPT', $this->serverData)) {
            return false;
        }
        if (!is_string($this->serverData['HTTP_ACCEPT'])) {
            return false;
        }

        return false !== stripos($this->serverData['HTTP_ACCEPT'], 'text/html');
    }

    /**
     * Get the URL decoded "username".
     *
     * @see https://www.rfc-editor.org/rfc/rfc6749#section-2.3.1
     */
    public function authUser(): ?string
    {
        if (!\array_key_exists('PHP_AUTH_USER', $this->serverData)) {
            return null;
        }

        if (!\is_string($this->serverData['PHP_AUTH_USER'])) {
            return null;
        }

        return urldecode($this->serverData['PHP_AUTH_USER']);
    }

    /**
     * Get the URL decoded "password".
     *
     * @see https://www.rfc-editor.org/rfc/rfc6749#section-2.3.1
     */
    public function authPass(): ?string
    {
        if (!\array_key_exists('PHP_AUTH_PW', $this->serverData)) {
            return null;
        }

        if (!\is_string($this->serverData['PHP_AUTH_PW'])) {
            return null;
        }

        return urldecode($this->serverData['PHP_AUTH_PW']);
    }

    public function bearerToken(): string
    {
        if (!\array_key_exists('HTTP_AUTHORIZATION', $this->serverData)) {
            // no authorization header results in different error
            throw new InvalidTokenException(null);
        }

        $authorizationHeader = $this->serverData['HTTP_AUTHORIZATION'];
        if (!\is_string($authorizationHeader)) {
            // MUST be string
            throw new InvalidTokenException(null);
        }

        if (0 !== strpos($authorizationHeader, 'Bearer ')) {
            // MUST be bearer
            throw new InvalidTokenException(null);
        }

        // b64token    = 1*( ALPHA / DIGIT /
        //                   "-" / "." / "_" / "~" / "+" / "/" ) *"="
        // credentials = "Bearer" 1*SP b64token
        if (1 !== preg_match('|^Bearer [a-zA-Z0-9-._~+/]+=*$|', $authorizationHeader)) {
            throw new InvalidTokenException('invalid token');
        }

        return substr($authorizationHeader, 7);
    }
}
