PluginProbe
HTTP Headers / 1.19.4
HTTP Headers v1.19.4
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.4, at http-headers.php

1,691 lines 61.0 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.4
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 = isset($_SERVER['REQUEST_METHOD']) ? sanitize_text_field(wp_unslash($_SERVER['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_option($option) {
832
833 include_once ABSPATH . 'wp-admin/includes/admin.php';
834
835 require_once ABSPATH . WPINC . '/pluggable.php';
836
837 $action = '-options';
838 if (isset($_POST['option_page'])) {
839 $action = sanitize_text_field(wp_unslash($_POST['option_page'])) . '-options';
840 }
841 if (!isset($_POST['_wpnonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['_wpnonce'])), $action)) {
842 wp_safe_redirect(sprintf("%soptions-general.php?page=http-headers&tab=advanced&status=ERR&code=101", get_admin_url()));
843 exit;
844 }
845 if (isset($_POST['hh_method']))
846 {
847 check_admin_referer('http-headers-mtd-options');
848 if (!is_super_admin()) {
849 wp_safe_redirect(sprintf("%soptions-general.php?page=http-headers&tab=advanced&status=ERR&code=102", get_admin_url()));
850 exit;
851 }
852 # When method is changed
853 http_headers_activate();
854
855 } elseif (http_headers_is_samesite_supported()) {
856
857 # When particular header is changed
858 switch (true) {
859 case array_key_exists('hh_www_authenticate', $_POST):
860 check_admin_referer('http-headers-wwa-options');
861 http_headers_update_auth_credentials();
862 http_headers_update_auth_directives();
863 break;
864 case array_key_exists('hh_content_encoding', $_POST):
865 check_admin_referer('http-headers-ce-options');
866 http_headers_update_content_encoding_directives();
867 break;
868 case array_key_exists('hh_content_type', $_POST):
869 check_admin_referer('http-headers-cty-options');
870 http_headers_update_content_type_directives();
871 break;
872 case array_key_exists('hh_expires', $_POST):
873 check_admin_referer('http-headers-exp-options');
874 http_headers_update_expires_directives();
875 break;
876 case array_key_exists('hh_cookie_security', $_POST):
877 check_admin_referer('http-headers-cose-options');
878 http_headers_update_cookie_security_directives();
879 break;
880 case array_key_exists('hh_timing_allow_origin', $_POST):
881 check_admin_referer('http-headers-tao-options');
882 http_headers_update_timing_directives();
883 break;
884 case array_key_exists('option_page', $_POST) && strpos(sanitize_text_field(wp_unslash($_POST['option_page'])), 'http-headers-') === 0:
885 check_admin_referer(sanitize_text_field(wp_unslash($_POST['option_page'])).'-options');
886 http_headers_update_headers_directives();
887 break;
888 }
889 }
890 }
891
892 function http_headers_nginx_headers_directives() {
893 $lines = array();
894 list($headers, , $unset, $append) = http_headers_get_http_headers();
895
896 foreach ($unset as $header) {
897 $lines[] = sprintf(' more_clear_headers "%s";', $header);
898 }
899 $cors = $cors_header = $cors_inner = $cors_footer = array();
900 $all = array();
901 foreach ($headers as $key => $value) {
902 if (in_array($key, array('WWW-Authenticate'))) {
903 continue;
904 }
905 if (in_array($key, array('X-Content-Type-Options'))) {
906 $all[] = sprintf('add_header %s %s always;', $key, sprintf('%1$s%2$s%1$s', strpos($value, '"') === false ? '"' : "'", $value));
907 continue;
908 }
909 if ($key == 'Access-Control-Allow-Origin' && is_array($value)) {
910 $cors_header[] = sprintf('if ($http_origin ~* ^(%s)$) {', str_replace('.', '\.', join('|', $value)));
911 $cors_footer[] = '}';
912 $cors_inner[] = ' add_header Access-Control-Allow-Origin "$http_origin";';
913 if (!in_array('*', $value))
914 {
915 $cors_inner[] = ' add_header Vary "Origin";';
916 }
917 continue;
918 }
919 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'))) {
920 $cors_inner[] = sprintf(' add_header %s %s;', $key, sprintf('%1$s%2$s%1$s', strpos($value, '"') === false ? '"' : "'", $value));
921 continue;
922 }
923 $lines[] = sprintf(' add_header %s %s;', $key, sprintf('%1$s%2$s%1$s', strpos($value, '"') === false ? '"' : "'", $value));
924 }
925 foreach ($append as $key => $value) {
926 $lines[] = sprintf(' add_header %s %s;', $key, sprintf('%1$s%2$s%1$s', strpos($value, '"') === false ? '"' : "'", $value));
927 }
928 if (!empty($cors_inner))
929 {
930 $cors = array_merge(
931 $cors_header,
932 $cors_inner,
933 $cors_footer
934 );
935 }
936 if (!empty($lines)) {
937 $lines = array_merge(
938 $all,
939 $cors,
940 array('location ~* \.(php|html)$ {'),
941 $lines,
942 array('}')
943 );
944 }
945 return $lines;
946 }
947
948 function http_headers_nginx_content_encoding_directives() {
949 $lines = array();
950 if (get_option('hh_content_encoding') == 1) {
951
952 $lines[] = 'gzip on;';
953
954 $content_encoding_value = get_option('hh_content_encoding_value');
955 if (!$content_encoding_value) {
956 $content_encoding_value = array();
957 }
958
959 $content_encoding_ext = get_option('hh_content_encoding_ext');
960 if (!$content_encoding_ext) {
961 $content_encoding_ext = array();
962 }
963 if (!empty($content_encoding_ext)) {
964 //$lines[] = sprintf('<FilesMatch "\.(%s)$">', join('|', array_keys($content_encoding_ext)));
965 }
966 if (!empty($content_encoding_value)) {
967 $lines[] = sprintf('gzip_types %s;', join(' ', array_keys($content_encoding_value)));
968 }
969 }
970 return $lines;
971 }
972
973 function http_headers_nginx_content_type_directives() {
974 $lines = array();
975 if (get_option('hh_content_type') == 1) {
976 $values = get_option('hh_content_type_value', array());
977 foreach ($values as $ext => $media_type) {
978 $lines[] = sprintf("%s %s;", $media_type, $ext);
979 }
980 }
981
982 return $lines;
983 }
984
985 function http_headers_nginx_expires_directives() {
986 $lines = array();
987 if (get_option('hh_expires') == 1) {
988
989 $types = get_option('hh_expires_type', array());
990 $values = get_option('hh_expires_value', array());
991
992 $lines[] = 'map $sent_http_content_type $expires {';
993 foreach (array_keys($types) as $type) {
994 list($base, $period, $suffix) = explode('_', $values[$type]);
995 if (in_array($base, array('access', 'modification'))) {
996 $lines[] = $type != 'default'
997 ? sprintf(' %s %u%s;', $type, $period, $suffix[0])
998 : sprintf(' default %u%s;', $period, $suffix[0]);
999 } elseif ($base == 'invalid') {
1000 $lines[] = $type != 'default'
1001 ? sprintf(' %s 0;', $type)
1002 : sprintf(' default 0;');
1003 }
1004 }
1005 $lines[] = '}';
1006
1007 $lines[] = 'expires $expires;';
1008 }
1009 return $lines;
1010 }
1011
1012 function http_headers_nginx_timing_directives() {
1013 $lines = array();
1014 if (get_option('hh_timing_allow_origin') == 1) {
1015 $value = get_option('hh_timing_allow_origin_value');
1016 switch ($value)
1017 {
1018 case 'origin':
1019 $value = get_option('hh_timing_allow_origin_url');
1020 break;
1021 }
1022 if (!empty($value))
1023 {
1024 $lines[] = 'location ~* \.(js|css|jpe?g|png|gif|eot|otf|svg|ttf|woff2?)$ {';
1025 $lines[] = sprintf(' add_header Timing-Allow-Origin "%s";', $value);
1026 $lines[] = '}';
1027 }
1028 }
1029 return $lines;
1030 }
1031
1032 function http_headers_nginx_auth_directives() {
1033 $lines = array();
1034 if (get_option('hh_www_authenticate') == 1) {
1035
1036 $type = get_option('hh_www_authenticate_type');
1037
1038 $file = $type == 'Basic' ? http_headers_get_htpasswd_filename() : http_headers_get_htdigest_filename();
1039
1040 $lines[] = sprintf('location ~ ^%s$ {', str_replace('.', '\.', basename($file)));
1041 $lines[] = ' deny all;';
1042 $lines[] = '}';
1043
1044 $lines[] = sprintf('location %s {', get_home_path());
1045 if ($type == 'Basic') {
1046 $lines[] = sprintf(' auth_basic "%s";', get_option('hh_www_authenticate_realm'));
1047 $lines[] = sprintf(' auth_basic_user_file %s;', $file);
1048 } else {
1049 $lines[] = sprintf(' auth_digest "%s";', get_option('hh_www_authenticate_realm'));
1050 $lines[] = sprintf(' auth_digest_user_file %s;', $file);
1051 }
1052 $lines[] = '}';
1053 }
1054 return $lines;
1055 }
1056
1057 function http_headers_nginx_auth_credentials() {
1058 return http_headers_apache_auth_credentials();
1059 }
1060
1061 function http_headers_nginx_cookie_security_directives() {
1062 $lines = array();
1063
1064 //TODO
1065
1066 return $lines;
1067 }
1068
1069 function http_headers_nginx_check_requirements() {
1070 //TODO scheduled for v2.0.0
1071 return true;
1072 }
1073
1074 function http_headers_iis_headers_directives() {
1075 //TODO scheduled for v2.0.0
1076 }
1077
1078 function http_headers_iis_content_encoding_directives() {
1079 //TODO scheduled for v2.0.0
1080 }
1081
1082 function http_headers_iis_content_type_directives() {
1083 //TODO scheduled for v2.0.0
1084 }
1085
1086 function http_headers_iis_expires_directives() {
1087 //TODO scheduled for v2.0.0
1088 }
1089
1090 function http_headers_iis_timing_directives() {
1091 //TODO scheduled for v2.0.0
1092 }
1093
1094 function http_headers_iis_auth_directives() {
1095 //TODO scheduled for v2.0.0
1096 }
1097
1098 function http_headers_iis_auth_credentials() {
1099 //TODO scheduled for v2.0.0
1100 }
1101
1102 function http_headers_iis_cookie_security_directives() {
1103 //TODO scheduled for v2.0.0
1104 }
1105
1106 function http_headers_iis_check_requirements() {
1107 //TODO scheduled for v2.0.0
1108 return true;
1109 }
1110
1111 function http_headers_apache_headers_directives() {
1112 $lines = array();
1113 list($headers, , $unset, $append) = http_headers_get_http_headers();
1114
1115 foreach ($unset as $header) {
1116 $lines[] = sprintf(' Header always unset %s', $header);
1117 $lines[] = sprintf(' Header unset %s', $header);
1118 }
1119 $all = array();
1120 foreach ($headers as $key => $value) {
1121 if (in_array($key, array('WWW-Authenticate'))) {
1122 continue;
1123 }
1124 if (in_array($key, array('X-Content-Type-Options'))) {
1125 $all[] = sprintf(' Header always set %s %s', $key, sprintf('%1$s%2$s%1$s', strpos($value, '"') === false ? '"' : "'", $value));
1126 continue;
1127 }
1128 if ($key == 'Strict-Transport-Security') {
1129 $lines[] = sprintf(' Header set %s %s env=HTTPS', $key, sprintf('%1$s%2$s%1$s', strpos($value, '"') === false ? '"' : "'", $value));
1130 continue;
1131 }
1132 if ($key == 'Access-Control-Allow-Origin') {
1133 $all[] = ' <IfModule mod_setenvif.c>';
1134 if (!is_array($value)) {
1135 if ($value) {
1136 $value = array($value);
1137 } else {
1138 $value = array();
1139 }
1140 }
1141 //$value[] = 'null';
1142 if (is_array($value))
1143 {
1144 $all[] = sprintf(' SetEnvIf Origin "^(%s)$" CORS=$0', str_replace(array('.', '*'), array('\.', '.+'), join('|', $value)));
1145 } else {
1146 $all[] = ' SetEnvIf Origin "^(.+)$" CORS=$0';
1147 }
1148 $all[] = ' </IfModule>';
1149 $all[] = ' Header set Access-Control-Allow-Origin %{CORS}e env=CORS';
1150 if (!in_array('*', $value))
1151 {
1152 $all[] = ' Header append Vary "Origin" env=CORS';
1153 }
1154 continue;
1155 }
1156 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'))) {
1157 $all[] = sprintf(' Header set %s %s env=CORS', $key, sprintf('%1$s%2$s%1$s', strpos($value, '"') === false ? '"' : "'", $value));
1158 continue;
1159 }
1160 $lines[] = sprintf(' Header set %s %s', $key, sprintf('%1$s%2$s%1$s', strpos($value, '"') === false ? '"' : "'", $value));
1161 }
1162 foreach ($append as $key => $value) {
1163 $lines[] = sprintf(' Header append %s %s', $key, sprintf('%1$s%2$s%1$s', strpos($value, '"') === false ? '"' : "'", $value));
1164 }
1165 if (!empty($lines) || !empty($all)) {
1166 $lines = array_merge(
1167 array('<IfModule mod_headers.c>'),
1168 $all,
1169 array(' <FilesMatch "\.(php|html)$">'),
1170 $lines,
1171 array(' </FilesMatch>', '</IfModule>')
1172 );
1173 }
1174 return $lines;
1175 }
1176
1177 function http_headers_apache_content_encoding_directives() {
1178 $lines = array();
1179 if (get_option('hh_content_encoding') == 1) {
1180
1181 $content_encoding_module = get_option('hh_content_encoding_module');
1182
1183 $module = 'mod_deflate.c';
1184 $filter = 'DEFLATE';
1185 $accept_encoding = 'gzip';
1186
1187 if ($content_encoding_module == 'brotli') {
1188 $module = 'mod_brotli.c';
1189 $filter = 'BROTLI_COMPRESS';
1190 $accept_encoding = 'br';
1191 }
1192
1193 $content_encoding_value = get_option('hh_content_encoding_value');
1194 if (!$content_encoding_value) {
1195 $content_encoding_value = array();
1196 }
1197
1198 $content_encoding_ext = get_option('hh_content_encoding_ext');
1199 if (!$content_encoding_ext) {
1200 $content_encoding_ext = array();
1201 }
1202
1203 $type = join('|', array_keys($content_encoding_value));
1204 $ext = join('|', array_keys($content_encoding_ext));
1205
1206 if (!empty($type) && !empty($ext)) {
1207 $expression = sprintf('(%%{CONTENT_TYPE} =~ m#^(%1$s)# || %%{REQUEST_FILENAME} =~ /.(%2$s)$/)', $type, $ext);
1208 } elseif (!empty($type)) {
1209 $expression = sprintf('%%{CONTENT_TYPE} =~ m#^(%1$s)#', $type);
1210 } elseif (!empty($ext)) {
1211 $expression = sprintf('%%{REQUEST_FILENAME} =~ /.(%1$s)$/', $ext);
1212 }
1213
1214 if (isset($expression)) {
1215 $lines[] = '<IfModule mod_filter.c>';
1216 $lines[] = ' FilterDeclare HttpHeaders';
1217 if (in_array($content_encoding_module, array('brotli', 'deflate'))) {
1218 $lines[] = sprintf('<IfModule %s>', $module);
1219 $lines[] = sprintf(' FilterProvider HttpHeaders %1$s "%%{HTTP:Accept-Encoding} =~ /%2$s/ && %3$s"', $filter, $accept_encoding, $expression);
1220 $lines[] = ' </IfModule>';
1221 } else {
1222 $lines[] = ' <IfModule mod_deflate.c>';
1223 $lines[] = ' <IfModule !mod_brotli.c>';
1224 $lines[] = sprintf(' FilterProvider HttpHeaders DEFLATE "%%{HTTP:Accept-Encoding} =~ /gzip/ && %1$s"', $expression);
1225 $lines[] = ' </IfModule>';
1226 $lines[] = ' </IfModule>';
1227 $lines[] = ' <IfModule mod_brotli.c>';
1228 $lines[] = sprintf(' FilterProvider HttpHeaders BROTLI_COMPRESS "%%{HTTP:Accept-Encoding} =~ /br/ && %1$s"', $expression);
1229 $lines[] = ' </IfModule>';
1230 }
1231 $lines[] = ' FilterChain HttpHeaders';
1232 $lines[] = '</IfModule>';
1233 }
1234 }
1235
1236 return $lines;
1237 }
1238
1239 function http_headers_apache_expires_directives() {
1240 $lines = array();
1241 if (get_option('hh_expires') == 1) {
1242
1243 $types = get_option('hh_expires_type', array());
1244 $values = get_option('hh_expires_value', array());
1245 if (!is_array($types))
1246 {
1247 $types = array();
1248 }
1249 if (!is_array($values))
1250 {
1251 $values = array();
1252 }
1253
1254 $lines[] = '<IfModule mod_expires.c>';
1255 $lines[] = ' ExpiresActive On';
1256 foreach (array_keys($types) as $type) {
1257 list($base, $period, $suffix) = explode('_', $values[$type]);
1258 if (in_array($base, array('access', 'modification'))) {
1259 $lines[] = $type != 'default'
1260 ? sprintf(' ExpiresByType %s "%s plus %u %s"', $type, $base, $period, $suffix)
1261 : sprintf(' ExpiresDefault "%s plus %u %s"', $base, $period, $suffix);
1262 } elseif ($base == 'invalid') {
1263 $lines[] = $type != 'default'
1264 ? sprintf(' ExpiresByType %s A0', $type)
1265 : sprintf(' ExpiresDefault A0');
1266 }
1267 }
1268 $lines[] = '</IfModule>';
1269 }
1270
1271 return $lines;
1272 }
1273
1274 function http_headers_apache_content_type_directives() {
1275 $lines = array();
1276 if (get_option('hh_content_type') == 1) {
1277 $values = get_option('hh_content_type_value', array());
1278 $lines[] = '<IfModule mod_mime.c>';
1279 foreach ($values as $ext => $media_type) {
1280 $lines[] = sprintf(" AddType %s .%s", $media_type, $ext);
1281 }
1282 $lines[] = '</IfModule>';
1283 }
1284
1285 return $lines;
1286 }
1287
1288 function http_headers_apache_timing_directives() {
1289 $lines = array();
1290 if (get_option('hh_timing_allow_origin') == 1) {
1291 $value = get_option('hh_timing_allow_origin_value');
1292 switch ($value)
1293 {
1294 case 'origin':
1295 $value = get_option('hh_timing_allow_origin_url');
1296 break;
1297 }
1298 if (!empty($value))
1299 {
1300 $lines[] = '<IfModule mod_headers.c>';
1301 $lines[] = ' <FilesMatch "\\.(js|css|jpe?g|png|gif|eot|otf|svg|ttf|woff2?)$">';
1302 $lines[] = sprintf(' Header set Timing-Allow-Origin "%s"', $value);
1303 $lines[] = ' </FilesMatch>';
1304 $lines[] = '</IfModule>';
1305 }
1306 }
1307
1308 return $lines;
1309 }
1310
1311 function http_headers_apache_auth_directives() {
1312 $lines = array();
1313 if (get_option('hh_www_authenticate') == 1) {
1314
1315 $type = get_option('hh_www_authenticate_type');
1316
1317 $file = $type == 'Basic' ? http_headers_get_htpasswd_filename() : http_headers_get_htdigest_filename();
1318
1319 $lines[] = sprintf('<FilesMatch "^%s$">', str_replace('.', '\.', basename($file)));
1320 $lines[] = ' <IfModule mod_authz_core.c>';
1321 $lines[] = ' Require all denied';
1322 $lines[] = ' </IfModule>';
1323 $lines[] = ' <IfModule !mod_authz_core.c>';
1324 $lines[] = ' Order deny,allow';
1325 $lines[] = ' Deny from all';
1326 $lines[] = ' </IfModule>';
1327 $lines[] = '</FilesMatch>';
1328 // no empty AuthName
1329 $realm = get_option('hh_www_authenticate_realm'); // AuthName
1330 $realm = ($realm == '') ? 'restricted area':$realm; // Empty => give fixed value
1331
1332 $lines[] = sprintf('<IfModule mod_auth_%s.c>', strtolower($type));
1333 $lines[] = sprintf(' AuthType %s', get_option('hh_www_authenticate_type'));
1334 $lines[] = sprintf(' AuthName "%s"', $realm);
1335 $lines[] = sprintf(' AuthUserFile "%s"', $file);
1336 $lines[] = ' Require valid-user';
1337 $lines[] = '</IfModule>';
1338 }
1339
1340 return $lines;
1341 }
1342
1343 function http_headers_apache_auth_credentials() {
1344 if (get_option('hh_www_authenticate') == 1) {
1345 $type = get_option('hh_www_authenticate_type');
1346 $usernames = get_option('hh_www_authenticate_user', array());
1347 $passwords = get_option('hh_www_authenticate_pswd', array());
1348 if (!is_array($usernames)) {
1349 $usernames = array($usernames);
1350 }
1351 if (!is_array($passwords)) {
1352 $passwords = array($passwords);
1353 }
1354 $realm = get_option('hh_www_authenticate_realm');
1355 $auth = array();
1356 switch ($type) {
1357 case 'Basic':
1358 $ht_file = http_headers_get_htpasswd_filename();
1359 foreach ($usernames as $k => $user) {
1360 $auth[] = sprintf('%s:{SHA}%s', $user, base64_encode(sha1($passwords[$k], true)));
1361 }
1362 break;
1363 case 'Digest':
1364 $ht_file = http_headers_get_htdigest_filename();
1365 foreach ($usernames as $k => $user) {
1366 $auth[] = sprintf('%s:%s:%s', $user, $realm, md5($user.':'.$realm.':'.$passwords[$k]));
1367 }
1368 break;
1369 }
1370 $auth = join("\n", $auth);
1371
1372 return compact('ht_file', 'auth');
1373 }
1374 return false;
1375 }
1376
1377 function http_headers_apache_cookie_security_directives() {
1378 $lines = array();
1379 if (get_option('hh_cookie_security') == 1) {
1380 $value = get_option('hh_cookie_security_value', array());
1381 $str = '';
1382 if (isset($value['HttpOnly'])) {
1383 $str .= ';HttpOnly';
1384 }
1385 if (isset($value['Secure'])) {
1386 $str .= ';Secure';
1387 }
1388 if (isset($value['SameSite']) && in_array($value['SameSite'], array('None', 'Lax', 'Strict'))) {
1389 $str .= ';SameSite=' . $value['SameSite'];
1390 }
1391 if ($str) {
1392 $lines[] = '<IfModule mod_headers.c>';
1393 $lines[] = ' Header always edit Set-Cookie (.*) "$1'.$str.'"';
1394 $lines[] = '</IfModule>';
1395 }
1396 }
1397
1398 return $lines;
1399 }
1400
1401 function http_headers_apache_check_requirements() {
1402 return http_headers_check_filename(http_headers_get_htaccess_filename());
1403 }
1404
1405 function http_headers_update_headers_directives() {
1406 $result = false;
1407 if (http_headers_is_apache_mode()) {
1408 $lines = http_headers_apache_headers_directives();
1409 $result = insert_with_markers(http_headers_get_htaccess_filename(), "HttpHeaders", $lines);
1410 }
1411
1412 return $result;
1413 }
1414
1415 function http_headers_update_content_encoding_directives() {
1416 $lines = array();
1417 if (http_headers_is_apache_mode()) {
1418 $lines = http_headers_apache_content_encoding_directives();
1419 }
1420
1421 return insert_with_markers(http_headers_get_htaccess_filename(), "HttpHeadersCompression", $lines);
1422 }
1423
1424 function http_headers_update_expires_directives() {
1425 $lines = array();
1426 if (http_headers_is_apache_mode()) {
1427 $lines = http_headers_apache_expires_directives();
1428 }
1429
1430 return insert_with_markers(http_headers_get_htaccess_filename(), "HttpHeadersExpires", $lines);
1431 }
1432
1433 function http_headers_update_content_type_directives() {
1434 $lines = array();
1435 if (http_headers_is_apache_mode()) {
1436 $lines = http_headers_apache_content_type_directives();
1437 }
1438
1439 return insert_with_markers(http_headers_get_htaccess_filename(), "HttpHeadersContentType", $lines);
1440 }
1441
1442 function http_headers_update_timing_directives() {
1443 $lines = array();
1444 if (http_headers_is_apache_mode()) {
1445 $lines = http_headers_apache_timing_directives();
1446 }
1447
1448 return insert_with_markers(http_headers_get_htaccess_filename(), "HttpHeadersTiming", $lines);
1449 }
1450
1451 function http_headers_update_auth_directives() {
1452 $lines = array();
1453 if (http_headers_is_apache_mode()) {
1454 $lines = http_headers_apache_auth_directives();
1455 }
1456
1457 return insert_with_markers(http_headers_get_htaccess_filename(), "HttpHeadersAuth", $lines);
1458 }
1459
1460 function http_headers_update_auth_credentials() {
1461 if (http_headers_is_apache_mode()) {
1462 $credentials = http_headers_apache_auth_credentials();
1463 if (isset($credentials['ht_file']) && !empty($credentials['ht_file']))
1464 {
1465 return @file_put_contents($credentials['ht_file'], $credentials['auth'], LOCK_EX);
1466 }
1467 }
1468
1469 return false;
1470 }
1471
1472 function http_headers_update_cookie_security_directives() {
1473 $lines = array();
1474 $is_apache = http_headers_is_apache_mode();
1475 $htaccess = http_headers_get_htaccess_filename();
1476 $is_cgi = strpos(PHP_SAPI, 'cgi') !== false;
1477 if ($is_cgi) {
1478 $filename = http_headers_get_user_ini_filename();
1479 $lines = http_headers_php_cookie_security_directives();
1480 } elseif ($is_apache) {
1481 $filename = $htaccess;
1482 $lines = http_headers_apache_cookie_security_directives();
1483 }
1484
1485 if (!$is_apache) {
1486 insert_with_markers($htaccess, "HttpHeadersCookieSecurity", array());
1487 }
1488
1489 if ($is_cgi) {
1490 return http_headers_update_user_ini_filename($filename, "HttpHeadersCookieSecurity", $lines);
1491 }
1492
1493 return insert_with_markers($filename, "HttpHeadersCookieSecurity", $lines);
1494 }
1495 function http_headers_update_user_ini_filename($filename, $marker, $insertion) {
1496 if (WP_Filesystem()) {
1497 global $wp_filesystem;
1498 }
1499
1500 if (!is_array($insertion)) {
1501 $insertion = explode("\n", $insertion);
1502 }
1503
1504 $start_marker = "; BEGIN " . $marker;
1505 $end_marker = "; END " . $marker;
1506
1507 $data = "";
1508 if ($wp_filesystem->is_file($filename)) {
1509 $data = $wp_filesystem->get_contents($filename);
1510 }
1511
1512 $string = $start_marker;
1513 if ($insertion)
1514 {
1515 $string .= "\n".join("\n", $insertion);
1516 }
1517 $string .= "\n".$end_marker;
1518
1519 $pattern = '/'.$start_marker.'.*'.$end_marker.'/isU';
1520
1521 if (preg_match($pattern, $data)) {
1522 $data = preg_replace($pattern, $string, $data);
1523 } else {
1524 $data .= "\n".$string;
1525 }
1526
1527 $bytes = @file_put_contents($filename, $data, LOCK_EX);
1528
1529 return !!$bytes;
1530 }
1531
1532
1533 function http_headers_is_php_mode() {
1534 return get_option('hh_method') == 'php';
1535 }
1536
1537 function http_headers_is_apache_mode() {
1538 return get_option('hh_method') == 'htaccess';
1539 }
1540
1541 function http_headers_is_samesite_supported() {
1542 return version_compare(PHP_VERSION, '7.3.0', '>=');
1543 }
1544
1545 function http_headers_text_domain() {
1546 load_plugin_textdomain('http-headers', false, basename( dirname( __FILE__ ) ) . '/languages/');
1547 }
1548
1549 function http_headers_settings_link( $links ) {
1550 $url = get_admin_url() . 'options-general.php?page=http-headers';
1551 $settings_link = '<a href="' . $url . '">' . __('Settings', 'http-headers') . '</a>';
1552 array_unshift( $links, $settings_link );
1553 return $links;
1554 }
1555
1556 function http_headers_after_setup_theme() {
1557 add_filter('plugin_action_links_' . plugin_basename(__FILE__), 'http_headers_settings_link');
1558 }
1559
1560 function http_headers_enqueue($hook) {
1561 if ( 'http-headers.php' != $hook ) {
1562 # FIXME
1563 //return;
1564 }
1565
1566 wp_enqueue_script('http_headers_admin_scripts', plugin_dir_url( __FILE__ ) . 'assets/scripts.js', array(), '1.16.1', true);
1567 wp_localize_script('http_headers_admin_scripts', 'hh', array(
1568 'lbl_delete' => __('Delete', 'http-headers'),
1569 'lbl_value' => __('Value', 'http-headers'),
1570 'lbl_remove_endpoint' => __('Remove endpoint', 'http-headers'),
1571 'lbl_remove_group' => __('Remove group', 'http-headers'),
1572 ));
1573 wp_enqueue_style('http_headers_admin_styles', plugin_dir_url( __FILE__ ) . 'assets/styles.css', array(), '1.16.1');
1574 }
1575
1576 function http_headers_ajax_inspect() {
1577 check_ajax_referer('inspect');
1578 if (current_user_can('manage_options')) {
1579 include 'views/ajax-inspect.php';
1580 }
1581 wp_die();
1582 }
1583
1584 function http_headers_check_filename($filename) {
1585 if (WP_Filesystem()) {
1586 global $wp_filesystem;
1587 }
1588
1589 if (!$wp_filesystem->is_file($filename)) {
1590 return -1;
1591 }
1592
1593 clearstatcache();
1594 if (!$wp_filesystem->is_writable($filename)) {
1595 return -2;
1596 }
1597
1598 return true;
1599 }
1600
1601 function http_headers_get_web_server_filename() {
1602 if (http_headers_is_apache_mode()) {
1603 return http_headers_get_htaccess_filename();
1604 }
1605
1606 return NULL;
1607 }
1608
1609 function http_headers_check_web_server_requirements() {
1610 if (http_headers_is_apache_mode()) {
1611 return http_headers_apache_check_requirements();
1612 }
1613
1614 return true;
1615 }
1616
1617 function http_headers_check_php_requirements() {
1618 if (strpos(PHP_SAPI, 'cgi') !== false) {
1619 // cgi, cgi-fcgi, fpm-fcgi
1620 return http_headers_check_filename(http_headers_get_user_ini_filename());
1621 }
1622
1623 return true;
1624 }
1625
1626 function http_headers_logout() {
1627 if (get_option('hh_clear_site_data') == 1) {
1628 $values = get_option('hh_clear_site_data_value', array());
1629 $tmp = array_keys($values);
1630 if ($tmp) {
1631 header(sprintf('Clear-Site-Data: "%s"', join('", "', $tmp)));
1632 }
1633 }
1634 }
1635
1636 function http_headers_activate() {
1637 http_headers_update_headers_directives();
1638 http_headers_update_auth_credentials();
1639 http_headers_update_auth_directives();
1640 http_headers_update_content_encoding_directives();
1641 http_headers_update_content_type_directives();
1642 http_headers_update_expires_directives();
1643 http_headers_update_cookie_security_directives();
1644 http_headers_update_timing_directives();
1645 }
1646
1647 function http_headers_deactivate() {
1648 $filename = http_headers_get_htaccess_filename();
1649
1650 insert_with_markers($filename, "HttpHeaders", array());
1651 insert_with_markers($filename, "HttpHeadersCompression", array());
1652 insert_with_markers($filename, "HttpHeadersContentType", array());
1653 insert_with_markers($filename, "HttpHeadersExpires", array());
1654 insert_with_markers($filename, "HttpHeadersTiming", array());
1655 insert_with_markers($filename, "HttpHeadersAuth", array());
1656 insert_with_markers($filename, "HttpHeadersCookieSecurity", array());
1657 }
1658
1659 function http_headers_pre_update_option($value, $option, $old_value) {
1660
1661 if (in_array($option, array('hh_htaccess_path', 'hh_htdigest_path', 'hh_htpasswd_path', 'hh_user_ini_path', 'hh_method'))
1662 && !is_super_admin())
1663 {
1664 return $old_value;
1665 }
1666
1667 return $value;
1668 }
1669
1670 register_activation_hook(__FILE__, 'http_headers_activate');
1671 register_deactivation_hook(__FILE__, 'http_headers_deactivate');
1672 add_action('wp_logout', 'http_headers_logout');
1673
1674 if ( is_admin() ){ // admin actions
1675 add_action('admin_menu', 'http_headers_admin_add_page');
1676 add_action('admin_init', 'http_headers_admin');
1677 add_filter('pre_update_option', 'http_headers_pre_update_option', 10, 3);
1678 add_action('added_option', 'http_headers_option');
1679 add_action('updated_option', 'http_headers_option');
1680 add_action('admin_enqueue_scripts', 'http_headers_enqueue');
1681 add_action('after_setup_theme', 'http_headers_after_setup_theme');
1682 add_action('plugins_loaded', 'http_headers_text_domain');
1683 add_action('wp_ajax_inspect', 'http_headers_ajax_inspect');
1684 } else {
1685 // non-admin enqueues, actions, and filters
1686 add_action('send_headers', 'http_headers');
1687 }
1688
1689 function http_headers_admin_page() {
1690 include 'views/index.php';
1691 }