• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

68publishers / file-storage / 22677890135

04 Mar 2026 04:07PM UTC coverage: 88.344%. First build
22677890135

push

github

tg666
Added port to Referer header if exists

0 of 3 new or added lines in 1 file covered. (0.0%)

576 of 652 relevant lines covered (88.34%)

0.88 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

53.13
/src/Resource/ResourceFactory.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace SixtyEightPublishers\FileStorage\Resource;
6

7
use finfo;
8
use League\Flysystem\FilesystemException as LeagueFilesystemException;
9
use League\Flysystem\FilesystemReader;
10
use League\Flysystem\UnableToRetrieveMetadata;
11
use Psr\Http\Message\StreamInterface;
12
use RuntimeException;
13
use SixtyEightPublishers\FileStorage\Exception\FileNotFoundException;
14
use SixtyEightPublishers\FileStorage\Exception\FilesystemException;
15
use SixtyEightPublishers\FileStorage\PathInfoInterface;
16
use function array_key_exists;
17
use function array_shift;
18
use function error_clear_last;
19
use function error_get_last;
20
use function explode;
21
use function file_exists;
22
use function filter_var;
23
use function fopen;
24
use function fread;
25
use function fstat;
26
use function ftell;
27
use function fwrite;
28
use function get_debug_type;
29
use function implode;
30
use function is_file;
31
use function is_resource;
32
use function parse_url;
33
use function rewind;
34
use function sprintf;
35
use function str_starts_with;
36
use function stream_context_create;
37
use function stream_get_meta_data;
38
use function strlen;
39
use function strtolower;
40
use function trim;
41
use const FILEINFO_MIME_TYPE;
42

