security.php

main 101 lines · 2.9 KB Raw
Alex Alex Commit Initial commit 01/10/2026 09:20
1<?php
2
3function znote_security_bool(array $options, string $key, bool $default): bool {
4 if (!array_key_exists($key, $options)) {
5 return $default;
6 }
7
8 $value = $options[$key];
9 if (is_bool($value)) {
10 return $value;
11 }
12 if (is_int($value)) {
13 return $value !== 0;
14 }
15
16 $value = strtolower(trim((string)$value));
17 if (in_array($value, array('1', 'true', 'yes', 'on'), true)) {
18 return true;
19 }
20 if (in_array($value, array('0', 'false', 'no', 'off', ''), true)) {
21 return false;
22 }
23
24 return $default;
25}
26
27function znote_security_header_value($value): string {
28 return trim(str_replace(array("\r", "\n"), '', (string)$value));
29}
30
31function znote_security_send_header(string $name, $value): void {
32 $value = znote_security_header_value($value);
33 if ($value === '') {
34 header_remove($name);
35 return;
36 }
37
38 header($name . ': ' . $value);
39}
40
41function znote_security_remove_browser_headers(): void {
42 foreach (array(
43 'X-Content-Type-Options',
44 'X-Frame-Options',
45 'Referrer-Policy',
46 'Permissions-Policy',
47 'Content-Security-Policy',
48 'X-Permitted-Cross-Domain-Policies',
49 'Strict-Transport-Security',
50 ) as $header) {
51 header_remove($header);
52 }
53}
54
55function znote_security_boot(array $options = array()): void {
56 $showErrors = znote_security_bool($options, 'display_errors', false);
57 ini_set('display_errors', $showErrors ? '1' : '0');
58 ini_set('display_startup_errors', $showErrors ? '1' : '0');
59 ini_set('log_errors', '1');
60
61 if (PHP_SAPI === 'cli' || headers_sent()) {
62 return;
63 }
64
65 if (!znote_security_bool($options, 'headers_enabled', true)) {
66 znote_security_remove_browser_headers();
67 return;
68 }
69
70 if (znote_security_bool($options, 'content_type_options', true)) {
71 znote_security_send_header('X-Content-Type-Options', 'nosniff');
72 } else {
73 header_remove('X-Content-Type-Options');
74 }
75
76 znote_security_send_header('X-Frame-Options', $options['frame_options'] ?? 'SAMEORIGIN');
77 znote_security_send_header('Referrer-Policy', $options['referrer_policy'] ?? 'strict-origin-when-cross-origin');
78 znote_security_send_header('Permissions-Policy', $options['permissions_policy'] ?? 'camera=(), microphone=(), geolocation=(), browsing-topics=()');
79 znote_security_send_header('Content-Security-Policy', $options['content_security_policy'] ?? "frame-ancestors 'self'; object-src 'none'; base-uri 'self'");
80
81 if (znote_security_bool($options, 'cross_domain_policy', true)) {
82 znote_security_send_header('X-Permitted-Cross-Domain-Policies', 'none');
83 } else {
84 header_remove('X-Permitted-Cross-Domain-Policies');
85 }
86
87 if (znote_security_bool($options, 'hsts', false)
88 && function_exists('znote_session_request_is_https')
89 && znote_session_request_is_https()
90 ) {
91 $maxAge = max(0, (int)($options['hsts_max_age'] ?? 31536000));
92 $value = 'max-age=' . $maxAge;
93 if (znote_security_bool($options, 'hsts_include_subdomains', false)) {
94 $value .= '; includeSubDomains';
95 }
96 znote_security_send_header('Strict-Transport-Security', $value);
97 } else {
98 header_remove('Strict-Transport-Security');
99 }
100}
101
Top