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

codeigniter4 / CodeIgniter4 / 21568681844

01 Feb 2026 07:16PM UTC coverage: 85.41% (+1.0%) from 84.387%
21568681844

push

github

web-flow
Merge pull request #9916 from codeigniter4/4.7

4.7.0 Merge code

1603 of 1888 new or added lines in 101 files covered. (84.9%)

31 existing lines in 11 files now uncovered.

22163 of 25949 relevant lines covered (85.41%)

205.52 hits per line

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

99.38
/system/HTTP/ContentSecurityPolicy.php
1
<?php
2

3
declare(strict_types=1);
4

5
/**
6
 * This file is part of CodeIgniter 4 framework.
7
 *
8
 * (c) CodeIgniter Foundation <admin@codeigniter.com>
9
 *
10
 * For the full copyright and license information, please view
11
 * the LICENSE file that was distributed with this source code.
12
 */
13

14
namespace CodeIgniter\HTTP;
15

16
use CodeIgniter\Exceptions\InvalidArgumentException;
17
use Config\App;
18
use Config\ContentSecurityPolicy as ContentSecurityPolicyConfig;
19

20
/**
21
 * Provides tools for working with the Content-Security-Policy header
22
 * to help defeat XSS attacks.
23
 *
24
 * @see http://www.w3.org/TR/CSP/
25
 * @see http://www.html5rocks.com/en/tutorials/security/content-security-policy/
26
 * @see http://content-security-policy.com/
27
 * @see https://www.owasp.org/index.php/Content_Security_Policy
28
 * @see \CodeIgniter\HTTP\ContentSecurityPolicyTest
29
 */
