PluginProbe
HTTP Headers / 1.19.5
HTTP Headers v1.19.5
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.5, at http-headers.php

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