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

aplus-framework / http / 21082416990

16 Jan 2026 10:08PM UTC coverage: 98.78% (+0.008%) from 98.772%
21082416990

push

github

natanfelles
Update PHPDocs

1619 of 1639 relevant lines covered (98.78%)

14.36 hits per line

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

96.15
/src/CSP.php
1
<?php declare(strict_types=1);
2
/*
3
 * This file is part of Aplus Framework HTTP Library.
4
 *
5
 * (c) Natan Felles <natanfelles@gmail.com>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
namespace Framework\HTTP;
11

12
use InvalidArgumentException;
13
use LogicException;
14
use Stringable;
15

16
/**
17
 * Class CSP.
18
 *
19
 * @see https://content-security-policy.com/
20
 * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
21
 * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
22
 *
23
 * @package http
24
 */
25
class CSP implements Stringable
26
{
27
    /**
28
     * Restricts the URLs which can be used in a document's `<base>` element.
29
     *
30
     * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/base-uri
31
     */
32
    public const string baseUri = 'base-uri';
33
    /**
34
     * Defines the valid sources for web workers and nested browsing contexts
35
     * loaded using elements such as `<frame>` and `<iframe>`.
36
     *
37
     * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/child-src
38
     */
39
    public const string childSrc = 'child-src';
40
    /**
41
     * Restricts the URLs which can be loaded using script interfaces.
42
     *
43
     * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/connect-src
44
     */
45
    public const string connectSrc = 'connect-src';
46
    /**
47
     * Serves as a fallback for the other fetch directives.
48
     *
49
     * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/default-src
50
     */
51
    public const string defaultSrc = 'default-src';
52
    /**
53
     * Specifies valid sources for fonts loaded using `@font-face`.
54
     *
55
     * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/font-src
56
     */
57
    public const string fontSrc = 'font-src';
58
    /**
59
     * Restricts the URLs which can be used as the target of a form submissions
60
     * from a given context.
61
     *
62
     * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/form-action
63
     */
64
    public const string formAction = 'form-action';
65
    /**
66
     * Specifies valid parents that may embed a page using `<frame>`, `<iframe>`,
67
     * `<object>`, `<embed>`, or `<applet>`.
68
     *
69
     * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-ancestors
70
     */
71
    public const string frameAncestors = 'frame-ancestors';
72
    /**
73
     * Specifies valid sources for nested browsing contexts loading using
74
     * elements such as `<frame>` and `<iframe>`.
75
     *
76
     * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-src
77
     */
78
    public const string frameSrc = 'frame-src';
79
    /**
80
     * Specifies valid sources of images and favicons.
81
     *
82
     * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/img-src
83
     */
84
    public const string imgSrc = 'img-src';
85
    /**
86
     * Specifies valid sources of application manifest files.
87
     *
88
     * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/manifest-src
89
     */
90
    public const string manifestSrc = 'manifest-src';
91
    /**
92
     * Specifies valid sources for loading media using the `<audio>`, `<video>`
93
     * and `<track>` elements.
94
     *
95
     * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/media-src
96
     */
97
    public const string mediaSrc = 'media-src';
98
    /**
99
     * Restricts the URLs to which a document can initiate navigation by any
100
     * means, including `<form>` (if form-action is not specified), `<a>`,
101
     * `window.location`, `window.open`, etc.
102
     *
103
     * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/navigate-to
104
     */
105
    public const string navigateTo = 'navigate-to';
106
    /**
107
     * Specifies valid sources for the `<object>`, `<embed>`, and `<applet>`
108
     * elements.
109
     *
110
     * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/object-src
111
     */
112
    public const string objectSrc = 'object-src';
113
    /**
114
     * Restricts the set of plugins that can be embedded into a document by
115
     * limiting the types of resources which can be loaded.
116
     *
117
     * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/plugin-types
118
     * @deprecated
119
     */
120
    public const string pluginTypes = 'plugin-types';
121
    /**
122
     * Specifies valid sources to be prefetched or prerendered.
123
     *
124
     * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/prefetch-src
125
     * @deprecated
126
     */
127
    public const string prefetchSrc = 'prefetch-src';
128
    /**
129
     * Fires a SecurityPolicyViolationEvent.
130
     *
131
     * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/report-to
132
     */
133
    public const string reportTo = 'report-to';
134
    /**
135
     * Instructs the user agent to report attempts to violate the Content
136
     * Security Policy. These violation reports consist of JSON documents sent
137
     * via an HTTP POST request to the specified URI.
138
     *
139
     * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/report-uri
140
     * @deprecated
141
     */
142
    public const string reportUri = 'report-uri';
143
    /**
144
     * Enables a sandbox for the requested resource similar to the `<iframe>`
145
     * sandbox attribute.
146
     *
147
     * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/sandbox
148
     */
149
    public const string sandbox = 'sandbox';
150
    /**
151
     * Specifies valid sources for JavaScript and WebAssembly resources.
152
     *
153
     * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src
154
     */
155
    public const string scriptSrc = 'script-src';
156
    /**
157
     * Specifies valid sources for JavaScript inline event handlers.
158
     *
159
     * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src-attr
160
     */
161
    public const string scriptSrcAttr = 'script-src-attr';
162
    /**
163
     * Specifies valid sources for JavaScript `<script>` elements.
164
     *
165
     * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src-elem
166
     */
167
    public const string scriptSrcElem = 'script-src-elem';
168
    /**
169
     * Specifies valid sources for stylesheets.
170
     *
171
     * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/style-src
172
     */
173
    public const string styleSrc = 'style-src';
174
    /**
175
     * Specifies valid sources for inline styles applied to individual DOM
176
     * elements.
177
     *
178
     * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/style-src-attr
179
     */
180
    public const string styleSrcAttr = 'style-src-attr';
181
    /**
182
     * Specifies valid sources for stylesheets `<style>` elements and `<link>`
183
     * elements with `rel="stylesheet"`.
184
     *
185
     * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/style-src-elem
186
     */
187
    public const string styleSrcElem = 'style-src-elem';
188
    /**
189
     * Instructs user agents to treat all of a site's insecure URLs (those
190
     * served over HTTP) as though they have been replaced with secure URLs
191
     * (those served over HTTPS). This directive is intended for websites with
192
     * large numbers of insecure legacy URLs that need to be rewritten.
193
     *
194
     * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/upgrade-insecure-requests
195
     */
196
    public const string upgradeInsecureRequests = 'upgrade-insecure-requests';
197
    /**
198
     * Specifies valid sources for Worker, SharedWorker, or ServiceWorker
199
     * scripts.
200
     *
201
     * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/worker-src
202
     */
203
    public const string workerSrc = 'worker-src';
204
    /**
205
     * @var array<string,array<string>>
206
     */
207
    protected array $directives = [];
208

209
    /**
210
     * @param array<string,array<string>> $directives
211
     */
212
    public function __construct(array $directives = [])
213
    {
214
        if ($directives) {
12✔
215
            $this->setDirectives($directives);
7✔
216
        }
217
    }
218

219
    public function __toString() : string
220
    {
221
        return $this->render();
1✔
222
    }
223

224
    public function render() : string
225
    {
226
        if (empty($this->directives)) {
5✔
227
            throw new LogicException('No CSP directive has been set');
1✔
228
        }
229
        $directives = [];
4✔
230
        foreach ($this->directives as $name => $values) {
4✔
231
            $values = \implode(' ', $values);
4✔
232
            $directive = $name . ' ' . $values;
4✔
233
            $directives[] = \trim($directive);
4✔
234
        }
235
        return \implode('; ', $directives) . ';';
4✔
236
    }
237

238
    /**
239
     * @param string $directive
240
     * @param array<string>|string $values
241
     *
242
     * @return static
243
     */
244
    public function addValues(string $directive, array | string $values) : static
245
    {
246
        $directive = \strtolower($directive);
3✔
247
        $values = (array) $values;
3✔
248
        $this->directives[$directive] ??= [];
3✔
249
        foreach ($values as $value) {
3✔
250
            $this->directives[$directive][] = $this->sanitizeValue($value);
3✔
251
        }
252
        return $this;
3✔
253
    }
254

255
    protected function sanitizeValue(string $value) : string
256
    {
257
        if (\in_array($value, [
11✔
258
                'none',
11✔
259
                'self',
11✔
260
                'strict-dynamic',
11✔
261
                'unsafe-eval',
11✔
262
                'unsafe-hashes',
11✔
263
                'unsafe-inline',
11✔
264
            ])
11✔
265
            || \str_starts_with($value, 'nonce-')
7✔
266
            || \str_starts_with($value, 'sha256-')
7✔
267
            || \str_starts_with($value, 'sha384-')
7✔
268
            || \str_starts_with($value, 'sha512-')
11✔
269
        ) {
270
            return "'{$value}'";
9✔
271
        }
272
        return \trim($value);
7✔
273
    }
274

275
    /**
276
     * @param string $name
277
     * @param array<string>|string $values
278
     *
279
     * @return static
280
     */
281
    public function setDirective(string $name, array | string $values) : static
282
    {
283
        $values = (array) $values;
8✔
284
        foreach ($values as &$value) {
8✔
285
            $value = $this->sanitizeValue($value);
8✔
286
        }
287
        unset($value);
8✔
288
        $this->directives[\strtolower($name)] = $values;
8✔
289
        return $this;
8✔
290
    }
291

292
    /**
293
     * @param array<string,array<string>> $directives
294
     *
295
     * @return static
296
     */
297
    public function setDirectives(array $directives) : static
298
    {
299
        foreach ($directives as $name => $values) {
8✔
300
            $this->setDirective($name, $values);
8✔
301
        }
302
        return $this;
8✔
303
    }
304

305
    /**
306
     * @param string $name
307
     *
308
     * @return array<string>|null
309
     */
310
    public function getDirective(string $name) : ?array
311
    {
312
        return $this->directives[\strtolower($name)] ?? null;
5✔
313
    }
314

315
    /**
316
     * @return array<string,array<string>>
317
     */
318
    public function getDirectives() : array
319
    {
320
        return $this->directives;
2✔
321
    }
322

323
    public function removeDirective(string $name) : static
324
    {
325
        unset($this->directives[\strtolower($name)]);
1✔
326
        return $this;
1✔
327
    }
328

329
    /**
330
     * @param string $type
331
     *
332
     * @see https://content-security-policy.com/nonce/
333
     *
334
     * @return string
335
     */
336
    protected function addNonce(string $type) : string
337
    {
338
        $nonce = \bin2hex(\random_bytes(8));
2✔
339
        $this->addValues($type, "'nonce-{$nonce}'");
2✔
340
        return $nonce;
2✔
341
    }
342

343
    protected function getNonceAttr(string $type) : string
344
    {
345
        $nonce = match ($type) {
2✔
346
            static::scriptSrc => $this->addNonce(static::scriptSrc),
2✔
347
            static::styleSrc => $this->addNonce(static::styleSrc),
1✔
348
            default => throw new InvalidArgumentException(
×
349
                'Invalid CSP directive: ' . $type
×
350
            ),
×
351
        };
2✔
352
        return ' nonce="' . $nonce . '"';
2✔
353
    }
354

355
    /**
356
     * Creates a nonce, adds it to the script-src directive, and returns the
357
     * attribute to be inserted into the script tag.
358
     *
359
     * @return string the nonce attribute
360
     */
361
    public function getScriptNonceAttr() : string
362
    {
363
        return $this->getNonceAttr(static::scriptSrc);
1✔
364
    }
365

366
    /**
367
     * Creates a nonce, adds it to the style-src directive, and returns the
368
     * attribute to be inserted into the style tag.
369
     *
370
     * @return string the nonce attribute
371
     */
372
    public function getStyleNonceAttr() : string
373
    {
374
        return $this->getNonceAttr(static::styleSrc);
1✔
375
    }
376

377
    /**
378
     * @param string $html
379
     *
380
     * @return array<string>
381
     */
382
    public static function getStyleHashes(string $html) : array
383
    {
384
        return static::makeHashes(static::getStyleContents($html));
1✔
385
    }
386

387
    /**
388
     * @see https://stackoverflow.com/a/72636724
389
     * @see https://stackoverflow.com/a/50124875
390
     *
391
     * @param string $html
392
     *
393
     * @return array<string>
394
     */
395
    public static function getStyleContents(string $html) : array
396
    {
397
        \preg_match_all(
2✔
398
            '#<style[\w="\'\s-]*>([^<]+)</style>#i',
2✔
399
            $html,
2✔
400
            $matches
2✔
401
        );
2✔
402
        return $matches[1];
2✔
403
    }
404

405
    /**
406
     * @param string $html
407
     *
408
     * @return array<string>
409
     */
410
    public static function getScriptHashes(string $html) : array
411
    {
412
        return static::makeHashes(static::getScriptContents($html));
1✔
413
    }
414

415
    /**
416
     * @param string $html
417
     *
418
     * @return array<string>
419
     */
420
    public static function getScriptContents(string $html) : array
421
    {
422
        \preg_match_all(
2✔
423
            '#<script[\w="\'\s-]*>([^<]+)</script>#i',
2✔
424
            $html,
2✔
425
            $matches
2✔
426
        );
2✔
427
        return $matches[1];
2✔
428
    }
429

430
    /**
431
     * @param array<string> $contents
432
     * @param string $algo
433
     *
434
     * @return array<string>
435
     */
436
    public static function makeHashes(
437
        array $contents,
438
        string $algo = 'sha256'
439
    ) : array {
440
        $hashes = [];
3✔
441
        foreach ($contents as $content) {
3✔
442
            $hashes[] = static::makeHash($algo, $content);
3✔
443
        }
444
        return $hashes;
3✔
445
    }
446

447
    /**
448
     * @see https://content-security-policy.com/hash/
449
     * @see https://security.stackexchange.com/q/58789
450
     *
451
     * @param string $algo
452
     * @param string $content
453
     *
454
     * @return string
455
     */
456
    public static function makeHash(string $algo, string $content) : string
457
    {
458
        $content = \hash($algo, $content, true);
4✔
459
        $content = \base64_encode($content);
4✔
460
        return $algo . '-' . $content;
4✔
461
    }
462
}
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