30
class ContentSecurityPolicy
31
{
32
    private const DIRECTIVES_ALLOWING_SOURCE_LISTS = [
33
        'base-uri'        => 'baseURI',
34
        'child-src'       => 'childSrc',
35
        'connect-src'     => 'connectSrc',
36
        'default-src'     => 'defaultSrc',
37
        'font-src'        => 'fontSrc',
38
        'form-action'     => 'formAction',
39
        'frame-ancestors' => 'frameAncestors',
40
        'frame-src'       => 'frameSrc',
41
        'img-src'         => 'imageSrc',
42
        'media-src'       => 'mediaSrc',
43
        'object-src'      => 'objectSrc',
44
        'plugin-types'    => 'pluginTypes',
45
        'script-src'      => 'scriptSrc',
46
        'style-src'       => 'styleSrc',
47
        'sandbox'         => 'sandbox',
48
        'manifest-src'    => 'manifestSrc',
49
        'script-src-elem' => 'scriptSrcElem',
50
        'script-src-attr' => 'scriptSrcAttr',
51
        'style-src-elem'  => 'styleSrcElem',
52
        'style-src-attr'  => 'styleSrcAttr',
53
        'worker-src'      => 'workerSrc',
54
    ];
55

56
    /**
57
     * Map of CSP directives to this class's properties.
58
     *
59
     * @var array<string, string>
60
     */
61
    protected array $directives = [
62
        ...self::DIRECTIVES_ALLOWING_SOURCE_LISTS,
63
        'report-uri' => 'reportURI',
64
        'report-to'  => 'reportTo',
65
    ];
66

67
    /**
68
     * The `base-uri` directive restricts the URLs that can be used to specify the document base URL.
69
     *
70
     * @var array<string, bool>|string|null
71
     */
72
    protected $baseURI = [];
73

74
    /**
75
     * The `child-src` directive governs the creation of nested browsing contexts as well
76
     * as Worker execution contexts.
77
     *
78
     * @var array<string, bool>|string
79
     */
80
    protected $childSrc = [];
81

82
    /**
83
     * The `connect-src` directive restricts which URLs the protected resource can load using script interfaces.
84
     *
85
     * @var array<string, bool>|string
86
     */
87
    protected $connectSrc = [];
88

89
    /**
90
     * The `default-src` directive sets a default source list for a number of directives.
91
     *
92
     * @var array<string, bool>|string|null
93
     */
94
    protected $defaultSrc = [];
95

96
    /**
97
     * The `font-src` directive restricts from where the protected resource can load fonts.
98
     *
99
     * @var array<string, bool>|string
100
     */
101
    protected $fontSrc = [];
102

103
    /**
104
     * The `form-action` directive restricts which URLs can be used as the action of HTML form elements.
105
     *
106
     * @var array<string, bool>|string
107
     */
108
    protected $formAction = [];
109

110
    /**
111
     * The `frame-ancestors` directive indicates whether the user agent should allow embedding
112
     * the resource using a `frame`, `iframe`, `object`, `embed` or `applet` element,
113
     * or equivalent functionality in non-HTML resources.
114
     *
115
     * @var array<string, bool>|string
116
     */
117
    protected $frameAncestors = [];
118

119
    /**
120
     * The `frame-src` directive restricts the URLs which may be loaded into child navigables.
121
     *
122
     * @var array<string, bool>|string
123
     */
124
    protected $frameSrc = [];
125

126
    /**
127
     * The `img-src` directive restricts from where the protected resource can load images.
128
     *
129
     * @var array<string, bool>|string
130
     */
131
    protected $imageSrc = [];
132

133
    /**
134
     * The `media-src` directive restricts from where the protected resource can load video,
135
     * audio, and associated text tracks.
136
     *
137
     * @var array<string, bool>|string
138
     */
139
    protected $mediaSrc = [];
140

141
    /**
142
     * The `object-src` directive restricts from where the protected resource can load plugins.
143
     *
144
     * @var array<string, bool>|string
145
     */
146
    protected $objectSrc = [];
147

148
    /**
149
     * The `plugin-types` directive restricts the set of plugins that can be invoked by the
150
     * protected resource by limiting the types of resources that can be embedded.
151
     *
152
     * @var array<string, bool>|string
153
     */
154
    protected $pluginTypes = [];
155

156
    /**
157
     * The `script-src` directive restricts which scripts the protected resource can execute.
158
     *
159
     * @var array<string, bool>|string
160
     */
161
    protected $scriptSrc = [];
162

163
    /**
164
     * The `style-src` directive restricts which styles the user may applies to the protected resource.
165
     *
166
     * @var array<string, bool>|string
167
     */
168
    protected $styleSrc = [];
169

170
    /**
171
     * The `sandbox` directive specifies an HTML sandbox policy that the user agent applies to the protected resource.
172
     *
173
     * @var array<string, bool>|string
174
     */
175
    protected $sandbox = [];
176

177
    /**
178
     * The `report-uri` directive specifies a URL to which the user agent sends reports about policy violation.
179
     *
180
     * @var string|null
181
     */
182
    protected $reportURI;
183

184
    /**
185
     * The `report-to` directive specifies a named group in a Reporting API
186
     * endpoint to which the user agent sends reports about policy violation.
187
     */
188
    protected ?string $reportTo = null;
189

190
    // --------------------------------------------------------------
191
    // CSP Level 3 Directives
192
    // --------------------------------------------------------------
193

194
    /**
195
     * The `manifest-src` directive restricts the URLs from which application manifests may be loaded.
196
     *
197
     * @var array<string, bool>|string
198
     */
199
    protected $manifestSrc = [];
200

201
    /**
202
     * The `script-src-elem` directive applies to all script requests and script blocks.
203
     *
204
     * @var array<string, bool>|string
205
     */
206
    protected array|string $scriptSrcElem = [];
207

208
    /**
209
     * The `script-src-attr` directive applies to event handlers and, if present,
210
     * it will override the `script-src` directive for relevant checks.
211
     *
212
     * @var array<string, bool>|string
213
     */
214
    protected array|string $scriptSrcAttr = [];
215

216
    /**
217
     * The `style-src-elem` directive governs the behaviour of styles except
218
     * for styles defined in inline attributes.
219
     *
220
     * @var array<string, bool>|string
221
     */
222
    protected array|string $styleSrcElem = [];
223

224
    /**
225
     * The `style-src-attr` directive governs the behaviour of style attributes.
226
     *
227
     * @var array<string, bool>|string
228
     */
229
    protected array|string $styleSrcAttr = [];
230

231
    /**
232
     * The `worker-src` directive restricts the URLs which may be loaded as a `Worker`,
233
     * `SharedWorker`, or `ServiceWorker`.
234
     *
235
     * @var array<string, bool>|string
236
     */
237
    protected array|string $workerSrc = [];
238

239
    /**
240
     * Instructs user agents to rewrite URL schemes by changing HTTP to HTTPS.
241
     *
242
     * @var bool
243
     */
244
    protected $upgradeInsecureRequests = false;
245

246
    /**
247
     * Set to `true` to make all directives report-only instead of enforced.
248
     *
249
     * @var bool
250
     */
251
    protected $reportOnly = false;
252

253
    /**
254
     * Set of valid keyword-sources.
255
     *
256
     * @see https://www.w3.org/TR/CSP3/#source-expression
257
     *
258
     * @var list<string>
259
     */
260
    protected $validSources = [
261
        // CSP2 keywords
262
        'self',
263
        'none',
264
        'unsafe-inline',
265
        'unsafe-eval',
266
        // CSP3 keywords
267
        'strict-dynamic',
268
        'unsafe-hashes',
269
        'report-sample',
270
        'unsafe-allow-redirects',
271
        'wasm-unsafe-eval',
272
        'trusted-types-eval',
273
        'report-sha256',
274
        'report-sha384',
275
        'report-sha512',
276
    ];
277

278
    /**
279
     * Set of nonces generated.
280
     *
281
     * @var list<string>
282
     *
283
     * @deprecated 4.7.0 Never used.
284
     */
285
    protected $nonces = [];
286

287
    /**
288
     * Nonce for style tags.
289
     *
290
     * @var string|null
291
     */
292
    protected $styleNonce;
293

294
    /**
295
     * Nonce for script tags.
296
     *
297
     * @var string|null
298
     */
299
    protected $scriptNonce;
300

301
    /**
302
     * Nonce placeholder for style tags.
303
     *
304
     * @var string
305
     */
306
    protected $styleNonceTag = '{csp-style-nonce}';
307

308
    /**
309
     * Nonce placeholder for script tags.
310
     *
311
     * @var string
312
     */
313
    protected $scriptNonceTag = '{csp-script-nonce}';
314

315
    /**
316
     * Replace nonce tags automatically?
317
     *
318
     * @var bool
319
     */
320
    protected $autoNonce = true;
321

322
    /**
323
     * An array of header info since we have to build
324
     * ourselves before passing to a Response object.
325
     *
326
     * @var array<string, string>
327
     */
328
    protected $tempHeaders = [];
329

330
    /**
331
     * An array of header info to build that should only be reported.
332
     *
333
     * @var array<string, string>
334
     */
335
    protected $reportOnlyHeaders = [];
336

337
    /**
338
     * Whether Content Security Policy is being enforced.
339
     *
340
     * @var bool
341
     */
342
    protected $CSPEnabled = false;
343

344
    /**
345
     * Map of reporting endpoints to their URLs.
346
     *
347
     * @var array<string, string>
348
     */
349
    private array $reportingEndpoints = [];
350

351
    /**
352
     * Stores our default values from the Config file.
353
     */
354
    public function __construct(ContentSecurityPolicyConfig $config)
355
    {
356
        $this->CSPEnabled = config(App::class)->CSPEnabled;
624✔
357

358
        foreach (get_object_vars($config) as $setting => $value) {
624✔
359
            if (! property_exists($this, $setting)) {
624✔
NEW
360
                continue;
×
361
            }
362

363
            if (
364
                in_array($setting, self::DIRECTIVES_ALLOWING_SOURCE_LISTS, true)
624✔
365
                && is_array($value)
624✔
366
                && array_is_list($value)
624✔
367
            ) {
368
                // Config sets these directives as `list<string>|string`
369
                // but we need them as `array<string, bool>` internally.
370
                $this->{$setting} = array_combine($value, array_fill(0, count($value), $this->reportOnly));
624✔
371

372
                continue;
624✔
373
            }
374

375
            $this->{$setting} = $value;
624✔
376
        }
377

378
        if (! is_array($this->styleSrc)) {
624✔
379
            $this->styleSrc = [$this->styleSrc => $this->reportOnly];
624✔
380
        }
381

382
        if (! is_array($this->scriptSrc)) {
624✔
383
            $this->scriptSrc = [$this->scriptSrc => $this->reportOnly];
624✔
384
        }
385
    }
386

387
    /**
388
     * Whether Content Security Policy is being enforced.
389
     */
390
    public function enabled(): bool
391
    {
392
        return $this->CSPEnabled;
140✔
393
    }
394

395
    /**
396
     * Get the nonce for the style tag.
397
     */
398
    public function getStyleNonce(): string
399
    {
400
        if ($this->styleNonce === null) {
8✔
401
            $this->styleNonce = base64_encode(random_bytes(12));
8✔
402
            $this->addStyleSrc('nonce-' . $this->styleNonce);
8✔
403
        }
404

405
        return $this->styleNonce;
8✔
406
    }
407

408
    /**
409
     * Get the nonce for the script tag.
410
     */
411
    public function getScriptNonce(): string
412
    {
413
        if ($this->scriptNonce === null) {
10✔
414
            $this->scriptNonce = base64_encode(random_bytes(12));
10✔
415
            $this->addScriptSrc('nonce-' . $this->scriptNonce);
10✔
416
        }
417

418
        return $this->scriptNonce;
10✔
419
    }
420

421
    /**
422
     * Compiles and sets the appropriate headers in the request.
423
     *
424
     * Should be called just prior to sending the response to the user agent.
425
     *
426
     * @return void
427
     */
428
    public function finalize(ResponseInterface $response)
429
    {
430
        if ($this->autoNonce) {
49✔
431
            $this->generateNonces($response);
47✔
432
        }
433

434
        $this->buildHeaders($response);
49✔
435
    }
436

437
    /**
438
     * If TRUE, nothing will be restricted. Instead all violations will
439
     * be reported to the reportURI for monitoring. This is useful when
440
     * you are just starting to implement the policy, and will help
441
     * determine what errors need to be addressed before you turn on
442
     * all filtering.
443
     *
444
     * @return $this
445
     */
446
    public function reportOnly(bool $value = true)
447
    {
448
        $this->reportOnly = $value;
10✔
449

450
        return $this;
10✔
451
    }
452

453
    /**
454
     * Adds a new value to the `base-uri` directive.
455
     *
456
     * `base-uri` restricts the URLs that can appear in a page's <base> element.
457
     *
458
     * @see http://www.w3.org/TR/CSP/#directive-base-uri
459
     *
460
     * @param list<string>|string $uri
461
     *
462
     * @return $this
463
     */
464
    public function addBaseURI($uri, ?bool $explicitReporting = null)
465
    {
466
        $this->addOption($uri, 'baseURI', $explicitReporting ?? $this->reportOnly);
3✔
467

468
        return $this;
3✔
469
    }
470

471
    /**
472
     * Adds a new value to the `child-src` directive.
473
     *
474
     * `child-src` lists the URLs for workers and embedded frame contents.
475
     * For example: child-src https://youtube.com would enable embedding
476
     * videos from YouTube but not from other origins.
477
     *
478
     * @see http://www.w3.org/TR/CSP/#directive-child-src
479
     *
480
     * @param list<string>|string $uri
481
     *
482
     * @return $this
483
     */
484
    public function addChildSrc($uri, ?bool $explicitReporting = null)
485
    {
486
        $this->addOption($uri, 'childSrc', $explicitReporting ?? $this->reportOnly);
2✔
487

488
        return $this;
2✔
489
    }
490

491
    /**
492
     * Adds a new value to the `connect-src` directive.
493
     *
494
     * `connect-src` limits the origins to which you can connect
495
     * (via XHR, WebSockets, and EventSource).
496
     *
497
     * @see http://www.w3.org/TR/CSP/#directive-connect-src
498
     *
499
     * @param list<string>|string $uri
500
     *
501
     * @return $this
502
     */
503
    public function addConnectSrc($uri, ?bool $explicitReporting = null)
504
    {
505
        $this->addOption($uri, 'connectSrc', $explicitReporting ?? $this->reportOnly);
1✔
506

507
        return $this;
1✔
508
    }
509

510
    /**
511
     * Adds a new value to the `default-src` directive.
512
     *
513
     * `default-src` is the URI that is used for many of the settings when
514
     * no other source has been set.
515
     *
516
     * @see http://www.w3.org/TR/CSP/#directive-default-src
517
     *
518
     * @param list<string>|string $uri
519
     *
520
     * @return $this
521
     */
522
    public function setDefaultSrc($uri, ?bool $explicitReporting = null)
523
    {
524
        $this->defaultSrc = [(string) $uri => $explicitReporting ?? $this->reportOnly];
1✔
525

526
        return $this;
1✔
527
    }
528

529
    /**
530
     * Adds a new value to the `font-src` directive.
531
     *
532
     * `font-src` specifies the origins that can serve web fonts.
533
     *
534
     * @see http://www.w3.org/TR/CSP/#directive-font-src
535
     *
536
     * @param list<string>|string $uri
537
     *
538
     * @return $this
539
     */
540
    public function addFontSrc($uri, ?bool $explicitReporting = null)
541
    {
542
        $this->addOption($uri, 'fontSrc', $explicitReporting ?? $this->reportOnly);
3✔
543

544
        return $this;
3✔
545
    }
546

547
    /**
548
     * Adds a new value to the `form-action` directive.
549
     *
550
     * @see http://www.w3.org/TR/CSP/#directive-form-action
551
     *
552
     * @param list<string>|string $uri
553
     *
554
     * @return $this
555
     */
556
    public function addFormAction($uri, ?bool $explicitReporting = null)
557
    {
558
        $this->addOption($uri, 'formAction', $explicitReporting ?? $this->reportOnly);
2✔
559

560
        return $this;
2✔
561
    }
562

563
    /**
564
     * Adds a new value to the `frame-ancestors` directive.
565
     *
566
     * @see http://www.w3.org/TR/CSP/#directive-frame-ancestors
567
     *
568
     * @param list<string>|string $uri
569
     *
570
     * @return $this
571
     */
572
    public function addFrameAncestor($uri, ?bool $explicitReporting = null)
573
    {
574
        $this->addOption($uri, 'frameAncestors', $explicitReporting ?? $this->reportOnly);
2✔
575

576
        return $this;
2✔
577
    }
578

579
    /**
580
     * Adds a new value to the `frame-src` directive.
581
     *
582
     * @see http://www.w3.org/TR/CSP/#directive-frame-src
583
     *
584
     * @param list<string>|string $uri
585
     *
586
     * @return $this
587
     */
588
    public function addFrameSrc($uri, ?bool $explicitReporting = null)
589
    {
590
        $this->addOption($uri, 'frameSrc', $explicitReporting ?? $this->reportOnly);
2✔
591

592
        return $this;
2✔
593
    }
594

595
    /**
596
     * Adds a new value to the `img-src` directive.
597
     *
598
     * @see http://www.w3.org/TR/CSP/#directive-img-src
599
     *
600
     * @param list<string>|string $uri
601
     *
602
     * @return $this
603
     */
604
    public function addImageSrc($uri, ?bool $explicitReporting = null)
605
    {
606
        $this->addOption($uri, 'imageSrc', $explicitReporting ?? $this->reportOnly);
5✔
607

608
        return $this;
5✔
609
    }
610

611
    /**
612
     * Adds a new value to the `media-src` directive.
613
     *
614
     * @see http://www.w3.org/TR/CSP/#directive-media-src
615
     *
616
     * @param list<string>|string $uri
617
     *
618
     * @return $this
619
     */
620
    public function addMediaSrc($uri, ?bool $explicitReporting = null)
621
    {
622
        $this->addOption($uri, 'mediaSrc', $explicitReporting ?? $this->reportOnly);
2✔
623

624
        return $this;
2✔
625
    }
626

627
    /**
628
     * Adds a new value to the `manifest-src` directive.
629
     *
630
     * @see https://www.w3.org/TR/CSP/#directive-manifest-src
631
     *
632
     * @param list<string>|string $uri
633
     *
634
     * @return $this
635
     */
636
    public function addManifestSrc($uri, ?bool $explicitReporting = null)
637
    {
638
        $this->addOption($uri, 'manifestSrc', $explicitReporting ?? $this->reportOnly);
2✔
639

640
        return $this;
2✔
641
    }
642

643
    /**
644
     * Adds a new value to the `object-src` directive.
645
     *
646
     * @see http://www.w3.org/TR/CSP/#directive-object-src
647
     *
648
     * @param list<string>|string $uri
649
     *
650
     * @return $this
651
     */
652
    public function addObjectSrc($uri, ?bool $explicitReporting = null)
653
    {
654
        $this->addOption($uri, 'objectSrc', $explicitReporting ?? $this->reportOnly);
2✔
655

656
        return $this;
2✔
657
    }
658

659
    /**
660
     * Adds a new value to the `plugin-types` directive.
661
     *
662
     * @see http://www.w3.org/TR/CSP/#directive-plugin-types
663
     *
664
     * @param list<string>|string $mime
665
     *
666
     * @return $this
667
     */
668
    public function addPluginType($mime, ?bool $explicitReporting = null)
669
    {
670
        $this->addOption($mime, 'pluginTypes', $explicitReporting ?? $this->reportOnly);
2✔
671

672
        return $this;
2✔
673
    }
674

675
    /**
676
     * Adds a new value to the `sandbox` directive.
677
     *
678
     * @see http://www.w3.org/TR/CSP/#directive-sandbox
679
     *
680
     * @param list<string>|string $flags
681
     *
682
     * @return $this
683
     */
684
    public function addSandbox($flags, ?bool $explicitReporting = null)
685
    {
686
        $this->addOption($flags, 'sandbox', $explicitReporting ?? $this->reportOnly);
2✔
687

688
        return $this;
2✔
689
    }
690

691
    /**
692
     * Adds a new value to the `script-src` directive.
693
     *
694
     * @see http://www.w3.org/TR/CSP/#directive-script-src
695
     *
696
     * @param list<string>|string $uri
697
     *
698
     * @return $this
699
     */
700
    public function addScriptSrc($uri, ?bool $explicitReporting = null)
701
    {
702
        $this->addOption($uri, 'scriptSrc', $explicitReporting ?? $this->reportOnly);
13✔
703

704
        return $this;
13✔
705
    }
706

707
    /**
708
     * Adds a new value to the `script-src-elem` directive.
709
     *
710
     * @see https://www.w3.org/TR/CSP/#directive-script-src-elem
711
     *
712
     * @param list<string>|string $uri
713
     */
714
    public function addScriptSrcElem(array|string $uri, ?bool $explicitReporting = null): static
715
    {
716
        $this->addOption($uri, 'scriptSrcElem', $explicitReporting ?? $this->reportOnly);
1✔
717

718
        return $this;
1✔
719
    }
720

721
    /**
722
     * Adds a new value to the `script-src-attr` directive.
723
     *
724
     * @see https://www.w3.org/TR/CSP/#directive-script-src-attr
725
     *
726
     * @param list<string>|string $uri
727
     */
728
    public function addScriptSrcAttr(array|string $uri, ?bool $explicitReporting = null): static
729
    {
730
        $this->addOption($uri, 'scriptSrcAttr', $explicitReporting ?? $this->reportOnly);
1✔
731

732
        return $this;
1✔
733
    }
734

735
    /**
736
     * Adds a new value to the `style-src` directive.
737
     *
738
     * @see http://www.w3.org/TR/CSP/#directive-style-src
739
     *
740
     * @param list<string>|string $uri
741
     *
742
     * @return $this
743
     */
744
    public function addStyleSrc($uri, ?bool $explicitReporting = null)
745
    {
746
        $this->addOption($uri, 'styleSrc', $explicitReporting ?? $this->reportOnly);
12✔
747

748
        return $this;
12✔
749
    }
750

751
    /**
752
     * Adds a new value to the `style-src-elem` directive.
753
     *
754
     * @see https://www.w3.org/TR/CSP/#directive-style-src-elem
755
     *
756
     * @param list<string>|string $uri
757
     */
758
    public function addStyleSrcElem(array|string $uri, ?bool $explicitReporting = null): static
759
    {
760
        $this->addOption($uri, 'styleSrcElem', $explicitReporting ?? $this->reportOnly);
1✔
761

762
        return $this;
1✔
763
    }
764

765
    /**
766
     * Adds a new value to the `style-src-attr` directive.
767
     *
768
     * @see https://www.w3.org/TR/CSP/#directive-style-src-attr
769
     *
770
     * @param list<string>|string $uri
771
     */
772
    public function addStyleSrcAttr(array|string $uri, ?bool $explicitReporting = null): static
773
    {
774
        $this->addOption($uri, 'styleSrcAttr', $explicitReporting ?? $this->reportOnly);
1✔
775

776
        return $this;
1✔
777
    }
778

779
    /**
780
     * Adds a new value to the `worker-src` directive.
781
     *
782
     * @see https://www.w3.org/TR/CSP/#directive-worker-src
783
     *
784
     * @param list<string>|string $uri
785
     */
786
    public function addWorkerSrc($uri, ?bool $explicitReporting = null): static
787
    {
788
        $this->addOption($uri, 'workerSrc', $explicitReporting ?? $this->reportOnly);
1✔
789

790
        return $this;
1✔
791
    }
792

793
    /**
794
     * Sets whether the user agents should rewrite URL schemes, changing HTTP to HTTPS.
795
     *
796
     * @return $this
797
     */
798
    public function upgradeInsecureRequests(bool $value = true)
799
    {
800
        $this->upgradeInsecureRequests = $value;
1✔
801

802
        return $this;
1✔
803
    }
804

805
    /**
806
     * Specifies a URL where a browser will send reports when a content
807
     * security policy is violated.
808
     *
809
     * @see http://www.w3.org/TR/CSP/#directive-report-uri
810
     *
811
     * @param string $uri URL to send reports. Set `''` if you want to remove
812
     *                    this directive at runtime.
813
     *
814
     * @return $this
815
     */
816
    public function setReportURI(string $uri)
817
    {
818
        $this->reportURI = $uri;
3✔
819

820
        return $this;
3✔
821
    }
822

823
    /**
824
     * Specifies a named group in a Reporting API endpoint to which the user
825
     * agent sends reports about policy violation.
826
     *
827
     * @see https://www.w3.org/TR/CSP/#directive-report-to
828
     *
829
     * @param string $endpoint The name of the reporting endpoint. Set `''` if you
830
     *                         want to remove this directive at runtime.
831
     */
832
    public function setReportToEndpoint(string $endpoint): static
833
    {
834
        if ($endpoint === '') {
5✔
835
            $this->reportURI = null;
1✔
836
            $this->reportTo  = null;
1✔
837

838
            return $this;
1✔
839
        }
840

841
        if (! array_key_exists($endpoint, $this->reportingEndpoints)) {
5✔
842
            throw new InvalidArgumentException(sprintf('The reporting endpoint "%s" has not been defined.', $endpoint));
1✔
843
        }
844

845
        $this->reportURI = $this->reportingEndpoints[$endpoint]; // for BC with browsers that do not support `report-to`
4✔
846
        $this->reportTo  = $endpoint;
4✔
847

848
        return $this;
4✔
849
    }
850

851
    /**
852
     * Adds reporting endpoints to the `Reporting-Endpoints` header.
853
     *
854
     * @param array<string, string> $endpoint
855
     */
856
    public function addReportingEndpoints(array $endpoint): static
857
    {
858
        foreach ($endpoint as $name => $url) {
4✔
859
            $this->reportingEndpoints[$name] = $url;
4✔
860
        }
861

862
        return $this;
4✔
863
    }
864

865
    /**
866
     * DRY method to add an string or array to a class property.
867
     *
868
     * @param list<string>|string $options
869
     *
870
     * @return void
871
     */
872
    protected function addOption($options, string $target, ?bool $explicitReporting = null)
873
    {
874
        // Ensure we have an array to work with...
875
        if (is_string($this->{$target})) {
46✔
876
            $this->{$target} = [$this->{$target} => $this->reportOnly];
12✔
877
        }
878

879
        $options = is_array($options) ? $options : [$options];
46✔
880

881
        foreach ($options as $option) {
46✔
882
            $this->{$target}[$option] = $explicitReporting ?? $this->reportOnly;
46✔
883
        }
884
    }
885

886
    /**
887
     * Scans the body of the request message and replaces any nonce
888
     * placeholders with actual nonces, that we'll then add to our
889
     * headers.
890
     *
891
     * @return void
892
     */
893
    protected function generateNonces(ResponseInterface $response)
894
    {
895
        $body = (string) $response->getBody();
47✔
896

897
        if ($body === '') {
47✔
898
            return;
2✔
899
        }
900

901
        // Replace style and script placeholders with nonces
902
        $pattern = sprintf('/(%s|%s)/', preg_quote($this->styleNonceTag, '/'), preg_quote($this->scriptNonceTag, '/'));
45✔
903

904
        $body = preg_replace_callback($pattern, function ($match): string {
45✔
905
            $nonce = $match[0] === $this->styleNonceTag ? $this->getStyleNonce() : $this->getScriptNonce();
4✔
906

907
            return "nonce=\"{$nonce}\"";
4✔
908
        }, $body);
45✔
909

910
        $response->setBody($body);
45✔
911
    }
912

913
    /**
914
     * Based on the current state of the elements, will add the appropriate
915
     * Content-Security-Policy and Content-Security-Policy-Report-Only headers
916
     * with their values to the response object.
917
     *
918
     * @return void
919
     */
920
    protected function buildHeaders(ResponseInterface $response)
921
    {
922
        $response->setHeader('Content-Security-Policy', []);
49✔
923
        $response->setHeader('Content-Security-Policy-Report-Only', []);
49✔
924
        $response->setHeader('Reporting-Endpoints', []);
49✔
925

926
        if (in_array($this->baseURI, ['', null, []], true)) {
49✔
927
            $this->baseURI = 'self';
46✔
928
        }
929

930
        if (in_array($this->defaultSrc, ['', null, []], true)) {
49✔
931
            $this->defaultSrc = 'self';
47✔
932
        }
933

934
        foreach ($this->directives as $name => $property) {
49✔
935
            if ($name === 'report-uri' && (string) $this->reportURI === '') {
49✔
936
                continue;
46✔
937
            }
938

939
            if ($name === 'report-to' && (string) $this->reportTo === '') {
49✔
940
                continue;
47✔
941
            }
942

943
            if ($this->{$property} !== null) {
49✔
944
                $this->addToHeader($name, $this->{$property});
49✔
945
            }
946
        }
947

948
        // Compile our own header strings here since if we just
949
        // append it to the response, it will be joined with
950
        // commas, not semi-colons as we need.
951
        if ($this->reportingEndpoints !== []) {
49✔
952
            $endpoints = [];
4✔
953

954
            foreach ($this->reportingEndpoints as $name => $url) {
4✔
955
                $endpoints[] = trim("{$name}=\"{$url}\"");
4✔
956
            }
957

958
            $response->appendHeader('Reporting-Endpoints', implode(', ', $endpoints));
4✔
959
            $this->reportingEndpoints = [];
4✔
960
        }
961

962
        if ($this->tempHeaders !== []) {
49✔
963
            $header = [];
49✔
964

965
            foreach ($this->tempHeaders as $name => $value) {
49✔
966
                $header[] = trim("{$name} {$value}");
49✔
967
            }
968

969
            if ($this->upgradeInsecureRequests) {
49✔
970
                $header[] = 'upgrade-insecure-requests';
1✔
971
            }
972

973
            $response->appendHeader('Content-Security-Policy', implode('; ', $header));
49✔
974
            $this->tempHeaders = [];
49✔
975
        }
976

977
        if ($this->reportOnlyHeaders !== []) {
49✔
978
            $header = [];
17✔
979

980
            foreach ($this->reportOnlyHeaders as $name => $value) {
17✔
981
                $header[] = trim("{$name} {$value}");
17✔
982
            }
983

984
            $response->appendHeader('Content-Security-Policy-Report-Only', implode('; ', $header));
17✔
985
            $this->reportOnlyHeaders = [];
17✔
986
        }
987
    }
988

989
    /**
990
     * Adds a directive and its options to the appropriate header. The $values
991
     * array might have options that are geared toward either the regular or the
992
     * reportOnly header, since it's viable to have both simultaneously.
993
     *
994
     * @param array<string, bool>|string $values
995
     *
996
     * @return void
997
     */
998
    protected function addToHeader(string $name, $values = null)
999
    {
1000
        if (is_string($values)) {
49✔
1001
            $values = [$values => $this->reportOnly];
49✔
1002
        }
1003

1004
        $sources       = [];
49✔
1005
        $reportSources = [];
49✔
1006

1007
        foreach ($values as $value => $reportOnly) {
49✔
1008
            if (
1009
                in_array($value, $this->validSources, true)
49✔
1010
                || str_starts_with($value, 'nonce-')
35✔
1011
                || str_starts_with($value, 'sha256-')
32✔
1012
                || str_starts_with($value, 'sha384-')
32✔
1013
                || str_starts_with($value, 'sha512-')
49✔
1014
            ) {
1015
                $value = "'{$value}'";
49✔
1016
            }
1017

1018
            if ($reportOnly) {
49✔
1019
                $reportSources[] = $value;
17✔
1020
            } else {
1021
                $sources[] = $value;
49✔
1022
            }
1023
        }
1024

1025
        if ($sources !== []) {
49✔
1026
            $this->tempHeaders[$name] = implode(' ', $sources);
49✔
1027
        }
1028

1029
        if ($reportSources !== []) {
49✔
1030
            $this->reportOnlyHeaders[$name] = implode(' ', $reportSources);
17✔
1031
        }
1032
    }
1033

1034
    public function clearDirective(string $directive): void
1035
    {
1036
        if (! array_key_exists($directive, $this->directives)) {
2✔
1037
            return;
1✔
1038
        }
1039

1040
        if ($directive === 'report-uri') {
2✔
1041
            $this->reportURI = null;
1✔
1042

1043
            return;
1✔
1044
        }
1045

1046
        if ($directive === 'report-to') {
2✔
1047
            $this->reportURI = null;
1✔
1048
            $this->reportTo  = null;
1✔
1049

1050
            return;
1✔
1051
        }
1052

1053
        $this->{$this->directives[$directive]} = [];
2✔
1054
    }
1055
}
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