43
final class ResourceFactory implements ResourceFactoryInterface
44
{
45
    public function __construct(
1✔
46
        private readonly FilesystemReader $filesystemReader,
47
    ) {}
1✔
48

49
    /**
50
     * @throws FileNotFoundException
51
     * @throws LeagueFilesystemException
52
     * @throws FilesystemException
53
     */
54
    public function createResource(PathInfoInterface $pathInfo): ResourceInterface
55
    {
56
        $path = $pathInfo->getPath();
1✔
57

58
        if (false === $this->filesystemReader->fileExists($path)) {
1✔
59
            throw new FileNotFoundException($path);
1✔
60
        }
61

62
        try {
63
            $source = $this->filesystemReader->readStream($path);
1✔
64
        } catch (LeagueFilesystemException $e) {
1✔
65
            throw new FilesystemException(
1✔
66
                message: sprintf(
1✔
67
                    'Can not read stream from file "%s".',
1✔
68
                    $path,
69
                ),
70
                previous: $e,
71
            );
72
        }
73

74
        return new StreamResource(
1✔
75
            pathInfo: $pathInfo,
76
            source: $source,
77
            mimeType: function () use ($path): ?string {
1✔
78
                try {
79
                    return $this->filesystemReader->mimeType($path);
1✔
80
                } catch (LeagueFilesystemException|UnableToRetrieveMetadata $e) {
×
81
                    return null;
×
82
                }
83
            },
1✔
84
            filesize: function () use ($path): ?int {
1✔
85
                try {
86
                    return $this->filesystemReader->fileSize($path);
1✔
87
                } catch (LeagueFilesystemException|UnableToRetrieveMetadata $e) {
×
88
                    return null;
×
89
                }
90
            },
1✔
91
        );
92
    }
93

94
    public function createResourceFromFile(PathInfoInterface $pathInfo, string $filename): ResourceInterface
95
    {
96
        return match (true) {
97
            (bool) filter_var($filename, FILTER_VALIDATE_URL) => $this->getResourceFromUrl(
1✔
98
                pathInfo: $pathInfo,
×
99
                url: $filename,
100
            ),
101
            file_exists($filename) && is_file($filename) => $this->getResourceFromLocalFile(
1✔
102
                pathInfo: $pathInfo,
1✔
103
                filename: $filename,
104
            ),
105
            default => throw new FileNotFoundException($filename),
1✔
106
        };
107
    }
108

109
    public function createResourceFromPsrStream(PathInfoInterface $pathInfo, StreamInterface $stream): ResourceInterface
110
    {
111
        $size = $stream->getSize();
1✔
112
        $streamCopy = clone $stream;
1✔
113
        $source = $streamCopy->detach();
1✔
114

115
        # For non resource based streams:
116
        if (!is_resource($source)) {
1✔
117
            $source = @fopen('php://temp', 'rb+');
1✔
118

119
            if (false === $source) {
1✔
120
                throw new FilesystemException(
×
121
                    message: sprintf(
×
122
                        'Unable to create temporary stream for PSR-7 stream of type %s.',
×
123
                        get_debug_type($stream),
×
124
                    ),
125
                );
126
            }
127

128
            try {
129
                $stream->rewind();
1✔
130
            } catch (RuntimeException $e) {
1✔
131
                # ignore
132
            }
133

134
            # clone data to the resource
135
            while (!$stream->eof()) {
1✔
136
                $chunk = $stream->read(8192);
1✔
137
                if ('' === $chunk) {
1✔
138
                    break;
×
139
                }
140

141
                fwrite($source, $chunk);
1✔
142
            }
143
            rewind($source);
1✔
144
        }
145

146
        return new StreamResource(
1✔
147
            pathInfo: $pathInfo,
148
            source: $source,
149
            mimeType: function (StreamResource $resource): ?string {
1✔
150
                $source = $resource->getSource();
1✔
151

152
                if (ftell($source) !== 0 && stream_get_meta_data($source)['seekable']) {
1✔
153
                    rewind($source);
1✔
154
                }
155

156
                $finfo = new finfo(FILEINFO_MIME_TYPE);
1✔
157
                $mimeType = null;
1✔
158
                $chunk = fread($source, 1024) ?: '';
1✔
159

160
                rewind($source);
1✔
161

162
                if ('' !== $chunk) {
1✔
163
                    $mimeType = $finfo->buffer($chunk) ?: null;
1✔
164
                }
165

166
                if (null === $mimeType) {
1✔
167
                    $mimeType = @mime_content_type($resource->getSource()) ?: null;
×
168
                }
169

170
                return $mimeType;
1✔
171
            },
1✔
172
            filesize: $size ?? function (StreamResource $resource): ?int {
1✔
173
                $stat = fstat($resource->getSource());
×
174

175
                return false !== $stat && array_key_exists('size', $stat) ? (int) $stat['size'] : null;
×
176
            },
1✔
177
        );
178
    }
179

180
    /**
181
     * @throws FilesystemException
182
     */
183
    private function getResourceFromUrl(PathInfoInterface $pathInfo, string $url): ResourceInterface
184
    {
185
        error_clear_last();
×
186

187
        $headers = [
×
188
            "Accept-language: en\r\n",
189
            "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36\r\n",
190
        ];
191

192
        $parsedUrl = parse_url($url) ?: [];
×
193

194
        if (isset($parsedUrl['scheme'], $parsedUrl['host'])) {
×
NEW
195
            $host = $parsedUrl['host'];
×
196

NEW
197
            if (isset($parsedUrl['port'])) {
×
NEW
198
                $host .= ':' . $parsedUrl['port'];
×
199
            }
200

201
            $headers[] = sprintf(
×
202
                "Referer: %s://%s\r\n",
×
203
                $parsedUrl['scheme'],
×
204
                $host,
205
            );
206
        }
207

208
        $context = stream_context_create(
×
209
            options: [
210
                'http' => [
211
                    'method' => 'GET',
×
212
                    'protocol_version' => 1.1,
×
213
                    'follow_location' => true,
214
                    'header' => implode('', $headers),
×
215
                ],
216
            ],
217
        );
218

219
        $source = @fopen(
×
220
            filename: $url,
×
221
            mode: 'rb',
×
222
            context: $context,
223
        );
224

225
        if (false === $source) {
×
226
            throw new FilesystemException(
×
227
                message: sprintf(
×
228
                    'Can not read stream from url "%s". %s',
×
229
                    $url,
230
                    error_get_last()['message'] ?? '',
×
231
                ),
232
            );
233
        }
234

235
        $headers = stream_get_meta_data($source)['wrapper_data'] ?? [];
×
236

237
        return new StreamResource(
×
238
            pathInfo: $pathInfo,
239
            source: $source,
240
            mimeType: function () use ($headers): ?string {
×
241
                $contentTypeHeader = $this->getHeaderValue(
×
242
                    headers: $headers,
243
                    name: 'Content-Type',
×
244
                );
245

246
                if (null === $contentTypeHeader) {
×
247
                    return null;
×
248
                }
249

250
                $parts = explode(
×
251
                    separator: ';',
×
252
                    string: $contentTypeHeader,
253
                );
254

255
                return array_shift($parts);
×
256
            },
×
257
            filesize: function () use ($headers): ?int {
×
258
                $filesize = $this->getHeaderValue(
×
259
                    headers: $headers,
260
                    name: 'Content-Length',
×
261
                );
262

263
                return null !== $filesize ? (int) $filesize : null;
×
264
            },
×
265
        );
266
    }
267

268
    /**
269
     * @throws FilesystemException
270
     */
271
    private function getResourceFromLocalFile(PathInfoInterface $pathInfo, string $filename): ResourceInterface
272
    {
273
        error_clear_last();
1✔
274

275
        $source = @fopen(
1✔
276
            filename: $filename,
277
            mode: 'rb',
1✔
278
        );
279

280
        if (false === $source) {
1✔
281
            throw new FilesystemException(
1✔
282
                message: sprintf(
1✔
283
                    'Can not read stream from file "%s". %s',
1✔
284
                    $filename,
285
                    error_get_last()['message'] ?? '',
1✔
286
                ),
287
            );
288
        }
289

290
        return new StreamResource(
1✔
291
            pathInfo: $pathInfo,
292
            source: $source,
293
            mimeType: function (StreamResource $resource): ?string {
1✔
294
                $mimeType = @mime_content_type($resource->getSource());
1✔
295

296
                return false === $mimeType ? null : $mimeType;
1✔
297
            },
1✔
298
            filesize: function () use ($filename): ?int {
1✔
299
                $filesize = @filesize(
1✔
300
                    filename: $filename,
1✔
301
                );
302

303
                return false === $filesize ? null : $filesize;
1✔
304
            },
1✔
305
        );
306
    }
307

308
    /**
309
     * @param array<int, string> $headers
310
     */
311
    private function getHeaderValue(array $headers, string $name): ?string
312
    {
313
        $name = strtolower($name);
×
314

315
        foreach ($headers as $header) {
×
316
            $header = trim(strtolower($header));
×
317

318
            if (!str_starts_with($header, $name . ':')) {
×
319
                continue;
×
320
            }
321

322
            $value = substr(
×
323
                string: $header,
324
                offset: strlen($name) + 1,
×
325
            );
326

327
            return trim($value);
×
328
        }
329

330
        return null;
×
331
    }
332
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc