Completed
Push — master ( 51a9d2...f4880c )
by Freek
17:28
created

Policy::isKeyword()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 6
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Spatie\Csp\Policies;
4
5
use ReflectionClass;
6
use Spatie\Csp\Value;
7
use Spatie\Csp\Keyword;
8
use Spatie\Csp\Directive;
9
use Illuminate\Http\Request;
10
use Spatie\Csp\Exceptions\InvalidDirective;
11
use Symfony\Component\HttpFoundation\Response;
12
13
abstract class Policy
14
{
15
    protected $directives = [];
16
17
    protected $reportOnly = false;
18
19
    abstract public function configure();
20
21
    /**
22
     * @param string $directive
23
     * @param string|array|bool $values
24
     *
25
     * @return \Spatie\Csp\Policies\Policy
26
     *
27
     * @throws \Spatie\Csp\Exceptions\InvalidDirective
28
     */
29
    public function addDirective(string $directive, $values): self
30
    {
31
        $this->guardAgainstInvalidDirectives($directive);
32
33
        if ($values === Value::NO_VALUE) {
34
            $this->directives[$directive][] = Value::NO_VALUE;
35
36
            return $this;
37
        }
38
39
        $values = array_filter(array_flatten(array_map(function ($value) {
40
            return explode(' ', $value);
41
        }, array_wrap($values))));
42
43
        foreach ($values as $value) {
44
            $sanitizedValue = $this->sanitizeValue($value);
45
46
            if (! in_array($sanitizedValue, $this->directives[$directive] ?? [])) {
47
                $this->directives[$directive][] = $sanitizedValue;
48
            }
49
        }
50
51
        return $this;
52
    }
53
54
    public function reportOnly(): self
55
    {
56
        $this->reportOnly = true;
57
58
        return $this;
59
    }
60
61
    public function enforce(): self
62
    {
63
        $this->reportOnly = false;
64
65
        return $this;
66
    }
67
68
    public function reportTo(string $uri): self
69
    {
70
        $this->directives['report-uri'] = [$uri];
71
72
        return $this;
73
    }
74
75
    public function shouldBeApplied(Request $request, Response $response): bool
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $response is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
76
    {
77
        return config('csp.enabled');
78
    }
79
80
    public function addNonceForDirective(string $directive): self
81
    {
82
        return $this->addDirective($directive, "'nonce-".app('csp-nonce')."'");
83
    }
84
85
    public function applyTo(Response $response)
86
    {
87
        $this->configure();
88
89
        $headerName = $this->reportOnly
90
            ? 'Content-Security-Policy-Report-Only'
91
            : 'Content-Security-Policy';
92
93
        if ($response->headers->has($headerName)) {
94
            return;
95
        }
96
97
        $response->headers->set($headerName, (string) $this);
98
    }
99
100
    public function __toString()
101
    {
102
        return collect($this->directives)
103
            ->map(function (array $values, string $directive) {
104
                $valueString = implode(' ', $values);
105
106
                return empty($valueString) ? "{$directive}" : "{$directive} {$valueString}";
107
            })
108
            ->implode(';');
109
    }
110
111
    protected function guardAgainstInvalidDirectives(string $directive)
112
    {
113
        if (! Directive::isValid($directive)) {
114
            throw InvalidDirective::notSupported($directive);
115
        }
116
    }
117
118
    protected function isHash(string $value): bool
119
    {
120
        $acceptableHashingAlgorithms = [
121
          'sha256-',
122
          'sha384-',
123
          'sha512-',
124
        ];
125
126
        return starts_with($value, $acceptableHashingAlgorithms);
127
    }
128
129
    protected function isKeyword(string $value): bool
130
    {
131
        $keywords = (new ReflectionClass(Keyword::class))->getConstants();
132
133
        return in_array($value, $keywords);
134
    }
135
136
    protected function sanitizeValue(string $value): string
137
    {
138
        if (
139
            $this->isKeyword($value)
140
            || $this->isHash($value)
141
        ) {
142
            return "'{$value}'";
143
        }
144
145
        return $value;
146
    }
147
}
148