1: <?php
2: /**
3: * This file is part of the PHPLucidFrame library.
4: * Core utility for input sanitizing, data escaping, CSRF protection and XSS prevention
5: *
6: * @package PHPLucidFrame\Core
7: * @since PHPLucidFrame v 1.0.0
8: * @copyright Copyright (c), PHPLucidFrame.
9: * @link http://phplucidframe.com
10: * @license http://www.opensource.org/licenses/mit-license.php MIT License
11: *
12: * This source file is subject to the MIT license that is bundled
13: * with this source code in the file LICENSE
14: */
15:
16: use LucidFrame\Core\Form;
17:
18: /**
19: * @internal
20: * @ignore
21: *
22: * Check the default security secret to be changed
23: */
24: function security_prerequisite()
25: {
26: $secret = trim(_cfg('securitySecret'));
27: if (empty($secret)) {
28: $msg = 'To define your own security secret, ';
29: $msg .= 'open your terminal or command line, <code class="inline">cd</code> to your project directory, ';
30: $msg .= 'then run <code class="inline">php lucidframe secret:generate</code> (recommended)';
31: $msg .= ' OR go to <a href="https://www.phplucidframe.com/secret-generator" target="_blank">https://phplucidframe.com/secret-generator</a>';
32: $msg .= ' and copy the generated secret to your <code class="inline">.secret</code> file in the project root.';
33: _cfg('sitewideWarnings', function_exists('_t') ? _t($msg) : $msg);
34: }
35:
36: Form::restoreToken();
37: if (!Form::$formToken) {
38: Form::generateToken();
39: }
40: }
41:
42: /**
43: * Return a component of the current path.
44: * When viewing a page http://www.example.com/foo/bar and its path would be "foo/bar",
45: * for example, _arg(0) returns "foo" and _arg(1) returns "bar"
46: *
47: * @param mixed $index
48: * The index of the component, where each component is separated by a '/' (forward-slash),
49: * and where the first component has an index of 0 (zero).
50: * @param string $path
51: * A path to break into components. Defaults to the path of the current page.
52: *
53: * @return mixed
54: * The component specified by `$index`, or `null` if the specified component was not found.
55: * If called without arguments, it returns an array containing all the components of the current path.
56: */
57: function _arg($index = null, $path = null)
58: {
59: if (isset($_GET[$index])) {
60: return _get($index);
61: }
62:
63: if (is_null($path)) {
64: $path = route_path();
65: }
66: $arguments = explode('/', $path);
67:
68: if (is_numeric($index)) {
69: if (!isset($index)) {
70: return $arguments;
71: }
72: if (isset($arguments[$index])) {
73: return strip_tags(trim($arguments[$index]));
74: }
75: } elseif (is_string($index)) {
76: $query = '-' . $index . '/';
77: $pos = strpos($path, $query);
78: if ($pos !== false) {
79: $start = $pos + strlen($query);
80: $path = substr($path, $start);
81: $end = strpos($path, '/-');
82: if ($end) {
83: $path = substr($path, 0, $end);
84: }
85: if (substr_count($path, '/')) {
86: return explode('/', $path);
87: } else {
88: return $path;
89: }
90: }
91: } elseif (is_null($index)) {
92: return explode('/', str_replace('/-', '/', $path));
93: }
94:
95: return '';
96: }
97:
98: /**
99: * Sanitize input values from GET
100: * @param mixed $name (Optional) The name in $_GET to be sanitized; if it is omitted, the whole array of $_GET will be sanitized
101: * @return mixed The cleaned value or array of values
102: */
103: function _get($name = null)
104: {
105: if ($name === null) {
106: $get = $_GET;
107: foreach ($get as $name => $value) {
108: if (is_array($value)) {
109: $get[$name] = _get($name);
110: } else {
111: $get[$name] = urldecode(_sanitize($value));;
112: }
113: }
114:
115: return $get;
116: } else {
117: if (isset($_GET[$name])) {
118: if (is_array($_GET[$name])) {
119: $get = $_GET[$name];
120: foreach ($get as $key => $value) {
121: if (is_array($value)) {
122: array_walk($get[$key], function(&$val) {
123: $val = is_array($val) ? $val : urldecode(_sanitize($val));
124: });
125: } else {
126: $get[$key] = urldecode(_sanitize($value));
127: }
128: }
129:
130: return $get;
131: } else {
132: return urldecode(_sanitize($_GET[$name]));
133: }
134: } else {
135: return null;
136: }
137: }
138: }
139:
140: /**
141: * Sanitize input values from POST
142: * @param mixed $name (Optional) The name in $_POST to be sanitized; if it is omitted, the whole array of $_POST will be sanitized
143: * @return mixed the cleaned value or array of values
144: */
145: function _post($name = null)
146: {
147: if (_isContentType('application/json')) {
148: return __input($name);
149: }
150:
151: if ($name === null) {
152: $post = $_POST;
153: foreach ($post as $name => $value) {
154: if (is_array($value)) {
155: $post[$name] = _post($name);
156: } else {
157: $post[$name] = _sanitize(stripslashes($value));
158: }
159: }
160:
161: return $post;
162: } else {
163: if (isset($_POST[$name])) {
164: if (is_array($_POST[$name])) {
165: $post = $_POST[$name];
166: foreach ($post as $key => $value) {
167: if (is_array($value)) {
168: array_walk($post[$key], function(&$val) {
169: $val = is_array($val) ? $val : _sanitize(stripslashes($val));
170: });
171: } else {
172: $post[$key] = _sanitize(stripslashes($value));
173: }
174: }
175:
176: return $post;
177: } else {
178: return _sanitize(stripslashes($_POST[$name]));
179: }
180: } else {
181: return null;
182: }
183: }
184: }
185:
186: /**
187: * Accessing PUT request data
188: * @param string $name The optional name of the value to be sanitized
189: * @return mixed the cleaned value
190: */
191: function _put($name = null)
192: {
193: return __input($name);
194: }
195:
196: /**
197: * Accessing PATCH request data
198: * @param string $name The optional name of the value to be sanitized
199: * @return mixed the cleaned value
200: */
201: function _patch($name = null)
202: {
203: return __input($name);
204: }
205:
206: /**
207: * Strips javascript tags in the value to prevent from XSS attack
208: * @param mixed $value The value or The array of values being stripped.
209: * @return mixed the cleaned value
210: */
211: function _xss($value)
212: {
213: if (is_object($value)) {
214: return $value;
215: }
216:
217: if (is_array($value)) {
218: foreach ($value as $key => $val) {
219: if (is_array($val)) {
220: $value[$key] = _xss($val);
221: } else {
222: $value[$key] = __xss($val);
223: }
224: }
225: } else {
226: $value = __xss($value);
227: }
228:
229: return $value;
230: }
231:
232: /**
233: * Sanitize strings
234: * @param mixed $input Value to filter
235: * @return mixed The filtered value
236: */
237: function _sanitize($input)
238: {
239: $input = htmlspecialchars_decode(trim($input), ENT_NOQUOTES);
240:
241: return htmlspecialchars($input, ENT_NOQUOTES);
242: }
243:
244: /**
245: * @internal
246: * @ignore
247: *
248: * Accessing PUT/PATCH data
249: * @param string $name The optional name of the value to be sanitized
250: * @return mixed the cleaned value
251: */
252: function __input($name = null)
253: {
254: $input = file_get_contents("php://input");
255: if (_isContentType('application/json')) {
256: $vars = json_decode($input, true);
257: } else {
258: parse_str($input, $vars);
259: }
260:
261: if ($name) {
262: return isset($vars[$name]) ? _sanitize(stripslashes($vars[$name])) : null;
263: }
264:
265: if (is_array($vars)) {
266: foreach ($vars as $key => $value) {
267: $vars[$key] = _sanitize(stripslashes($value));
268: }
269: }
270:
271: return $vars;
272: }
273:
274: /**
275: * @internal
276: * @ignore
277: *
278: * Strips javascript tags in the value to prevent from XSS attack
279: * @param mixed $value The value being stripped.
280: * @return mixed the cleaned value
281: */
282: function __xss($value)
283: {
284: $value = trim(stripslashes($value));
285: $ascii = '[\x00-\x20|&\#x0A;|&\#x0D;|&\#x09;|&\#14;|<|!|\-|>]*';
286:
287: # Remove some tags
288: $value = preg_replace('#</*(?:applet|b(?:ase|gsound|link)|!--\#exec|style|form|embed|frame(?:set)?|i(?:frame|layer)|l(?:ayer|ink)|meta|object|s(?:cript)|title|xml|\?xml)[^>]*+>#i', '', $value);
289: # Remove any attribute starting with "on" or xmlns
290: $value = preg_replace('#(<[^>]+?[\x00-\x20"\'])(?:on|xmlns)[^>]*+>#iu', '$1>', $value);
291: # Remove javascript: protocol
292: $value = preg_replace('#([a-z]*)'.$ascii.'='.$ascii.'([`\'"]*)'.$ascii.'j'.$ascii.'a'.$ascii.'v'.$ascii.'a'.$ascii.'s'.$ascii.'c'.$ascii.'r'.$ascii.'i'.$ascii.'p'.$ascii.'t'.$ascii.':#iu', '$1=$2noscript:', $value);
293: # Remove vbscript: protocol
294: $value = preg_replace('#([a-z]*)'.$ascii.'='.$ascii.'([`\'"]*)'.$ascii.'v'.$ascii.'b'.$ascii.'s'.$ascii.'c'.$ascii.'r'.$ascii.'i'.$ascii.'p'.$ascii.'t'.$ascii.':#iu', '$1=$2noscript:', $value);
295: # Remove livescript: protocol (older versions of Netscape only)
296: $value = preg_replace('#([a-z]*)'.$ascii.'='.$ascii.'([`\'"]*)'.$ascii.'l'.$ascii.'i'.$ascii.'v'.$ascii.'e'.$ascii.'s'.$ascii.'c'.$ascii.'r'.$ascii.'i'.$ascii.'p'.$ascii.'t'.$ascii.':#iu', '$1=$2noscript:', $value);
297: # Remove -moz-binding: css (Firefox only)
298: $value = preg_replace('#([a-z]*)'.$ascii.'([\'"]*)'.$ascii.'(-moz-binding|javascript)'.$ascii.':#u', '$1$2noscript:', $value);
299: # Remove dec/hex entities in tags, such as &#106;, &#0000106;, &#x6A;
300: $value = preg_replace('#([a-z]*)'.$ascii.'='.$ascii.'([`\'"]*)((&\#x*[0-9A-F]+);*)+#iu', '$1', $value);
301:
302: # CSS expression; only works in IE: <span style="width: expression(alert('Ping!'));"></span>
303: $chunk = str_split('expression');
304: # to match
305: # - expression:
306: # - expr/*XSS*/ession:
307: # - ex/*XSS*//*/*/pression:
308: $expression = $ascii;
309: foreach ($chunk as $chr) {
310: $expression .= $chr . '(\/\*.*\*\/)*';
311: }
312: $expression .= $ascii;
313: $value = preg_replace('#(<[^>]+?)style'.$ascii.'='.$ascii.'[`\'"]*.*?'.$expression.'\([^>]*+>#i', '$1>', $value);
314:
315: # CSS behaviour
316: $chunk = str_split('behavior');
317: $behavior = $ascii;
318: foreach ($chunk as $chr) {
319: $behavior .= $chr . '(\/\*.*\*\/)*';
320: }
321: $behavior .= $ascii;
322: $value = preg_replace('#(<[^>]+?)style'.$ascii.'='.$ascii.'[`\'"]*.*?'.$behavior.'[^>]*+>#i', '$1>', $value);
323:
324: return $value;
325: }
326: