1+ <?php
2+
3+ namespace Zaxbux \SecurityHeaders \Classes ;
4+
5+ use Cache ;
6+ use Illuminate \Http \Response ;
7+ use Zaxbux \SecurityHeaders \Models \Settings ;
8+ use Zaxbux \SecurityHeaders \Classes \HttpHeader ;
9+
10+ class HeaderBuilder {
11+
12+ const CACHE_KEY_CONTENT_SECURITY_POLICY = "zaxbux_securityheaders_csp " ;
13+ const CACHE_KEY_STRICT_TRANSPORT_SECURITY = "zaxbux_securityheaders_hsts " ;
14+ const CACHE_KEY_REFERRER_POLICY = "zaxbux_securityheaders_ref_policy " ;
15+ const CACHE_KEY_FRAME_OPTIONS = "zaxbux_securityheaders_frame_options " ;
16+ const CACHE_KEY_CONTENT_TYPE_OPTIONS = "zaxbux_securityheaders_content_type " ;
17+ const CACHE_KEY_XSS_PROTECTION = "zaxbux_securityheaders_xss " ;
18+
19+
20+ /**
21+ * Add the Content-Security-Policy or Content-Security-Policy-Report-Only header to the response
22+ *
23+ * @param Illuminate\Http\Response
24+ */
25+ public static function addContentSecurityPolicy (Response $ response , $ nonce ) {
26+ $ header = Cache::rememberForever (self ::CACHE_KEY_CONTENT_SECURITY_POLICY , function () {
27+ $ policy = Settings::get ('csp ' );
28+
29+ if (!$ policy ['enabled ' ]) {
30+ return false ;
31+ }
32+
33+ return self ::buildContentSecurityPolicyHeader ($ policy );
34+ });
35+
36+ if ($ header ) {
37+ $ response ->header ($ header ->getName (), \sprintf ($ header ->getValue (), $ nonce ));
38+ }
39+ }
40+
41+ /**
42+ * Add the Strict-Transport-Security header to the response
43+ *
44+ * @param Illuminate\Http\Response
45+ */
46+ public static function addStrictTransportSecurity (Response $ response ) {
47+ $ header = Cache::rememberForever (self ::CACHE_KEY_STRICT_TRANSPORT_SECURITY , function () {
48+ if (!Settings::get ('hsts_enable ' )) {
49+ return false ;
50+ }
51+
52+ $ value = sprintf ('max-age=%d ' , Settings::get ('hsts_max_age ' ));
53+
54+ if (Settings::get ('hsts_subdomains ' )) {
55+ $ value .= '; includeSubDomains ' ;
56+ }
57+
58+ if (Settings::get ('hsts_preload ' )) {
59+ $ value .= '; preload ' ;
60+ }
61+
62+ return new HttpHeader ('Strict-Transport-Security ' , $ value );
63+ });
64+
65+ if ($ header ) {
66+ $ response ->header ($ header ->getName (), $ header ->getValue ());
67+ }
68+ }
69+
70+ /**
71+ * Add the Referrer-Policy header to the response
72+ *
73+ * @param Illuminate\Http\Response
74+ */
75+ public static function addReferrerPolicy (Response $ response ) {
76+ $ header = Cache::rememberForever (self ::CACHE_KEY_REFERRER_POLICY , function () {
77+ if ($ value = Settings::get ('referrer_policy ' )) {
78+ return new HttpHeader ('Referrer-Policy ' , $ value );
79+ }
80+
81+ return false ;
82+ });
83+
84+ if ($ header ) {
85+ $ response ->header ($ header ->getName (), $ header ->getValue ());
86+ }
87+ }
88+
89+ /**
90+ * Add the Frame-options header to the response
91+ *
92+ * @param Illuminate\Http\Response
93+ */
94+ public static function addFrameOptions (Response $ response ) {
95+ $ header = Cache::rememberForever (self ::CACHE_KEY_FRAME_OPTIONS , function () {
96+ if (Settings::get ('frame_options ' )) {
97+ return new HttpHeader ('X-Frame-Options ' , 'nosniff ' );
98+ }
99+
100+ return false ;
101+ });
102+
103+ if ($ header ) {
104+ $ response ->header ($ header ->getName (), $ header ->getValue ());
105+ }
106+ }
107+
108+ /**
109+ * Add the X-Content-Type-Options header to the response
110+ *
111+ * @param Illuminate\Http\Response
112+ */
113+ public static function addContentTypeOptions (Response $ response ) {
114+ $ header = Cache::rememberForever (self ::CACHE_KEY_CONTENT_TYPE_OPTIONS , function () {
115+ if ($ value = Settings::get ('content_type_options ' )) {
116+ return new HttpHeader ('X-Content-Type-Options ' , $ value );
117+ }
118+
119+ return false ;
120+ });
121+
122+ if ($ header ) {
123+ $ response ->header ($ header ->getName (), $ header ->getValue ());
124+ }
125+ }
126+
127+ /**
128+ * Add the X-XSS-Protection header to the response
129+ *
130+ * @param Illuminate\Http\Response
131+ */
132+ public static function addXSSProtection (Response $ response ) {
133+ $ header = Cache::rememberForever (self ::CACHE_KEY_XSS_PROTECTION , function () {
134+ $ value = Settings::get ('xss_protection ' );
135+
136+ switch ($ value ) {
137+ case 'disable ' :
138+ $ value = '0 ' ;
139+ break ;
140+ case 'enable ' :
141+ $ value = '1 ' ;
142+ break ;
143+ case 'block ' :
144+ $ value = '1; mode=block ' ;
145+ break ;
146+ default :
147+ return false ;
148+ }
149+
150+ return new HttpHeader ('X-XSS-Protection ' , $ value );
151+ });
152+
153+ if ($ header ) {
154+ $ response ->header ($ header ->getName (), $ header ->getValue ());
155+ }
156+ }
157+
158+ private static function buildContentSecurityPolicyHeader ($ policy ) {
159+ $ header = new HttpHeader ('Content-Security-Policy ' );
160+
161+ $ directives = [];
162+
163+ if ($ policy ['report-only ' ]) {
164+ $ header ->setName ('Content-Security-Policy-Report-Only ' );
165+ }
166+
167+ foreach ($ policy as $ directive => $ value ) {
168+ if (\in_array ($ directive , array_merge (Settings::CSP_FETCH_DIRECTIVES , Settings::CSP_NAVIGATION_DIRECTIVES , ['base-uri ' ]))) {
169+ $ directives [] = self ::parseCSPDirectiveSources ($ directive , $ value );
170+ }
171+
172+ if ($ directive == 'plugin-types ' ) {
173+ $ types = [];
174+
175+ foreach ($ value ['types ' ] as $ type ) {
176+ $ types [] = $ type ['value ' ];
177+ }
178+
179+ if (count ($ types ) > 0 ) {
180+ $ directives [] = sprintf ('plugin-types %s; ' , \join (' ' , $ types ));
181+ }
182+ }
183+
184+ if ($ directive == 'sandbox ' && $ value ) {
185+ $ directives [] = \sprintf ('sandbox %s; ' , $ value );
186+ }
187+
188+ if ($ directive == 'upgrade-insecure-requests ' && $ value == true ) {
189+ $ directives [] = 'upgrade-insecure-requests; ' ;
190+ }
191+
192+ if ($ directive == 'block-all-mixed-content ' && $ value == true ) {
193+ $ directives [] = 'block-all-mixed-content; ' ;
194+ }
195+ }
196+
197+ if (count (array_filter ($ directives )) > 0 ) {
198+ return $ header ->setValue (\join (' ' , $ directives ));
199+ }
200+
201+ return false ;
202+ }
203+
204+ private static function parseCSPDirectiveSources ($ directive , $ sourceData ) {
205+ $ sources = [];
206+
207+ foreach ($ sourceData as $ source => $ data ) {
208+ // User-provided URIs and hashes
209+ if ($ source == '_sources ' ) {
210+ foreach ($ data as $ value ) {
211+ if (!empty ($ value ['value ' ])) {
212+ $ sources [] = $ value ['value ' ];
213+ }
214+ }
215+
216+ continue ;
217+ }
218+
219+ if ($ source == 'nonce ' && $ data == true ) {
220+ // %1$s is replaced with the nonce on every response
221+ $ sources [] = "'nonce-%1 \$s' " ;
222+
223+ continue ;
224+ }
225+
226+ // For checkboxes
227+ if ($ data == true ) {
228+ $ sources [] = \sprintf ("'%s' " , $ source );
229+ }
230+ }
231+
232+ if (count ($ sources ) > 0 ) {
233+ return \sprintf ('%s %s; ' , $ directive , \join (' ' , $ sources ));
234+ }
235+ }
236+ }
0 commit comments