PluginProbe
HTTP Headers / 1.19.1
HTTP Headers v1.19.1
1.19.5 trunk 1.0.0 1.1.0 1.1.1 1.1.2 1.10.0 1.10.1 1.10.2 1.10.3 1.10.4 1.10.5 1.11.0 1.12.0 1.12.1 1.12.2 1.13.0 1.13.1 1.13.2 1.13.3 1.13.4 1.14.0 1.14.1 1.14.2 1.15.0 All 60 releases
http-headers / http-headers.php

http-headers.php in HTTP Headers 1.19.1, at http-headers.php

1,621 lines 56.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /*
3 Plugin Name: HTTP Headers
4 Plugin URI: https://github.com/riverside/http-headers
5 Description: A plugin for HTTP headers management including security, access-control (CORS), caching, compression, and authentication.
6 Version: 1.19.1
7 Author: Dimitar Ivanov
8 Author URI: https://github.com/riverside
9 License: GPLv2 or later
10 Text Domain: http-headers
11 */
12
13 /*
14 This program is free software; you can redistribute it and/or
15 modify it under the terms of the GNU General Public License
16 as published by the Free Software Foundation; either version 2
17 of the License, or (at your option) any later version.
18
19 This program is distributed in the hope that it will be useful,
20 but WITHOUT ANY WARRANTY; without even the implied warranty of
21 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 GNU General Public License for more details.
23
24 You should have received a copy of the GNU General Public License
25 along with this program. If not, see <http://www.gnu.org/copyleft/gpl.html>.
26
27 Copyright (c) 2017-2023 Dimitar Ivanov
28 */
29
30 if (!defined('ABSPATH')) {
31 exit;
32 }
33
34 $options = include dirname(__FILE__) . '/views/includes/options.inc.php';
35 foreach ($options as $option) {
36 if (get_option($option[0]) === false) {
37 add_option($option[0], $option[1], null, 'yes');
38 }
39 }
40
41 function build_csp_value($value, $escape=false) {
42 if (!is_array($value))
43 {
44 return NULL;
45 }
46 $csp = array();
47 foreach ($value as $key => $val)
48 {
49 if (is_array($val))
50 {
51 $source = NULL;
52 if (isset($val['source']))
53 {
54 $source = $val['source'];
55 unset($val['source']);
56 }
57 if (!empty($val))
58 {
59 $val = join(" ", array_keys($val));
60 if ($source)
61 {
62 $val .= " " . $source;
63 }
64 $csp[] = sprintf("%s %s", $key, $escape ? esc_html($val) : $val);
65 } elseif ($source) {
66 $csp[] = sprintf("%s %s", $key, $escape ? esc_html($source) : $source);
67 }
68 } else {
69 if (in_array($key, array('block-all-mixed-content', 'upgrade-insecure-requests')))
70 {
71 $csp[] = $key;
72 }
73 if (in_array($key, array('plugin-types', 'report-to')) && !empty($val))
74 {
75 $csp[] = sprintf("%s %s", $key, $escape ? esc_html($val) : $val);
76 }
77 }
78 }
79
80 if (!$csp)
81 {
82 return NULL;
83 }
84
85 return join('; ', $csp);
86 }
87
88 function get_htaccess_filename() {
89 return get_option('hh_htaccess_path');
90 }
91
92 function get_user_ini_filename() {
93 return get_option('hh_user_ini_path');
94 }
95
96 function get_htpasswd_filename() {
97 return get_option('hh_htpasswd_path');
98 }
99
100 function get_htdigest_filename() {
101 return get_option('hh_htdigest_path');
102 }
103
104 function get_http_headers() {
105 $statuses = array();
106 $unset = array();
107 $headers = array();
108 $append = array();
109 if (get_option('hh_x_frame_options') == 1) {
110 $x_frame_options_value = strtoupper(get_option('hh_x_frame_options_value'));
111 if ($x_frame_options_value == 'ALLOW-FROM') {
112 $x_frame_options_value .= ' ' . get_option('hh_x_frame_options_domain');
113 }
114 $headers['X-Frame-Options'] = $x_frame_options_value;
115 }
116 if (get_option('hh_x_powered_by') == 1) {
117 if (get_option('hh_x_powered_by_option') == 'set') {
118 $headers['X-Powered-By'] = get_option('hh_x_powered_by_value');
119 } else {
120 $unset[] = 'X-Powered-By';
121 }
122 }
123 if (get_option('hh_x_xxs_protection') == 1) {
124 $headers['X-XSS-Protection'] = get_option('hh_x_xxs_protection_value');
125 if ($headers['X-XSS-Protection'] == '1; report=') {
126 $headers['X-XSS-Protection'] .= get_option('hh_x_xxs_protection_uri');
127 }
128 }
129 if (get_option('hh_x_content_type_options') == 1) {
130 $headers['X-Content-Type-Options'] = get_option('hh_x_content_type_options_value');
131 }
132 if (get_option('hh_x_download_options') == 1) {
133 $headers['X-Download-Options'] = get_option('hh_x_download_options_value');
134 }
135 if (get_option('hh_x_permitted_cross_domain_policies') == 1) {
136 $headers['X-Permitted-Cross-Domain-Policies'] = get_option('hh_x_permitted_cross_domain_policies_value');
137 }
138 if (get_option('hh_x_dns_prefetch_control') == 1) {
139 $headers['X-DNS-Prefetch-Control'] = get_option('hh_x_dns_prefetch_control_value');
140 }
141 if (get_option('hh_connection') == 1) {
142 $headers['Connection'] = get_option('hh_connection_value');
143 }
144 if (get_option('hh_pragma') == 1) {
145 $headers['Pragma'] = get_option('hh_pragma_value');
146 }
147 if (get_option('hh_age') == 1) {
148 $headers['Age'] = sprintf("%u", get_option('hh_age_value'));
149 }
150 if (get_option('hh_cache_control') == 1) {
151 $hh_cache_control_value = get_option('hh_cache_control_value', array());
152 $tmp = array();
153 foreach ($hh_cache_control_value as $k => $v) {
154 if (in_array($k, array('max-age', 's-maxage', 'stale-while-revalidate', 'stale-if-error'))) {
155 if (strlen($v) > 0) {
156 $tmp[] = sprintf("%s=%u", $k, $v);
157 }
158 } else {
159 $tmp[] = $k;
160 }
161 }
162 $hh_cache_control_value = join(', ', $tmp);
163 $headers['Cache-Control'] = $hh_cache_control_value;
164 }
165 if (get_option('hh_strict_transport_security') == 1) {
166 $hh_strict_transport_security = array();
167
168 $hh_strict_transport_security_max_age = get_option('hh_strict_transport_security_max_age');
169 if ($hh_strict_transport_security_max_age !== false)
170 {
171 $hh_strict_transport_security[] = sprintf('max-age=%u', get_option('hh_strict_transport_security_max_age'));
172 if (get_option('hh_strict_transport_security_sub_domains'))
173 {
174 $hh_strict_transport_security[] = 'includeSubDomains';
175 }
176 if (get_option('hh_strict_transport_security_preload'))
177 {
178 $hh_strict_transport_security[] = 'preload';
179 }
180 } else {
181 $hh_strict_transport_security = array(get_option('hh_strict_transport_security_value'));
182 }
183 $headers['Strict-Transport-Security'] = join('; ', $hh_strict_transport_security);
184 }
185 if (get_option('hh_x_ua_compatible') == 1) {
186 $headers['X-UA-Compatible'] = get_option('hh_x_ua_compatible_value');
187 }
188
189 if (get_option('hh_content_security_policy') == 1)
190 {
191 $value = get_option('hh_content_security_policy_value');
192 $csp = build_csp_value($value);
193 if ($csp)
194 {
195 $csp_report_only = get_option('hh_content_security_policy_report_only');
196 $headers['Content-Security-Policy'.($csp_report_only ? '-Report-Only' : NULL)] = $csp;
197 }
198 }
199
200 if (get_option('hh_access_control_allow_origin') == 1)
201 {
202 $value = get_option('hh_access_control_allow_origin_value');
203 switch ($value)
204 {
205 case 'origin':
206 $value = get_option('hh_access_control_allow_origin_url', array());
207 if (is_scalar($value))
208 {
209 $value = array($value);
210 }
211 break;
212 }
213 if (!empty($value))
214 {
215 $headers['Access-Control-Allow-Origin'] = $value;
216 }
217 }
218 if (get_option('hh_access_control_allow_credentials') == 1)
219 {
220 $headers['Access-Control-Allow-Credentials'] = get_option('hh_access_control_allow_credentials_value');
221 }
222 if (get_option('hh_access_control_max_age') == 1)
223 {
224 $value = get_option('hh_access_control_max_age_value');
225 if (!empty($value))
226 {
227 $headers['Access-Control-Max-Age'] = intval($value);
228 }
229 }
230 if (get_option('hh_access_control_allow_methods') == 1)
231 {
232 $value = get_option('hh_access_control_allow_methods_value');
233 if (!empty($value))
234 {
235 $headers['Access-Control-Allow-Methods'] = join(', ', array_keys($value));
236 }
237 }
238 if (get_option('hh_access_control_allow_headers') == 1)
239 {
240 $tmp = array();
241 $value = get_option('hh_access_control_allow_headers_value');
242 if (!empty($value))
243 {
244 $tmp = array_merge($tmp, array_keys($value));
245 }
246 $custom = get_option('hh_access_control_allow_headers_custom');
247 if (!empty($custom))
248 {
249 $tmp = array_merge($tmp, $custom);
250 }
251 if ($tmp)
252 {
253 $tmp = array_filter($tmp, 'trim');
254 $tmp = array_unique($tmp);
255 $headers['Access-Control-Allow-Headers'] = join(', ', $tmp);
256 }
257 }
258 if (get_option('hh_access_control_expose_headers') == 1)
259 {
260 $tmp = array();
261 $value = get_option('hh_access_control_expose_headers_value');
262 if (!empty($value))
263 {
264 $tmp = array_merge($tmp, array_keys($value));
265 }
266 $custom = get_option('hh_access_control_expose_headers_custom');
267 if (!empty($custom))
268 {
269 $tmp = array_merge($tmp, $custom);
270 }
271 if ($tmp)
272 {
273 $tmp = array_filter($tmp, 'trim');
274 $tmp = array_unique($tmp);
275 $headers['Access-Control-Expose-Headers'] = join(', ', $tmp);
276 }
277 }
278 if (get_option('hh_p3p') == 1)
279 {
280 $value = get_option('hh_p3p_value');
281 if (!empty($value))
282 {
283 $headers['P3P'] = 'CP="' . join(' ', array_keys($value)) . '"';
284 }
285 }
286 if (get_option('hh_referrer_policy') == 1) {
287 $headers['Referrer-Policy'] = get_option('hh_referrer_policy_value');
288 }
289 if (get_option('hh_cross_origin_resource_policy') == 1) {
290 $headers['Cross-Origin-Resource-Policy'] = get_option('hh_cross_origin_resource_policy_value');
291 }
292 if (get_option('hh_cross_origin_embedder_policy') == 1) {
293 $headers['Cross-Origin-Embedder-Policy'] = get_option('hh_cross_origin_embedder_policy_value');
294 }
295 if (get_option('hh_cross_origin_opener_policy') == 1) {
296 $headers['Cross-Origin-Opener-Policy'] = get_option('hh_cross_origin_opener_policy_value');
297 }
298 if (get_option('hh_www_authenticate') == 1) {
299
300 switch (get_option('hh_www_authenticate_type')) {
301 case 'Basic':
302 if (!(isset($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW'])
303 && $_SERVER['PHP_AUTH_USER'] == get_option('hh_www_authenticate_user')
304 && $_SERVER['PHP_AUTH_PW'] == get_option('hh_www_authenticate_pswd'))) {
305 $headers['WWW-Authenticate'] = sprintf("Basic realm='%s'", get_option('hh_www_authenticate_realm'));
306 $statuses['HTTP/1.1'] = '401 Unauthorized';
307 }
308 break;
309 case 'Digest':
310 if (empty($_SERVER['PHP_AUTH_DIGEST'])) {
311 $realm = get_option('hh_www_authenticate_realm');
312 $headers['WWW-Authenticate'] = sprintf("Digest realm='%s',qop='auth',nonce='%s',opaque='%s'",
313 $realm, uniqid(), md5($realm));
314 $statuses['HTTP/1.1'] = '401 Unauthorized';
315 }
316 break;
317 }
318 }
319 if (get_option('hh_vary') == 1)
320 {
321 $value = get_option('hh_vary_value');
322 if (!empty($value))
323 {
324 $append['Vary'] = join(', ', array_keys($value));
325 }
326 }
327
328 if (get_option('hh_expect_ct') == 1) {
329 $expect_ct_max_age = get_option('hh_expect_ct_max_age');
330 $expect_ct_report_uri = get_option('hh_expect_ct_report_uri');
331 if (!empty($expect_ct_report_uri) && !empty($expect_ct_max_age)) {
332
333 $expect_ct = array();
334 $expect_ct[] = sprintf("max-age=%u", $expect_ct_max_age);
335 if (get_option('hh_expect_ct_enforce') == 1) {
336 $expect_ct[] = "enforce";
337 }
338 $expect_ct[] = sprintf('report-uri="%s"', $expect_ct_report_uri);
339 $headers['Expect-CT'] = join(', ', $expect_ct);
340 }
341 }
342 if (get_option('hh_custom_headers') == 1) {
343 $custom_headers = get_option('hh_custom_headers_value');
344 if (isset($custom_headers['name'], $custom_headers['value']) && !empty($custom_headers['name'])) {
345 foreach ($custom_headers['name'] as $key => $name) {
346 $name = trim($name);
347 $value = trim($custom_headers['value'][$key]);
348 if (empty($name) || empty($value)) {
349 continue;
350 }
351 $headers[$name] = $value;
352 }
353 }
354 }
355
356 $value = get_http_header('report_to');
357 if ($value) {
358 $headers['Report-To'] = $value;
359 }
360
361 $value = get_http_header('nel');
362 if ($value) {
363 $headers['NEL'] = $value;
364 }
365
366 $value = get_http_header('feature_policy');
367 if ($value) {
368 $headers['Feature-Policy'] = $value;
369 }
370
371 $value = get_http_header('permissions_policy');
372 if ($value) {
373 $headers['Permissions-Policy'] = $value;
374 }
375
376 $value = get_http_header('x_robots_tag');
377 if ($value) {
378 $headers['X-Robots-Tag'] = $value;
379 }
380
381 return array($headers, $statuses, $unset, $append);
382 }
383
384 function get_http_header($header_name) {
385 $fn = sprintf('get_%s_header', $header_name);
386 if (!function_exists($fn)) {
387 return NULL;
388 }
389
390 return call_user_func($fn);
391 }
392
393 function get_report_to_header() {
394 if (get_option('hh_report_to') != 1) {
395 return NULL;
396 }
397 $report_to = get_option('hh_report_to_value');
398 $tmp = array();
399 foreach ($report_to as $item) {
400 $endpoints = array();
401 foreach ($item['endpoints'] as $endpoint) {
402 $endpoints[] = sprintf('{"url": "%s"%s%s}',
403 $endpoint['url'],
404 is_numeric($endpoint['priority']) ? sprintf(', "priority": %u', $endpoint['priority']) : NULL,
405 is_numeric($endpoint['weight']) ? sprintf(', "weight": %u', $endpoint['weight']) : NULL
406 );
407 }
408
409 $tmp[] = sprintf('{"max_age": %u%s%s, "endpoints": [%s]}',
410 $item['max_age'],
411 $item['group'] ? sprintf(', "group": "%s"', $item['group']) : NULL,
412 $item['include_subdomains'] ? sprintf(', "include_subdomains": true') : NULL,
413 join(", ", $endpoints)
414 );
415 }
416
417 return join(', ', $tmp);
418 }
419
420 function get_x_robots_tag_header() {
421 if (get_option('hh_x_robots_tag') != 1) {
422 return NULL;
423 }
424
425 $hh_x_robots_tag_value = get_option('hh_x_robots_tag_value', array());
426 $tmp = array();
427 foreach ($hh_x_robots_tag_value as $k => $v) {
428 if ($k == 'max-snippet') {
429 if (is_numeric($v) && $v >= -1) {
430 $tmp[] = "$k:$v";
431 }
432 } elseif ($k == 'max-image-preview') {
433 if (!empty($v)) {
434 $tmp[] = "$k:$v";
435 }
436 } elseif ($k == 'max-video-preview') {
437 if (is_numeric($v) && $v >= -1) {
438 $tmp[] = "$k:$v";
439 }
440 } elseif ($k == 'unavailable_after') {
441 if (!empty($v)) {
442 $tmp[] = "$k:$v";
443 }
444 } else {
445 $tmp[] = $k;
446 }
447 }
448 return join(', ', $tmp);
449 }
450
451 function get_nel_header() {
452 if (get_option('hh_nel') != 1) {
453 return NULL;
454 }
455
456 $nel = get_option('hh_nel_value', array());
457 return sprintf('{"report_to": "%s", "max_age": %u%s%s%s%s%s}',
458 isset($nel['report_to']) ? $nel['report_to'] : NULL,
459 isset($nel['max_age']) ? $nel['max_age'] : NULL,
460 isset($nel['include_subdomains']) ? ', "include_subdomains": true' : NULL,
461 array_key_exists('success_fraction', $nel) && is_numeric($nel['success_fraction']) ? ', "success_fraction": '. $nel['success_fraction'] : NULL,
462 array_key_exists('failure_fraction', $nel) && is_numeric($nel['failure_fraction']) ? ', "failure_fraction": '. $nel['failure_fraction'] : NULL,
463 isset($nel['request_headers']) && !empty($nel['request_headers']) ? sprintf(', "request_headers": ["%s"]', join('", "', array_map('trim', explode(',', $nel['request_headers'])))) : NULL,
464 isset($nel['response_headers']) && !empty($nel['response_headers']) ? sprintf(', "response_headers": ["%s"]', join('", "', array_map('trim', explode(',', $nel['response_headers'])))) : NULL
465 );
466 }
467
468 function get_feature_policy_header() {
469 if (get_option('hh_feature_policy') != 1) {
470 return NULL;
471 }
472 $feature_policy_feature = get_option('hh_feature_policy_feature');
473 $feature_policy_value = get_option('hh_feature_policy_value');
474 $feature_policy_origin = get_option('hh_feature_policy_origin');
475 $tmp = array();
476 $feature_policy_feature = is_array($feature_policy_feature) ? $feature_policy_feature : array();
477 foreach (array_keys($feature_policy_feature) as $feature) {
478 $value = NULL;
479 switch ($feature_policy_value[$feature]) {
480 case '*':
481 case "'none'":
482 $value = $feature_policy_value[$feature];
483 break;
484 case "'self'":
485 $value = $feature_policy_value[$feature];
486 if (!empty($feature_policy_origin[$feature])) {
487 $value .= " " . $feature_policy_origin[$feature];
488 }
489 break;
490 case 'origin(s)':
491 $value = $feature_policy_origin[$feature];
492 break;
493 }
494
495 $tmp[] = sprintf("%s %s", $feature, $value);
496 }
497
498 return join('; ', $tmp);
499 }
500
501 function get_permissions_policy_header() {
502 if (get_option('hh_permissions_policy') != 1) {
503 return NULL;
504 }
505 $permissions_policy_feature = get_option('hh_permissions_policy_feature');
506 $permissions_policy_value = get_option('hh_permissions_policy_value');
507 $permissions_policy_origin = get_option('hh_permissions_policy_origin');
508
509 $tmp = array();
510 $permissions_policy_feature = is_array($permissions_policy_feature) ? $permissions_policy_feature : array();
511 foreach (array_keys($permissions_policy_feature) as $feature) {
512
513 $origins = NULL;
514 if (!empty($permissions_policy_origin[$feature]))
515 {
516 $origins = $permissions_policy_origin[$feature];
517 $origins = str_replace(array('"', "'"), '', $origins);
518 $origins = explode(' ', $origins);
519 $origins = array_filter($origins);
520 $origins = array_unique($origins);
521 $origins = '"' . join('" "', $origins) . '"';
522 }
523
524 $value = NULL;
525 switch ($permissions_policy_value[$feature]) {
526 case '*':
527 $value = '*';
528 break;
529 case "none":
530 $value = '()';
531 break;
532 case "self":
533 $value = 'self';
534 if ($origins)
535 {
536 $value .= ' ' . $origins;
537 }
538 $value = sprintf('(%s)', $value);
539 break;
540 case 'origin(s)':
541 $value = sprintf('(%s)', $origins);
542 break;
543 }
544
545 $tmp[] = sprintf('%s=%s', $feature, $value);
546 }
547
548 return join(', ', $tmp);
549 }
550
551 function http_digest_parse($txt) {
552 $txt = stripslashes($txt);
553
554 $needed_parts = array('nonce'=>1, 'nc'=>1, 'cnonce'=>1, 'qop'=>1, 'username'=>1, 'uri'=>1, 'response'=>1);
555 $data = array();
556 $keys = implode('|', array_keys($needed_parts));
557
558 $matches = null;
559 preg_match_all('@(' . $keys . ')=(?:([\'"])([^\2]+?)\2|([^\s,]+))@', $txt, $matches, PREG_SET_ORDER);
560
561 foreach ($matches as $m) {
562 $data[$m[1]] = $m[3] ? $m[3] : $m[4];
563 unset($needed_parts[$m[1]]);
564 }
565
566 return $needed_parts ? false : $data;
567 }
568
569 function php_auth_digest() {
570 if (!($data = http_digest_parse($_SERVER['PHP_AUTH_DIGEST'])) || get_option('hh_www_authenticate_user') != $data['username']) {
571 die('Wrong Credentials!');
572 }
573
574 $A1 = md5($data['username'] . ':' . get_option('hh_www_authenticate_realm') . ':' . get_option('hh_www_authenticate_pswd'));
575 $A2 = md5($_SERVER['REQUEST_METHOD'].':'.$data['uri']);
576 $valid_response = md5($A1.':'.$data['nonce'].':'.$data['nc'].':'.$data['cnonce'].':'.$data['qop'].':'.$A2);
577 if ($data['response'] != $valid_response) {
578 die('Wrong Credentials!');
579 }
580 }
581
582 function php_content_encoding() {
583 if (substr_count($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip')) {
584 ob_start('ob_gzhandler');
585 } else {
586 ob_start();
587 }
588 }
589
590 function php_cookie_security_directives() {
591 $lines = array();
592 if (get_option('hh_cookie_security') == 1) {
593 $value = get_option('hh_cookie_security_value', array());
594 if (isset($value['HttpOnly'])) {
595 $lines[] = 'session.cookie_httponly = on';
596 }
597 if (isset($value['Secure'])) {
598 $lines[] = 'session.cookie_secure = on';
599 }
600 if (isset($value['SameSite']) && in_array($value['SameSite'], array('None', 'Lax', 'Strict'))) {
601 $lines[] = sprintf('session.cookie_samesite = "%s"', $value['SameSite']);
602 }
603 }
604
605 return $lines;
606 }
607
608 function http_headers() {
609 if (!is_php_mode()) {
610 return;
611 }
612 // PHP method below
613 list($headers, $statuses, $unset, $append) = get_http_headers();
614 $isCors = false;
615 foreach ($headers as $key => $value) {
616 if ($key == 'Access-Control-Allow-Origin') {
617 if (isset($_SERVER['HTTP_ORIGIN'])) {
618 if (in_array($value, array('*', 'null'))) {
619 $isCors = true;
620 header(sprintf("%s: *", $key));
621 }
622
623 if (is_array($value) && in_array($_SERVER['HTTP_ORIGIN'], $value)) {
624 $isCors = true;
625 header(sprintf("%s: %s", $key, $_SERVER['HTTP_ORIGIN']));
626 header("Vary: Origin", false);
627 }
628 }
629 continue;
630 }
631 if (in_array($key, array('Access-Control-Allow-Headers', 'Access-Control-Allow-Methods', 'Access-Control-Allow-Credentials', 'Access-Control-Max-Age', 'Access-Control-Expose-Headers'))) {
632 if ($isCors) {
633 header(sprintf("%s: %s", $key, $value));
634 }
635 continue;
636 }
637 header(sprintf("%s: %s", $key, $value));
638 }
639 foreach ($append as $key => $value) {
640 header(sprintf("%s: %s", $key, $value), false);
641 }
642 foreach ($unset as $header) {
643 if (function_exists('header_remove')) {
644 header_remove($header);
645 } else {
646 header("$header:");
647 }
648 }
649 foreach ($statuses as $key => $value) {
650 header(sprintf("%s %s", $key, $value));
651 exit;
652 }
653
654 if (get_option('hh_www_authenticate') == 1) {
655 php_auth_digest();
656 }
657
658 if (get_option('hh_content_encoding') == 1) {
659 php_content_encoding();
660 }
661 }
662
663 function http_headers_admin_add_page() {
664 add_options_page('HTTP Headers', 'HTTP Headers', 'manage_options', 'http-headers', 'http_headers_admin_page');
665 }
666
667 function http_headers_admin() {
668 register_setting('http-headers-mtd', 'hh_method');
669 register_setting('http-headers-mtd', 'hh_htaccess_path');
670 register_setting('http-headers-mtd', 'hh_user_ini_path');
671 register_setting('http-headers-mtd', 'hh_htpasswd_path');
672 register_setting('http-headers-mtd', 'hh_htdigest_path');
673 register_setting('http-headers-xfo', 'hh_x_frame_options');
674 register_setting('http-headers-xfo', 'hh_x_frame_options_value');
675 register_setting('http-headers-xfo', 'hh_x_frame_options_domain');
676 register_setting('http-headers-xss', 'hh_x_xxs_protection');
677 register_setting('http-headers-xss', 'hh_x_xxs_protection_value');
678 register_setting('http-headers-xss', 'hh_x_xxs_protection_uri');
679 register_setting('http-headers-cto', 'hh_x_content_type_options');
680 register_setting('http-headers-cto', 'hh_x_content_type_options_value');
681 register_setting('http-headers-sts', 'hh_strict_transport_security');
682 register_setting('http-headers-sts', 'hh_strict_transport_security_value'); //obsolete
683 register_setting('http-headers-sts', 'hh_strict_transport_security_max_age');
684 register_setting('http-headers-sts', 'hh_strict_transport_security_sub_domains');
685 register_setting('http-headers-sts', 'hh_strict_transport_security_preload');
686 register_setting('http-headers-uac', 'hh_x_ua_compatible');
687 register_setting('http-headers-uac', 'hh_x_ua_compatible_value');
688 register_setting('http-headers-p3p', 'hh_p3p');
689 register_setting('http-headers-p3p', 'hh_p3p_value');
690 register_setting('http-headers-rp', 'hh_referrer_policy');
691 register_setting('http-headers-rp', 'hh_referrer_policy_value');
692 register_setting('http-headers-csp', 'hh_content_security_policy');
693 register_setting('http-headers-csp', 'hh_content_security_policy_value');
694 register_setting('http-headers-csp', 'hh_content_security_policy_report_only');
695 register_setting('http-headers-acao', 'hh_access_control_allow_origin');
696 register_setting('http-headers-acao', 'hh_access_control_allow_origin_value');
697 register_setting('http-headers-acao', 'hh_access_control_allow_origin_url');
698 register_setting('http-headers-acac', 'hh_access_control_allow_credentials');
699 register_setting('http-headers-acac', 'hh_access_control_allow_credentials_value');
700 register_setting('http-headers-acam', 'hh_access_control_allow_methods');
701 register_setting('http-headers-acam', 'hh_access_control_allow_methods_value');
702 register_setting('http-headers-acah', 'hh_access_control_allow_headers');
703 register_setting('http-headers-acah', 'hh_access_control_allow_headers_value');
704 register_setting('http-headers-acah', 'hh_access_control_allow_headers_custom');
705 register_setting('http-headers-aceh', 'hh_access_control_expose_headers');
706 register_setting('http-headers-aceh', 'hh_access_control_expose_headers_value');
707 register_setting('http-headers-aceh', 'hh_access_control_expose_headers_custom');
708 register_setting('http-headers-acma', 'hh_access_control_max_age');
709 register_setting('http-headers-acma', 'hh_access_control_max_age_value');
710 register_setting('http-headers-ce', 'hh_content_encoding');
711 register_setting('http-headers-ce', 'hh_content_encoding_module');
712 register_setting('http-headers-ce', 'hh_content_encoding_value');
713 register_setting('http-headers-ce', 'hh_content_encoding_ext');
714 register_setting('http-headers-vary', 'hh_vary');
715 register_setting('http-headers-vary', 'hh_vary_value');
716 register_setting('http-headers-xpb', 'hh_x_powered_by');
717 register_setting('http-headers-xpb', 'hh_x_powered_by_option');
718 register_setting('http-headers-xpb', 'hh_x_powered_by_value');
719 register_setting('http-headers-wwa', 'hh_www_authenticate');
720 register_setting('http-headers-wwa', 'hh_www_authenticate_type');
721 register_setting('http-headers-wwa', 'hh_www_authenticate_realm');
722 register_setting('http-headers-wwa', 'hh_www_authenticate_user');
723 register_setting('http-headers-wwa', 'hh_www_authenticate_pswd');
724 register_setting('http-headers-cc', 'hh_cache_control');
725 register_setting('http-headers-cc', 'hh_cache_control_value');
726 register_setting('http-headers-age', 'hh_age');
727 register_setting('http-headers-age', 'hh_age_value');
728 register_setting('http-headers-pra', 'hh_pragma');
729 register_setting('http-headers-pra', 'hh_pragma_value');
730 register_setting('http-headers-exp', 'hh_expires');
731 register_setting('http-headers-exp', 'hh_expires_value');
732 register_setting('http-headers-exp', 'hh_expires_type');
733 register_setting('http-headers-con', 'hh_connection');
734 register_setting('http-headers-con', 'hh_connection_value');
735 register_setting('http-headers-cose', 'hh_cookie_security');
736 register_setting('http-headers-cose', 'hh_cookie_security_value');
737 register_setting('http-headers-ect', 'hh_expect_ct');
738 register_setting('http-headers-ect', 'hh_expect_ct_max_age');
739 register_setting('http-headers-ect', 'hh_expect_ct_report_uri');
740 register_setting('http-headers-ect', 'hh_expect_ct_enforce');
741 register_setting('http-headers-tao', 'hh_timing_allow_origin');
742 register_setting('http-headers-tao', 'hh_timing_allow_origin_value');
743 register_setting('http-headers-tao', 'hh_timing_allow_origin_url');
744 register_setting('http-headers-che', 'hh_custom_headers');
745 register_setting('http-headers-che', 'hh_custom_headers_value');
746 register_setting('http-headers-xdo', 'hh_x_download_options');
747 register_setting('http-headers-xdo', 'hh_x_download_options_value');
748 register_setting('http-headers-xpcd', 'hh_x_permitted_cross_domain_policies');
749 register_setting('http-headers-xpcd', 'hh_x_permitted_cross_domain_policies_value');
750 register_setting('http-headers-xdpc', 'hh_x_dns_prefetch_control');
751 register_setting('http-headers-xdpc', 'hh_x_dns_prefetch_control_value');
752 register_setting('http-headers-rt', 'hh_report_to');
753 register_setting('http-headers-rt', 'hh_report_to_value');
754 register_setting('http-headers-fp', 'hh_feature_policy');
755 register_setting('http-headers-fp', 'hh_feature_policy_value');
756 register_setting('http-headers-fp', 'hh_feature_policy_feature');
757 register_setting('http-headers-fp', 'hh_feature_policy_origin');
758 register_setting('http-headers-pp', 'hh_permissions_policy');
759 register_setting('http-headers-pp', 'hh_permissions_policy_value');
760 register_setting('http-headers-pp', 'hh_permissions_policy_feature');
761 register_setting('http-headers-pp', 'hh_permissions_policy_origin');
762 register_setting('http-headers-csd', 'hh_clear_site_data');
763 register_setting('http-headers-csd', 'hh_clear_site_data_value');
764 register_setting('http-headers-cty', 'hh_content_type');
765 register_setting('http-headers-cty', 'hh_content_type_value');
766 register_setting('http-headers-corp', 'hh_cross_origin_resource_policy');
767 register_setting('http-headers-corp', 'hh_cross_origin_resource_policy_value');
768 register_setting('http-headers-nel', 'hh_nel');
769 register_setting('http-headers-nel', 'hh_nel_value');
770 register_setting('http-headers-coep', 'hh_cross_origin_embedder_policy');
771 register_setting('http-headers-coep', 'hh_cross_origin_embedder_policy_value');
772 register_setting('http-headers-coop', 'hh_cross_origin_opener_policy');
773 register_setting('http-headers-coop', 'hh_cross_origin_opener_policy_value');
774 register_setting('http-headers-rob', 'hh_x_robots_tag');
775 register_setting('http-headers-rob', 'hh_x_robots_tag_value');
776 }
777
778 function http_headers_option($option) {
779
780 include_once ABSPATH . 'wp-admin/includes/admin.php';
781
782 require_once ABSPATH . WPINC . '/pluggable.php';
783
784 if (isset($_POST['hh_method']))
785 {
786 check_admin_referer('http-headers-mtd-options');
787 if (!is_super_admin()) {
788 wp_redirect(sprintf("%soptions-general.php?page=http-headers&tab=advanced&status=ERR&code=102", get_admin_url()));
789 exit;
790 }
791 # When method is changed
792 http_headers_activate();
793
794 } elseif (is_apache_mode()) {
795 # When particular header is changed
796 switch (true) {
797 case array_key_exists('hh_www_authenticate', $_POST):
798 check_admin_referer('http-headers-wwa-options');
799 update_auth_credentials();
800 update_auth_directives();
801 break;
802 case array_key_exists('hh_content_encoding', $_POST):
803 check_admin_referer('http-headers-ce-options');
804 update_content_encoding_directives();
805 break;
806 case array_key_exists('hh_content_type', $_POST):
807 check_admin_referer('http-headers-cty-options');
808 update_content_type_directives();
809 break;
810 case array_key_exists('hh_expires', $_POST):
811 check_admin_referer('http-headers-exp-options');
812 update_expires_directives();
813 break;
814 case array_key_exists('hh_cookie_security', $_POST):
815 check_admin_referer('http-headers-cose-options');
816 update_cookie_security_directives();
817 break;
818 case array_key_exists('hh_timing_allow_origin', $_POST):
819 check_admin_referer('http-headers-tao-options');
820 update_timing_directives();
821 break;
822 case array_key_exists('option_page', $_POST) && strpos($_POST['option_page'], 'http-headers-') === 0:
823 check_admin_referer($_POST['option_page'].'-options');
824 update_headers_directives();
825 break;
826 }
827 }
828 }
829
830 function nginx_headers_directives() {
831 $lines = array();
832 list($headers, , $unset, $append) = get_http_headers();
833
834 foreach ($unset as $header) {
835 $lines[] = sprintf(' more_clear_headers "%s";', $header);
836 }
837 $cors = $cors_header = $cors_inner = $cors_footer = array();
838 $all = array();
839 foreach ($headers as $key => $value) {
840 if (in_array($key, array('WWW-Authenticate'))) {
841 continue;
842 }
843 if (in_array($key, array('X-Content-Type-Options'))) {
844 $all[] = sprintf('add_header %s %s always;', $key, sprintf('%1$s%2$s%1$s', strpos($value, '"') === false ? '"' : "'", $value));
845 continue;
846 }
847 if ($key == 'Access-Control-Allow-Origin' && is_array($value)) {
848 $cors_header[] = sprintf('if ($http_origin ~* ^(%s)$) {', str_replace('.', '\.', join('|', $value)));
849 $cors_footer[] = '}';
850 $cors_inner[] = ' add_header Access-Control-Allow-Origin "$http_origin";';
851 if (!in_array('*', $value))
852 {
853 $cors_inner[] = ' add_header Vary "Origin";';
854 }
855 continue;
856 }
857 if (in_array($key, array('Access-Control-Allow-Headers', 'Access-Control-Allow-Methods', 'Access-Control-Allow-Credentials', 'Access-Control-Max-Age', 'Access-Control-Expose-Headers'))) {
858 $cors_inner[] = sprintf(' add_header %s %s;', $key, sprintf('%1$s%2$s%1$s', strpos($value, '"') === false ? '"' : "'", $value));
859 continue;
860 }
861 $lines[] = sprintf(' add_header %s %s;', $key, sprintf('%1$s%2$s%1$s', strpos($value, '"') === false ? '"' : "'", $value));
862 }
863 foreach ($append as $key => $value) {
864 $lines[] = sprintf(' add_header %s %s;', $key, sprintf('%1$s%2$s%1$s', strpos($value, '"') === false ? '"' : "'", $value));
865 }
866 if (!empty($cors_inner))
867 {
868 $cors = array_merge(
869 $cors_header,
870 $cors_inner,
871 $cors_footer
872 );
873 }
874 if (!empty($lines)) {
875 $lines = array_merge(
876 $all,
877 $cors,
878 array('location ~* \.(php|html)$ {'),
879 $lines,
880 array('}')
881 );
882 }
883 return $lines;
884 }
885
886 function nginx_content_encoding_directives() {
887 $lines = array();
888 if (get_option('hh_content_encoding') == 1) {
889
890 $lines[] = 'gzip on;';
891
892 $content_encoding_value = get_option('hh_content_encoding_value');
893 if (!$content_encoding_value) {
894 $content_encoding_value = array();
895 }
896
897 $content_encoding_ext = get_option('hh_content_encoding_ext');
898 if (!$content_encoding_ext) {
899 $content_encoding_ext = array();
900 }
901 if (!empty($content_encoding_ext)) {
902 //$lines[] = sprintf('<FilesMatch "\.(%s)$">', join('|', array_keys($content_encoding_ext)));
903 }
904 if (!empty($content_encoding_value)) {
905 $lines[] = sprintf('gzip_types %s;', join(' ', array_keys($content_encoding_value)));
906 }
907 }
908 return $lines;
909 }
910
911 function nginx_content_type_directives() {
912 $lines = array();
913 if (get_option('hh_content_type') == 1) {
914 $values = get_option('hh_content_type_value', array());
915 foreach ($values as $ext => $media_type) {
916 $lines[] = sprintf("%s %s;", $media_type, $ext);
917 }
918 }
919
920 return $lines;
921 }
922
923 function nginx_expires_directives() {
924 $lines = array();
925 if (get_option('hh_expires') == 1) {
926
927 $types = get_option('hh_expires_type', array());
928 $values = get_option('hh_expires_value', array());
929
930 $lines[] = 'map $sent_http_content_type $expires {';
931 foreach (array_keys($types) as $type) {
932 list($base, $period, $suffix) = explode('_', $values[$type]);
933 if (in_array($base, array('access', 'modification'))) {
934 $lines[] = $type != 'default'
935 ? sprintf(' %s %u%s;', $type, $period, $suffix[0])
936 : sprintf(' default %u%s;', $period, $suffix[0]);
937 } elseif ($base == 'invalid') {
938 $lines[] = $type != 'default'
939 ? sprintf(' %s 0;', $type)
940 : sprintf(' default 0;');
941 }
942 }
943 $lines[] = '}';
944
945 $lines[] = 'expires $expires;';
946 }
947 return $lines;
948 }
949
950 function nginx_timing_directives() {
951 $lines = array();
952 if (get_option('hh_timing_allow_origin') == 1) {
953 $value = get_option('hh_timing_allow_origin_value');
954 switch ($value)
955 {
956 case 'origin':
957 $value = get_option('hh_timing_allow_origin_url');
958 break;
959 }
960 if (!empty($value))
961 {
962 $lines[] = 'location ~* \.(js|css|jpe?g|png|gif|eot|otf|svg|ttf|woff2?)$ {';
963 $lines[] = sprintf(' add_header Timing-Allow-Origin "%s";', $value);
964 $lines[] = '}';
965 }
966 }
967 return $lines;
968 }
969
970 function nginx_auth_directives() {
971 $lines = array();
972 if (get_option('hh_www_authenticate') == 1) {
973
974 $type = get_option('hh_www_authenticate_type');
975
976 $file = $type == 'Basic' ? get_htpasswd_filename() : get_htdigest_filename();
977
978 $lines[] = sprintf('location ~ ^%s$ {', str_replace('.', '\.', basename($file)));
979 $lines[] = ' deny all;';
980 $lines[] = '}';
981
982 $lines[] = sprintf('location %s {', get_home_path());
983 if ($type == 'Basic') {
984 $lines[] = sprintf(' auth_basic "%s";', get_option('hh_www_authenticate_realm'));
985 $lines[] = sprintf(' auth_basic_user_file %s;', $file);
986 } else {
987 $lines[] = sprintf(' auth_digest "%s";', get_option('hh_www_authenticate_realm'));
988 $lines[] = sprintf(' auth_digest_user_file %s;', $file);
989 }
990 $lines[] = '}';
991 }
992 return $lines;
993 }
994
995 function nginx_auth_credentials() {
996 return apache_auth_credentials();
997 }
998
999 function nginx_cookie_security_directives() {
1000 $lines = array();
1001
1002 //TODO
1003
1004 return $lines;
1005 }
1006
1007 function nginx_check_requirements() {
1008 //TODO scheduled for v2.0.0
1009 return true;
1010 }
1011
1012 function iis_headers_directives() {
1013 //TODO scheduled for v2.0.0
1014 }
1015
1016 function iis_content_encoding_directives() {
1017 //TODO scheduled for v2.0.0
1018 }
1019
1020 function iis_content_type_directives() {
1021 //TODO scheduled for v2.0.0
1022 }
1023
1024 function iis_expires_directives() {
1025 //TODO scheduled for v2.0.0
1026 }
1027
1028 function iis_timing_directives() {
1029 //TODO scheduled for v2.0.0
1030 }
1031
1032 function iis_auth_directives() {
1033 //TODO scheduled for v2.0.0
1034 }
1035
1036 function iis_auth_credentials() {
1037 //TODO scheduled for v2.0.0
1038 }
1039
1040 function iis_cookie_security_directives() {
1041 //TODO scheduled for v2.0.0
1042 }
1043
1044 function iis_check_requirements() {
1045 //TODO scheduled for v2.0.0
1046 return true;
1047 }
1048
1049 function apache_headers_directives() {
1050 $lines = array();
1051 list($headers, , $unset, $append) = get_http_headers();
1052
1053 foreach ($unset as $header) {
1054 $lines[] = sprintf(' Header always unset %s', $header);
1055 $lines[] = sprintf(' Header unset %s', $header);
1056 }
1057 $all = array();
1058 foreach ($headers as $key => $value) {
1059 if (in_array($key, array('WWW-Authenticate'))) {
1060 continue;
1061 }
1062 if (in_array($key, array('X-Content-Type-Options'))) {
1063 $all[] = sprintf(' Header always set %s %s', $key, sprintf('%1$s%2$s%1$s', strpos($value, '"') === false ? '"' : "'", $value));
1064 continue;
1065 }
1066 if ($key == 'Strict-Transport-Security') {
1067 $lines[] = sprintf(' Header set %s %s env=HTTPS', $key, sprintf('%1$s%2$s%1$s', strpos($value, '"') === false ? '"' : "'", $value));
1068 continue;
1069 }
1070 if ($key == 'Access-Control-Allow-Origin') {
1071 $all[] = ' <IfModule mod_setenvif.c>';
1072 if (!is_array($value)) {
1073 if ($value) {
1074 $value = array($value);
1075 } else {
1076 $value = array();
1077 }
1078 }
1079 //$value[] = 'null';
1080 if (is_array($value))
1081 {
1082 $all[] = sprintf(' SetEnvIf Origin "^(%s)$" CORS=$0', str_replace(array('.', '*'), array('\.', '.+'), join('|', $value)));
1083 } else {
1084 $all[] = ' SetEnvIf Origin "^(.+)$" CORS=$0';
1085 }
1086 $all[] = ' </IfModule>';
1087 $all[] = ' Header set Access-Control-Allow-Origin %{CORS}e env=CORS';
1088 if (!in_array('*', $value))
1089 {
1090 $all[] = ' Header append Vary "Origin" env=CORS';
1091 }
1092 continue;
1093 }
1094 if (in_array($key, array('Access-Control-Allow-Headers', 'Access-Control-Allow-Methods', 'Access-Control-Allow-Credentials', 'Access-Control-Max-Age', 'Access-Control-Expose-Headers'))) {
1095 $all[] = sprintf(' Header set %s %s env=CORS', $key, sprintf('%1$s%2$s%1$s', strpos($value, '"') === false ? '"' : "'", $value));
1096 continue;
1097 }
1098 $lines[] = sprintf(' Header set %s %s', $key, sprintf('%1$s%2$s%1$s', strpos($value, '"') === false ? '"' : "'", $value));
1099 }
1100 foreach ($append as $key => $value) {
1101 $lines[] = sprintf(' Header append %s %s', $key, sprintf('%1$s%2$s%1$s', strpos($value, '"') === false ? '"' : "'", $value));
1102 }
1103 if (!empty($lines) || !empty($all)) {
1104 $lines = array_merge(
1105 array('<IfModule mod_headers.c>'),
1106 $all,
1107 array(' <FilesMatch "\.(php|html)$">'),
1108 $lines,
1109 array(' </FilesMatch>', '</IfModule>')
1110 );
1111 }
1112 return $lines;
1113 }
1114
1115 function apache_content_encoding_directives() {
1116 $lines = array();
1117 if (get_option('hh_content_encoding') == 1) {
1118
1119 $content_encoding_module = get_option('hh_content_encoding_module');
1120
1121 $module = 'mod_deflate.c';
1122 $filter = 'DEFLATE';
1123 $accept_encoding = 'gzip';
1124
1125 if ($content_encoding_module == 'brotli') {
1126 $module = 'mod_brotli.c';
1127 $filter = 'BROTLI_COMPRESS';
1128 $accept_encoding = 'br';
1129 }
1130
1131 $content_encoding_value = get_option('hh_content_encoding_value');
1132 if (!$content_encoding_value) {
1133 $content_encoding_value = array();
1134 }
1135
1136 $content_encoding_ext = get_option('hh_content_encoding_ext');
1137 if (!$content_encoding_ext) {
1138 $content_encoding_ext = array();
1139 }
1140
1141 $type = join('|', array_keys($content_encoding_value));
1142 $ext = join('|', array_keys($content_encoding_ext));
1143
1144 if (!empty($type) && !empty($ext)) {
1145 $expression = sprintf('(%%{CONTENT_TYPE} =~ m#^(%1$s)# || %%{REQUEST_FILENAME} =~ /.(%2$s)$/)', $type, $ext);
1146 } elseif (!empty($type)) {
1147 $expression = sprintf('%%{CONTENT_TYPE} =~ m#^(%1$s)#', $type);
1148 } elseif (!empty($ext)) {
1149 $expression = sprintf('%%{REQUEST_FILENAME} =~ /.(%1$s)$/', $ext);
1150 }
1151
1152 if (isset($expression)) {
1153 $lines[] = '<IfModule mod_filter.c>';
1154 $lines[] = ' FilterDeclare HttpHeaders';
1155 if (in_array($content_encoding_module, array('brotli', 'deflate'))) {
1156 $lines[] = sprintf('<IfModule %s>', $module);
1157 $lines[] = sprintf(' FilterProvider HttpHeaders %1$s "%%{HTTP:Accept-Encoding} =~ /%2$s/ && %3$s"', $filter, $accept_encoding, $expression);
1158 $lines[] = ' </IfModule>';
1159 } else {
1160 $lines[] = ' <IfModule mod_deflate.c>';
1161 $lines[] = ' <IfModule !mod_brotli.c>';
1162 $lines[] = sprintf(' FilterProvider HttpHeaders DEFLATE "%%{HTTP:Accept-Encoding} =~ /gzip/ && %1$s"', $expression);
1163 $lines[] = ' </IfModule>';
1164 $lines[] = ' </IfModule>';
1165 $lines[] = ' <IfModule mod_brotli.c>';
1166 $lines[] = sprintf(' FilterProvider HttpHeaders BROTLI_COMPRESS "%%{HTTP:Accept-Encoding} =~ /br/ && %1$s"', $expression);
1167 $lines[] = ' </IfModule>';
1168 }
1169 $lines[] = ' FilterChain HttpHeaders';
1170 $lines[] = '</IfModule>';
1171 }
1172 }
1173
1174 return $lines;
1175 }
1176
1177 function apache_expires_directives() {
1178 $lines = array();
1179 if (get_option('hh_expires') == 1) {
1180
1181 $types = get_option('hh_expires_type', array());
1182 $values = get_option('hh_expires_value', array());
1183 if (!is_array($types))
1184 {
1185 $types = array();
1186 }
1187 if (!is_array($values))
1188 {
1189 $values = array();
1190 }
1191
1192 $lines[] = '<IfModule mod_expires.c>';
1193 $lines[] = ' ExpiresActive On';
1194 foreach (array_keys($types) as $type) {
1195 list($base, $period, $suffix) = explode('_', $values[$type]);
1196 if (in_array($base, array('access', 'modification'))) {
1197 $lines[] = $type != 'default'
1198 ? sprintf(' ExpiresByType %s "%s plus %u %s"', $type, $base, $period, $suffix)
1199 : sprintf(' ExpiresDefault "%s plus %u %s"', $base, $period, $suffix);
1200 } elseif ($base == 'invalid') {
1201 $lines[] = $type != 'default'
1202 ? sprintf(' ExpiresByType %s A0', $type)
1203 : sprintf(' ExpiresDefault A0');
1204 }
1205 }
1206 $lines[] = '</IfModule>';
1207 }
1208
1209 return $lines;
1210 }
1211
1212 function apache_content_type_directives() {
1213 $lines = array();
1214 if (get_option('hh_content_type') == 1) {
1215 $values = get_option('hh_content_type_value', array());
1216 $lines[] = '<IfModule mod_mime.c>';
1217 foreach ($values as $ext => $media_type) {
1218 $lines[] = sprintf(" AddType %s .%s", $media_type, $ext);
1219 }
1220 $lines[] = '</IfModule>';
1221 }
1222
1223 return $lines;
1224 }
1225
1226 function apache_timing_directives() {
1227 $lines = array();
1228 if (get_option('hh_timing_allow_origin') == 1) {
1229 $value = get_option('hh_timing_allow_origin_value');
1230 switch ($value)
1231 {
1232 case 'origin':
1233 $value = get_option('hh_timing_allow_origin_url');
1234 break;
1235 }
1236 if (!empty($value))
1237 {
1238 $lines[] = '<IfModule mod_headers.c>';
1239 $lines[] = ' <FilesMatch "\\.(js|css|jpe?g|png|gif|eot|otf|svg|ttf|woff2?)$">';
1240 $lines[] = sprintf(' Header set Timing-Allow-Origin "%s"', $value);
1241 $lines[] = ' </FilesMatch>';
1242 $lines[] = '</IfModule>';
1243 }
1244 }
1245
1246 return $lines;
1247 }
1248
1249 function apache_auth_directives() {
1250 $lines = array();
1251 if (get_option('hh_www_authenticate') == 1) {
1252
1253 $type = get_option('hh_www_authenticate_type');
1254
1255 $file = $type == 'Basic' ? get_htpasswd_filename() : get_htdigest_filename();
1256
1257 $lines[] = sprintf('<FilesMatch "^%s$">', str_replace('.', '\.', basename($file)));
1258 $lines[] = ' <IfModule mod_authz_core.c>';
1259 $lines[] = ' Require all denied';
1260 $lines[] = ' </IfModule>';
1261 $lines[] = ' <IfModule !mod_authz_core.c>';
1262 $lines[] = ' Order deny,allow';
1263 $lines[] = ' Deny from all';
1264 $lines[] = ' </IfModule>';
1265 $lines[] = '</FilesMatch>';
1266 // no empty AuthName
1267 $realm = get_option('hh_www_authenticate_realm'); // AuthName
1268 $realm = ($realm == '') ? 'restricted area':$realm; // Empty => give fixed value
1269
1270 $lines[] = sprintf('<IfModule mod_auth_%s.c>', strtolower($type));
1271 $lines[] = sprintf(' AuthType %s', get_option('hh_www_authenticate_type'));
1272 $lines[] = sprintf(' AuthName "%s"', $realm);
1273 $lines[] = sprintf(' AuthUserFile "%s"', $file);
1274 $lines[] = ' Require valid-user';
1275 $lines[] = '</IfModule>';
1276 }
1277
1278 return $lines;
1279 }
1280
1281 function apache_auth_credentials() {
1282 if (get_option('hh_www_authenticate') == 1) {
1283 $type = get_option('hh_www_authenticate_type');
1284 $usernames = get_option('hh_www_authenticate_user', array());
1285 $passwords = get_option('hh_www_authenticate_pswd', array());
1286 if (!is_array($usernames)) {
1287 $usernames = array($usernames);
1288 }
1289 if (!is_array($passwords)) {
1290 $passwords = array($passwords);
1291 }
1292 $realm = get_option('hh_www_authenticate_realm');
1293 $auth = array();
1294 switch ($type) {
1295 case 'Basic':
1296 $ht_file = get_htpasswd_filename();
1297 foreach ($usernames as $k => $user) {
1298 $auth[] = sprintf('%s:{SHA}%s', $user, base64_encode(sha1($passwords[$k], true)));
1299 }
1300 break;
1301 case 'Digest':
1302 $ht_file = get_htdigest_filename();
1303 foreach ($usernames as $k => $user) {
1304 $auth[] = sprintf('%s:%s:%s', $user, $realm, md5($user.':'.$realm.':'.$passwords[$k]));
1305 }
1306 break;
1307 }
1308 $auth = join("\n", $auth);
1309
1310 return compact('ht_file', 'auth');
1311 }
1312 return false;
1313 }
1314
1315 function apache_cookie_security_directives() {
1316 $lines = array();
1317 if (get_option('hh_cookie_security') == 1) {
1318 $value = get_option('hh_cookie_security_value', array());
1319 $str = '';
1320 if (isset($value['HttpOnly'])) {
1321 $str .= ';HttpOnly';
1322 }
1323 if (isset($value['Secure'])) {
1324 $str .= ';Secure';
1325 }
1326 if (isset($value['SameSite']) && in_array($value['SameSite'], array('None', 'Lax', 'Strict'))) {
1327 $str .= ';SameSite=' . $value['SameSite'];
1328 }
1329 if ($str) {
1330 $lines[] = '<IfModule mod_headers.c>';
1331 $lines[] = ' Header always edit Set-Cookie (.*) "$1'.$str.'"';
1332 $lines[] = '</IfModule>';
1333 }
1334 }
1335
1336 return $lines;
1337 }
1338
1339 function apache_check_requirements() {
1340 return check_filename(get_htaccess_filename());
1341 }
1342
1343 function update_headers_directives() {
1344 $result = false;
1345 if (is_apache_mode()) {
1346 $lines = apache_headers_directives();
1347 $result = insert_with_markers(get_htaccess_filename(), "HttpHeaders", $lines);
1348 }
1349
1350 return $result;
1351 }
1352
1353 function update_content_encoding_directives() {
1354 $lines = array();
1355 if (is_apache_mode()) {
1356 $lines = apache_content_encoding_directives();
1357 }
1358
1359 return insert_with_markers(get_htaccess_filename(), "HttpHeadersCompression", $lines);
1360 }
1361
1362 function update_expires_directives() {
1363 $lines = array();
1364 if (is_apache_mode()) {
1365 $lines = apache_expires_directives();
1366 }
1367
1368 return insert_with_markers(get_htaccess_filename(), "HttpHeadersExpires", $lines);
1369 }
1370
1371 function update_content_type_directives() {
1372 $lines = array();
1373 if (is_apache_mode()) {
1374 $lines = apache_content_type_directives();
1375 }
1376
1377 return insert_with_markers(get_htaccess_filename(), "HttpHeadersContentType", $lines);
1378 }
1379
1380 function update_timing_directives() {
1381 $lines = array();
1382 if (is_apache_mode()) {
1383 $lines = apache_timing_directives();
1384 }
1385
1386 return insert_with_markers(get_htaccess_filename(), "HttpHeadersTiming", $lines);
1387 }
1388
1389 function update_auth_directives() {
1390 $lines = array();
1391 if (is_apache_mode()) {
1392 $lines = apache_auth_directives();
1393 }
1394
1395 return insert_with_markers(get_htaccess_filename(), "HttpHeadersAuth", $lines);
1396 }
1397
1398 function update_auth_credentials() {
1399 if (is_apache_mode()) {
1400 $credentials = apache_auth_credentials();
1401 if (isset($credentials['ht_file']) && !empty($credentials['ht_file']))
1402 {
1403 return @file_put_contents($credentials['ht_file'], $credentials['auth'], LOCK_EX);
1404 }
1405 }
1406
1407 return false;
1408 }
1409
1410 function update_cookie_security_directives() {
1411 $lines = array();
1412 $is_apache = is_apache_mode();
1413 $htaccess = get_htaccess_filename();
1414 $is_cgi = strpos(PHP_SAPI, 'cgi') !== false;
1415 if ($is_cgi) {
1416 $filename = get_user_ini_filename();
1417 $lines = php_cookie_security_directives();
1418 } elseif ($is_apache) {
1419 $filename = $htaccess;
1420 $lines = apache_cookie_security_directives();
1421 }
1422
1423 if (!$is_apache) {
1424 insert_with_markers($htaccess, "HttpHeadersCookieSecurity", array());
1425 }
1426
1427 if ($is_cgi) {
1428 return update_user_ini_filename($filename, "HttpHeadersCookieSecurity", $lines);
1429 }
1430
1431 return insert_with_markers($filename, "HttpHeadersCookieSecurity", $lines);
1432 }
1433
1434 function update_user_ini_filename($filename, $marker, $insertion) {
1435 if (!is_array($insertion)) {
1436 $insertion = explode("\n", $insertion);
1437 }
1438
1439 $start_marker = "; BEGIN " . $marker;
1440 $end_marker = "; END " . $marker;
1441
1442 $data = "";
1443 if (is_file($filename)) {
1444 $data = @file_get_contents($filename);
1445 }
1446
1447 $string = $start_marker;
1448 if ($insertion)
1449 {
1450 $string .= "\n".join("\n", $insertion);
1451 }
1452 $string .= "\n".$end_marker;
1453
1454 $pattern = '/'.$start_marker.'.*'.$end_marker.'/isU';
1455
1456 if (preg_match($pattern, $data)) {
1457 $data = preg_replace($pattern, $string, $data);
1458 } else {
1459 $data .= "\n".$string;
1460 }
1461
1462 $bytes = @file_put_contents($filename, $data, LOCK_EX);
1463
1464 return !!$bytes;
1465 }
1466
1467 function is_php_mode() {
1468 return get_option('hh_method') == 'php';
1469 }
1470
1471 function is_apache_mode() {
1472 return get_option('hh_method') == 'htaccess';
1473 }
1474
1475 function is_samesite_supported() {
1476 return version_compare(PHP_VERSION, '7.3.0', '>=');
1477 }
1478
1479 function http_headers_text_domain() {
1480 load_plugin_textdomain('http-headers', false, basename( dirname( __FILE__ ) ) . '/languages/');
1481 }
1482
1483 function http_headers_settings_link( $links ) {
1484 $url = get_admin_url() . 'options-general.php?page=http-headers';
1485 $settings_link = '<a href="' . $url . '">' . __('Settings', 'http-headers') . '</a>';
1486 array_unshift( $links, $settings_link );
1487 return $links;
1488 }
1489
1490 function http_headers_after_setup_theme() {
1491 add_filter('plugin_action_links_' . plugin_basename(__FILE__), 'http_headers_settings_link');
1492 }
1493
1494 function http_headers_enqueue($hook) {
1495 if ( 'http-headers.php' != $hook ) {
1496 # FIXME
1497 //return;
1498 }
1499
1500 wp_enqueue_script('http_headers_admin_scripts', plugin_dir_url( __FILE__ ) . 'assets/scripts.js', array(), '1.16.1', true);
1501 wp_localize_script('http_headers_admin_scripts', 'hh', array(
1502 'lbl_delete' => __('Delete', 'http-headers'),
1503 'lbl_value' => __('Value', 'http-headers'),
1504 'lbl_remove_endpoint' => __('Remove endpoint', 'http-headers'),
1505 'lbl_remove_group' => __('Remove group', 'http-headers'),
1506 ));
1507 wp_enqueue_style('http_headers_admin_styles', plugin_dir_url( __FILE__ ) . 'assets/styles.css', array(), '1.16.1');
1508 }
1509
1510 function http_headers_ajax_inspect() {
1511 check_ajax_referer('inspect');
1512 if (current_user_can('manage_options')) {
1513 include 'views/ajax-inspect.php';
1514 }
1515 wp_die();
1516 }
1517
1518 function check_filename($filename) {
1519 if (!is_file($filename)) {
1520 return -1;
1521 }
1522
1523 clearstatcache();
1524 if (!is_writable($filename)) {
1525 return -2;
1526 }
1527
1528 return true;
1529 }
1530
1531 function get_web_server_filename() {
1532 if (is_apache_mode()) {
1533 return get_htaccess_filename();
1534 }
1535
1536 return NULL;
1537 }
1538
1539 function check_web_server_requirements() {
1540 if (is_apache_mode()) {
1541 return apache_check_requirements();
1542 }
1543
1544 return true;
1545 }
1546
1547 function check_php_requirements() {
1548 if (strpos(PHP_SAPI, 'cgi') !== false) {
1549 // cgi, cgi-fcgi, fpm-fcgi
1550 return check_filename(get_user_ini_filename());
1551 }
1552
1553 return true;
1554 }
1555
1556 function http_headers_logout() {
1557 if (get_option('hh_clear_site_data') == 1) {
1558 $values = get_option('hh_clear_site_data_value', array());
1559 $tmp = array_keys($values);
1560 if ($tmp) {
1561 header(sprintf('Clear-Site-Data: "%s"', join('", "', $tmp)));
1562 }
1563 }
1564 }
1565
1566 function http_headers_activate() {
1567 update_headers_directives();
1568 update_auth_credentials();
1569 update_auth_directives();
1570 update_content_encoding_directives();
1571 update_content_type_directives();
1572 update_expires_directives();
1573 update_cookie_security_directives();
1574 update_timing_directives();
1575 }
1576
1577 function http_headers_deactivate() {
1578 $filename = get_htaccess_filename();
1579
1580 insert_with_markers($filename, "HttpHeaders", array());
1581 insert_with_markers($filename, "HttpHeadersCompression", array());
1582 insert_with_markers($filename, "HttpHeadersContentType", array());
1583 insert_with_markers($filename, "HttpHeadersExpires", array());
1584 insert_with_markers($filename, "HttpHeadersTiming", array());
1585 insert_with_markers($filename, "HttpHeadersAuth", array());
1586 insert_with_markers($filename, "HttpHeadersCookieSecurity", array());
1587 }
1588
1589 function http_headers_pre_update_option($value, $option, $old_value) {
1590
1591 if (in_array($option, array('hh_htaccess_path', 'hh_htdigest_path', 'hh_htpasswd_path', 'hh_user_ini_path', 'hh_method'))
1592 && !is_super_admin())
1593 {
1594 return $old_value;
1595 }
1596
1597 return $value;
1598 }
1599
1600 register_activation_hook(__FILE__, 'http_headers_activate');
1601 register_deactivation_hook(__FILE__, 'http_headers_deactivate');
1602 add_action('wp_logout', 'http_headers_logout');
1603
1604 if ( is_admin() ){ // admin actions
1605 add_action('admin_menu', 'http_headers_admin_add_page');
1606 add_action('admin_init', 'http_headers_admin');
1607 add_filter('pre_update_option', 'http_headers_pre_update_option', 10, 3);
1608 add_action('added_option', 'http_headers_option');
1609 add_action('updated_option', 'http_headers_option');
1610 add_action('admin_enqueue_scripts', 'http_headers_enqueue');
1611 add_action('after_setup_theme', 'http_headers_after_setup_theme');
1612 add_action('plugins_loaded', 'http_headers_text_domain');
1613 add_action('wp_ajax_inspect', 'http_headers_ajax_inspect');
1614 } else {
1615 // non-admin enqueues, actions, and filters
1616 add_action('send_headers', 'http_headers');
1617 }
1618
1619 function http_headers_admin_page() {
1620 include 'views/index.php';
1621 }