xref: /dokuwiki/vendor/simplepie/simplepie/src/HTTP/Psr18Client.php (revision 8e88a29b81301f78509349ab1152bb09c229123e)
1<?php
2
3// SPDX-FileCopyrightText: 2004-2023 Ryan Parman, Sam Sneddon, Ryan McCue
4// SPDX-License-Identifier: BSD-3-Clause
5
6declare(strict_types=1);
7
8namespace SimplePie\HTTP;
9
10use InvalidArgumentException;
11use Psr\Http\Client\ClientExceptionInterface;
12use Psr\Http\Client\ClientInterface;
13use Psr\Http\Message\RequestFactoryInterface;
14use Psr\Http\Message\UriFactoryInterface;
15use Throwable;
16
17/**
18 * HTTP Client based on PSR-18 and PSR-17 implementations
19 *
20 * @internal
21 */
22final class Psr18Client implements Client
23{
24    /**
25     * @var ClientInterface
26     */
27    private $httpClient;
28
29    /**
30     * @var RequestFactoryInterface
31     */
32    private $requestFactory;
33
34    /**
35     * @var UriFactoryInterface
36     */
37    private $uriFactory;
38
39    /**
40     * @var int
41     */
42    private $allowedRedirects = 5;
43
44    public function __construct(ClientInterface $httpClient, RequestFactoryInterface $requestFactory, UriFactoryInterface $uriFactory)
45    {
46        $this->httpClient = $httpClient;
47        $this->requestFactory = $requestFactory;
48        $this->uriFactory = $uriFactory;
49    }
50
51    public function getHttpClient(): ClientInterface
52    {
53        return $this->httpClient;
54    }
55
56    public function getRequestFactory(): RequestFactoryInterface
57    {
58        return $this->requestFactory;
59    }
60
61    public function getUriFactory(): UriFactoryInterface
62    {
63        return $this->uriFactory;
64    }
65
66    /**
67     * send a request and return the response
68     *
69     * @param Client::METHOD_* $method
70     * @param string $url
71     * @param array<string,string|string[]> $headers
72     *
73     * @throws ClientException if anything goes wrong requesting the data
74     */
75    public function request(string $method, string $url, array $headers = []): Response
76    {
77        if ($method !== self::METHOD_GET) {
78            throw new InvalidArgumentException(sprintf(
79                '%s(): Argument #1 ($method) only supports method "%s".',
80                __METHOD__,
81                self::METHOD_GET
82            ), 1);
83        }
84
85        if (preg_match('/^http(s)?:\/\//i', $url)) {
86            return $this->requestUrl($method, $url, $headers);
87        }
88
89        return $this->requestLocalFile($url);
90    }
91
92    /**
93     * @param array<string,string|string[]> $headers
94     */
95    private function requestUrl(string $method, string $url, array $headers): Response
96    {
97        $permanentUrl = $url;
98        $requestedUrl = $url;
99        $remainingRedirects = $this->allowedRedirects;
100
101        $request = $this->requestFactory->createRequest(
102            $method,
103            $this->uriFactory->createUri($requestedUrl)
104        );
105
106        foreach ($headers as $name => $value) {
107            $request = $request->withHeader($name, $value);
108        }
109
110        do {
111            $followRedirect = false;
112
113            try {
114                $response = $this->httpClient->sendRequest($request);
115            } catch (ClientExceptionInterface $th) {
116                throw new ClientException($th->getMessage(), $th->getCode(), $th);
117            }
118
119            $statusCode = $response->getStatusCode();
120
121            // If we have a redirect
122            if (in_array($statusCode, [300, 301, 302, 303, 307]) && $response->hasHeader('Location')) {
123                // Prevent infinity redirect loops
124                if ($remainingRedirects <= 0) {
125                    break;
126                }
127
128                $remainingRedirects--;
129                $followRedirect = true;
130
131                $requestedUrl = $response->getHeaderLine('Location');
132
133                if ($statusCode === 301) {
134                    $permanentUrl = $requestedUrl;
135                }
136
137                $request = $request->withUri($this->uriFactory->createUri($requestedUrl));
138            }
139        } while ($followRedirect);
140
141        return new Psr7Response($response, $permanentUrl, $requestedUrl);
142    }
143
144    private function requestLocalFile(string $path): Response
145    {
146        if (!is_readable($path)) {
147            throw new ClientException(sprintf('file "%s" is not readable', $path));
148        }
149
150        try {
151            $raw = file_get_contents($path);
152        } catch (Throwable $th) {
153            throw new ClientException($th->getMessage(), $th->getCode(), $th);
154        }
155
156        if ($raw === false) {
157            throw new ClientException('file_get_contents() could not read the file', 1);
158        }
159
160        return new RawTextResponse($raw, $path);
161    }
162}
163