mimmi20/browser-detector

View on GitHub
src/RequestBuilder.php

Summary

Maintainability
A
0 mins
Test Coverage
A
100%
<?php
/**
 * This file is part of the browser-detector package.
 *
 * Copyright (c) 2012-2024, Thomas Mueller <mimmi20@live.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types = 1);

namespace BrowserDetector;

use BrowserDetector\Header\HeaderLoader;
use BrowserDetector\Loader\BrowserLoaderInterface;
use BrowserDetector\Loader\EngineLoaderInterface;
use BrowserDetector\Loader\PlatformLoaderInterface;
use BrowserDetector\Parser\BrowserParserInterface;
use BrowserDetector\Parser\DeviceParserInterface;
use BrowserDetector\Parser\EngineParserInterface;
use BrowserDetector\Parser\PlatformParserInterface;
use Laminas\Diactoros\HeaderSecurity;
use Laminas\Diactoros\ServerRequestFactory;
use Psr\Http\Message\MessageInterface;
use UaNormalizer\NormalizerFactory;

use function array_change_key_case;
use function array_filter;
use function is_array;
use function is_string;
use function preg_replace;
use function str_starts_with;

use const ARRAY_FILTER_USE_BOTH;
use const CASE_UPPER;

final class RequestBuilder implements RequestBuilderInterface
{
    /** @throws void */
    public function __construct(
        private readonly DeviceParserInterface $deviceParser,
        private readonly PlatformParserInterface $platformParser,
        private readonly BrowserParserInterface $browserParser,
        private readonly EngineParserInterface $engineParser,
        private readonly NormalizerFactory $normalizerFactory,
        private readonly BrowserLoaderInterface $browserLoader,
        private readonly PlatformLoaderInterface $platformLoader,
        private readonly EngineLoaderInterface $engineLoader,
    ) {
        // nothing to do
    }

    /**
     * @param array<non-empty-string, non-empty-string>|GenericRequestInterface|MessageInterface|string $request
     *
     * @throws void
     */
    public function buildRequest(
        array | GenericRequestInterface | MessageInterface | string $request,
    ): GenericRequestInterface {
        if ($request instanceof GenericRequestInterface) {
            return $request;
        }

        if ($request instanceof MessageInterface) {
            return $this->createRequestFromPsr7Message($request);
        }

        if (is_array($request)) {
            return $this->createRequestFromArray($request);
        }

        return $this->createRequestFromString($request);
    }

    /**
     * Create a Generic Request from the given $userAgent
     *
     * @throws void
     */
    private function createRequestFromString(string $userAgent): GenericRequest
    {
        return $this->createRequestFromArray([Constants::HEADER_HTTP_USERAGENT => $userAgent]);
    }

    /**
     * Creates Generic Request from the given HTTP Request (normally $_SERVER).
     *
     * @param array<string, string> $inputHeaders HTTP Request
     *
     * @throws void
     */
    private function createRequestFromArray(array $inputHeaders): GenericRequest
    {
        $filteredHeaders = array_filter(
            $inputHeaders,
            static function (string $value, string | int $key): bool {
                if (!is_string($key)) {
                    return false;
                }

                return $value !== '';
            },
            ARRAY_FILTER_USE_BOTH,
        );

        $headers = [];

        foreach (array_change_key_case($filteredHeaders, CASE_UPPER) as $upperCaseHeader => $value) {
            if (!str_starts_with($upperCaseHeader, 'HTTP_')) {
                $upperCaseHeader = 'HTTP_' . $upperCaseHeader;
            }

            if (!HeaderSecurity::isValid($value)) {
                $value = $this->filterHeader($value);
            }

            $headers[$upperCaseHeader] = $value;
        }

        $message = ServerRequestFactory::fromGlobals($headers);

        return $this->createRequestFromPsr7Message($message);
    }

    /**
     * Create a Generic Request from a given PSR-7 HTTP message
     *
     * @throws void
     */
    private function createRequestFromPsr7Message(MessageInterface $message): GenericRequest
    {
        return new GenericRequest(
            $message,
            new HeaderLoader(
                $this->deviceParser,
                $this->platformParser,
                $this->browserParser,
                $this->engineParser,
                $this->normalizerFactory,
                $this->browserLoader,
                $this->platformLoader,
                $this->engineLoader,
            ),
        );
    }

    /** @throws void */
    private function filterHeader(string $header): string
    {
        return (string) preg_replace(
            ["#(((?<!\r)\n)|(\r(?!\n))|(\r\n(?![ \t])))#", '/[^\x09\x0a\x0d\x20-\x7E\x80-\xFE]/'],
            '-',
            $header,
        );
    }
}