1
|
|
|
<?php |
|
|
|
|
2
|
|
|
/** |
3
|
|
|
* @title Validate Class |
4
|
|
|
* @desc Various methods to Validate. |
5
|
|
|
* |
6
|
|
|
* @author Pierre-Henry Soria <[email protected]> |
7
|
|
|
* @copyright (c) 2012-2018, Pierre-Henry Soria. All Rights Reserved. |
8
|
|
|
* @license GNU General Public License; See PH7.LICENSE.txt and PH7.COPYRIGHT.txt in the root directory. |
9
|
|
|
* @package PH7 / Framework / Security / Validate |
10
|
|
|
*/ |
11
|
|
|
|
12
|
|
|
namespace PH7\Framework\Security\Validate; |
13
|
|
|
|
14
|
|
|
defined('PH7') or exit('Restricted access'); |
15
|
|
|
|
16
|
|
|
use DateTime; |
17
|
|
|
use Exception; |
18
|
|
|
use PH7\DbTableName; |
19
|
|
|
use PH7\ExistsCoreModel; |
20
|
|
|
use PH7\Framework\Config\Config; |
21
|
|
|
use PH7\Framework\Error\CException\PH7InvalidArgumentException; |
22
|
|
|
use PH7\Framework\Math\Measure\Year as YearMeasure; |
23
|
|
|
use PH7\Framework\Security\Ban\Ban; |
24
|
|
|
use PH7\Framework\Str\Str; |
25
|
|
|
|
26
|
|
|
class Validate |
27
|
|
|
{ |
28
|
|
|
const REGEX_NOT_NAME_PATTERN = '`(?:[\|<>"\=\]\[\}\{\\\\$£€@%~^#\(\):;\?!¿¡\*])|(?:(?:https?|ftps?)://)|(?:[0-9])`'; |
29
|
|
|
const REGEX_DATE_FORMAT = '`^\d\d/\d\d/\d\d\d\d$`'; |
30
|
|
|
|
31
|
|
|
const MAX_INT_NUMBER = 999999999999; |
32
|
|
|
|
33
|
|
|
const MIN_NAME_LENGTH = 2; |
34
|
|
|
const MAX_NAME_LENGTH = 20; |
35
|
|
|
|
36
|
|
|
const HEX_HASH = '#'; |
37
|
|
|
const MIN_HEX_LENGTH = 3; |
38
|
|
|
const MAX_HEX_LENGTH = 6; |
39
|
|
|
|
40
|
|
|
const DEF_MIN_USERNAME_LENGTH = 3; |
41
|
|
|
const DEF_MIN_PASS_LENGTH = 6; |
42
|
|
|
const DEF_MAX_PASS_LENGTH = 60; |
43
|
|
|
const DEF_MIN_AGE = 18; |
44
|
|
|
const DEF_MAX_AGE = 99; |
45
|
|
|
|
46
|
|
|
/** @var Str */ |
47
|
|
|
private $oStr; |
48
|
|
|
|
49
|
|
|
public function __construct() |
50
|
|
|
{ |
51
|
|
|
$this->oStr = new Str; |
52
|
|
|
} |
53
|
|
|
|
54
|
|
|
/** |
55
|
|
|
* Check the type of a value. |
56
|
|
|
* |
57
|
|
|
* @param string $sValue |
58
|
|
|
* @param string $sType Type whose value should be (case-insensitive). |
59
|
|
|
* @param bool $bRequired Default TRUE |
60
|
|
|
* |
61
|
|
|
* @return bool |
62
|
|
|
* |
63
|
|
|
* @throws PH7InvalidArgumentException If the type doesn't exist. |
64
|
|
|
*/ |
65
|
|
|
public static function type($sValue, $sType, $bRequired = true) |
66
|
|
|
{ |
67
|
|
|
$sType = strtolower($sType); // Case-insensitive type. |
68
|
|
|
|
69
|
|
|
if (false === $bRequired && 0 === (new Str)->length($sValue)) // Yoda Condition ;-) |
70
|
|
|
return true; |
71
|
|
|
|
72
|
|
|
switch ($sType) { |
73
|
|
|
case 'str': |
74
|
|
|
case 'string': |
75
|
|
|
$bValid = is_string($sValue); |
76
|
|
|
break; |
77
|
|
|
|
78
|
|
|
case 'int': |
79
|
|
|
case 'integer': |
80
|
|
|
$bValid = is_int($sValue); |
81
|
|
|
break; |
82
|
|
|
|
83
|
|
|
case 'float': |
84
|
|
|
case 'double': |
85
|
|
|
$bValid = is_float($sValue); |
86
|
|
|
break; |
87
|
|
|
|
88
|
|
|
case 'bool': |
89
|
|
|
case 'boolean': |
90
|
|
|
$bValid = is_bool($sValue); |
91
|
|
|
break; |
92
|
|
|
|
93
|
|
|
case 'num': |
94
|
|
|
case 'numeric': |
95
|
|
|
$bValid = is_numeric($sValue); |
96
|
|
|
break; |
97
|
|
|
|
98
|
|
|
case 'arr': |
99
|
|
|
case 'array': |
100
|
|
|
$bValid = is_array($sValue); |
101
|
|
|
break; |
102
|
|
|
|
103
|
|
|
case 'null': |
104
|
|
|
$bValid = is_null($sValue); |
105
|
|
|
break; |
106
|
|
|
|
107
|
|
|
case 'obj': |
108
|
|
|
case 'object': |
109
|
|
|
$bValid = is_object($sValue); |
110
|
|
|
break; |
111
|
|
|
|
112
|
|
|
default: |
113
|
|
|
throw new PH7InvalidArgumentException('Invalid Type!'); |
114
|
|
|
} |
115
|
|
|
|
116
|
|
|
return $bValid; |
117
|
|
|
} |
118
|
|
|
|
119
|
|
|
/** |
120
|
|
|
* Validate Is String. |
121
|
|
|
* |
122
|
|
|
* @param $sValue |
123
|
|
|
* @param int $iMin Default NULL |
124
|
|
|
* @param int $iMax Default NULL |
125
|
|
|
* |
126
|
|
|
* @return bool |
127
|
|
|
*/ |
128
|
|
|
public function str($sValue, $iMin = null, $iMax = null) |
129
|
|
|
{ |
130
|
|
|
$sValue = filter_var($sValue, FILTER_SANITIZE_STRING); |
131
|
|
|
|
132
|
|
|
if (!empty($sValue)) { |
133
|
|
|
if (!empty($iMin) && $this->oStr->length($sValue) < $iMin) |
134
|
|
|
return false; |
135
|
|
|
elseif (!empty($iMax) && $this->oStr->length($sValue) > $iMax) |
136
|
|
|
return false; |
137
|
|
|
elseif (!is_string($sValue)) |
138
|
|
|
return false; |
139
|
|
|
else |
140
|
|
|
return true; |
141
|
|
|
} |
142
|
|
|
return false; |
143
|
|
|
} |
144
|
|
|
|
145
|
|
|
/** |
146
|
|
|
* Validate if it's an integer. |
147
|
|
|
* |
148
|
|
|
* @param int $iInt |
149
|
|
|
* @param int $iMin Default 0 |
150
|
|
|
* @param int $iMax Default 999999999999 |
151
|
|
|
* |
152
|
|
|
* @return bool |
153
|
|
|
*/ |
154
|
|
|
public function int($iInt, $iMin = 0, $iMax = self::MAX_INT_NUMBER) |
155
|
|
|
{ |
156
|
|
|
$iInt = filter_var($iInt, FILTER_SANITIZE_NUMBER_INT); |
157
|
|
|
|
158
|
|
|
return filter_var($iInt, FILTER_VALIDATE_INT, static::getFilterOption($iMin, $iMax)) !== false; |
159
|
|
|
|
160
|
|
|
} |
161
|
|
|
|
162
|
|
|
/** |
163
|
|
|
* Validate if it's a numeric. |
164
|
|
|
* |
165
|
|
|
* @param string|int (numeric string or integer) $mNumeric |
166
|
|
|
* |
167
|
|
|
* @return bool |
168
|
|
|
*/ |
169
|
|
|
public function numeric($mNumeric) |
170
|
|
|
{ |
171
|
|
|
return is_numeric($mNumeric); |
172
|
|
|
} |
173
|
|
|
|
174
|
|
|
/** |
175
|
|
|
* Validate if it's a digit character. |
176
|
|
|
* |
177
|
|
|
* @param string (numeric string) $sDigit |
178
|
|
|
* |
179
|
|
|
* @return bool |
180
|
|
|
*/ |
181
|
|
|
public function digitChar($sDigit) |
182
|
|
|
{ |
183
|
|
|
return ctype_digit($sDigit); |
184
|
|
|
} |
185
|
|
|
|
186
|
|
|
/** |
187
|
|
|
* Validate if it's a float type. |
188
|
|
|
* |
189
|
|
|
* @param float $fFloat |
190
|
|
|
* @param float|int $mMin Default 0 |
191
|
|
|
* @param float|int $mMax Default 999999999999 |
192
|
|
|
* |
193
|
|
|
* @return bool |
194
|
|
|
*/ |
195
|
|
|
public function float($fFloat, $mMin = 0, $mMax = self::MAX_INT_NUMBER) |
196
|
|
|
{ |
197
|
|
|
$fFloat = filter_var($fFloat, FILTER_SANITIZE_NUMBER_FLOAT); |
198
|
|
|
|
199
|
|
|
return filter_var($fFloat, FILTER_VALIDATE_FLOAT, static::getFilterOption($mMin, $mMax)) !== false; |
200
|
|
|
} |
201
|
|
|
|
202
|
|
|
/** |
203
|
|
|
* Validate if it's a boolean type. |
204
|
|
|
* |
205
|
|
|
* @param bool $bBool |
206
|
|
|
* |
207
|
|
|
* @return bool |
208
|
|
|
*/ |
209
|
|
|
public function bool($bBool) |
210
|
|
|
{ |
211
|
|
|
return filter_var($bBool, FILTER_VALIDATE_BOOLEAN) !== false; |
212
|
|
|
} |
213
|
|
|
|
214
|
|
|
/** |
215
|
|
|
* Validate Username. |
216
|
|
|
* |
217
|
|
|
* @param string $sUsername |
218
|
|
|
* @param int $iMin Default 3 |
219
|
|
|
* @param int $iMax Default 40 |
220
|
|
|
* @param string $sTable Default DbTableName::MEMBER |
221
|
|
|
* |
222
|
|
|
* @return bool |
223
|
|
|
*/ |
224
|
|
|
public function username($sUsername, $iMin = self::DEF_MIN_USERNAME_LENGTH, $iMax = PH7_MAX_USERNAME_LENGTH, $sTable = DbTableName::MEMBER) |
225
|
|
|
{ |
226
|
|
|
$sUsername = trim($sUsername); |
227
|
|
|
|
228
|
|
|
return ( |
229
|
|
|
preg_match('#^' . PH7_USERNAME_PATTERN . '{' . $iMin . ',' . $iMax . '}$#', $sUsername) && |
230
|
|
|
!is_file(PH7_PATH_ROOT . $sUsername . PH7_PAGE_EXT) && !Ban::isUsername($sUsername) && |
231
|
|
|
!(new ExistsCoreModel)->username($sUsername, $sTable) |
232
|
|
|
); |
233
|
|
|
} |
234
|
|
|
|
235
|
|
|
/** |
236
|
|
|
* Validate Password. |
237
|
|
|
* |
238
|
|
|
* @param string $sPwd |
239
|
|
|
* @param int $iMin Default 6 |
240
|
|
|
* @param int $iMax Default 60 |
241
|
|
|
* |
242
|
|
|
* @return bool |
243
|
|
|
*/ |
244
|
|
|
public function password($sPwd, $iMin = self::DEF_MIN_PASS_LENGTH, $iMax = self::DEF_MAX_PASS_LENGTH) |
245
|
|
|
{ |
246
|
|
|
$iPwdLength = $this->oStr->length($sPwd); |
247
|
|
|
|
248
|
|
|
return $iPwdLength >= $iMin && $iPwdLength <= $iMax; |
249
|
|
|
} |
250
|
|
|
|
251
|
|
|
/** |
252
|
|
|
* Validate Email. |
253
|
|
|
* |
254
|
|
|
* @param string $sEmail |
255
|
|
|
* @param bool $bRealHost Checks whether the Email Host is valid. |
256
|
|
|
* |
257
|
|
|
* @return bool |
258
|
|
|
*/ |
259
|
|
|
public function email($sEmail, $bRealHost = false) |
260
|
|
|
{ |
261
|
|
|
$sEmail = filter_var($sEmail, FILTER_SANITIZE_EMAIL); |
262
|
|
|
|
263
|
|
|
if ($bRealHost) { |
264
|
|
|
$sEmailHost = substr(strrchr($sEmail, '@'), 1); |
265
|
|
|
// This function now works with Windows since version PHP 5.3, so we mustn't include the PEAR NET_DNS library. |
266
|
|
|
if (!(checkdnsrr($sEmailHost, 'MX') && checkdnsrr($sEmailHost, 'A'))) { |
267
|
|
|
return false; |
268
|
|
|
} |
269
|
|
|
} |
270
|
|
|
|
271
|
|
|
return filter_var($sEmail, FILTER_VALIDATE_EMAIL) !== false && |
272
|
|
|
$this->oStr->length($sEmail) <= PH7_MAX_EMAIL_LENGTH && !Ban::isEmail($sEmail); |
273
|
|
|
} |
274
|
|
|
|
275
|
|
|
/** |
276
|
|
|
* Validate Birthday. |
277
|
|
|
* |
278
|
|
|
* @param string $sValue The date format must be formatted like this: mm/dd/yyyy |
279
|
|
|
* @param int $iMin Default 18 |
280
|
|
|
* @param int $iMax Default 99 |
281
|
|
|
* |
282
|
|
|
* @return bool |
283
|
|
|
*/ |
284
|
|
|
public function birthDate($sValue, $iMin = self::DEF_MIN_AGE, $iMax = self::DEF_MAX_AGE) |
285
|
|
|
{ |
286
|
|
|
if (empty($sValue) || !preg_match(static::REGEX_DATE_FORMAT, $sValue)) { |
287
|
|
|
return false; |
288
|
|
|
} |
289
|
|
|
|
290
|
|
|
$aBirthDate = explode('/', $sValue); // Format is "mm/dd/yyyy" |
291
|
|
|
if (!checkdate($aBirthDate[0], $aBirthDate[1], $aBirthDate[2])) { |
292
|
|
|
return false; |
293
|
|
|
} |
294
|
|
|
|
295
|
|
|
$iUserAge = (new YearMeasure($aBirthDate[2], $aBirthDate[0], $aBirthDate[1]))->get(); // Get the current user's age |
296
|
|
|
|
297
|
|
|
return $iUserAge >= $iMin && $iUserAge <= $iMax; |
298
|
|
|
} |
299
|
|
|
|
300
|
|
|
/** |
301
|
|
|
* Validate Date. |
302
|
|
|
* |
303
|
|
|
* @param string $sValue |
304
|
|
|
* |
305
|
|
|
* @return bool |
306
|
|
|
*/ |
307
|
|
|
public function date($sValue) |
308
|
|
|
{ |
309
|
|
|
try { |
310
|
|
|
new DateTime($sValue); |
311
|
|
|
return true; |
312
|
|
|
} catch (Exception $oE) { |
313
|
|
|
return false; |
314
|
|
|
} |
315
|
|
|
} |
316
|
|
|
|
317
|
|
|
/** |
318
|
|
|
* Validate URL. |
319
|
|
|
* |
320
|
|
|
* @param string $sUrl |
321
|
|
|
* @param bool $bRealUrl Checks if the current URL exists. |
322
|
|
|
* |
323
|
|
|
* @return bool |
324
|
|
|
*/ |
325
|
|
|
public function url($sUrl, $bRealUrl = false) |
326
|
|
|
{ |
327
|
|
|
$sUrl = filter_var($sUrl, FILTER_SANITIZE_URL); |
328
|
|
|
|
329
|
|
|
if (filter_var($sUrl, FILTER_VALIDATE_URL) === false || $this->oStr->length($sUrl) >= PH7_MAX_URL_LENGTH) { |
330
|
|
|
return false; |
331
|
|
|
} |
332
|
|
|
|
333
|
|
|
if ($bRealUrl) { |
334
|
|
|
/** |
335
|
|
|
* Checks if the URL is valid and contains the HTTP status code '200 OK', '301 Moved Permanently' or '302 Found' |
336
|
|
|
*/ |
337
|
|
|
$rCurl = curl_init(); |
338
|
|
|
curl_setopt_array($rCurl, [CURLOPT_RETURNTRANSFER => true, CURLOPT_URL => $sUrl]); |
339
|
|
|
curl_exec($rCurl); |
340
|
|
|
$iResponse = (int)curl_getinfo($rCurl, CURLINFO_HTTP_CODE); |
341
|
|
|
curl_close($rCurl); |
342
|
|
|
|
343
|
|
|
return $iResponse === 200 || $iResponse === 301 || $iResponse === 302; |
344
|
|
|
} |
345
|
|
|
|
346
|
|
|
return true; |
347
|
|
|
} |
348
|
|
|
|
349
|
|
|
/** |
350
|
|
|
* Validate IP address. |
351
|
|
|
* |
352
|
|
|
* @param string $sIp |
353
|
|
|
* |
354
|
|
|
* @return bool |
355
|
|
|
*/ |
356
|
|
|
public function ip($sIp) |
357
|
|
|
{ |
358
|
|
|
return filter_var($sIp, FILTER_VALIDATE_IP) !== false; |
359
|
|
|
} |
360
|
|
|
|
361
|
|
|
/** |
362
|
|
|
* Validate international phone numbers in EPP format. |
363
|
|
|
* |
364
|
|
|
* @param string $sNumber |
365
|
|
|
* |
366
|
|
|
* @return bool |
367
|
|
|
*/ |
368
|
|
|
public function phone($sNumber) |
369
|
|
|
{ |
370
|
|
|
return preg_match('#^' . Config::getInstance()->values['validate']['phone.pattern'] . '$#', $sNumber); |
371
|
|
|
} |
372
|
|
|
|
373
|
|
|
/** |
374
|
|
|
* @param string $sHexCode |
375
|
|
|
* |
376
|
|
|
* @return bool |
377
|
|
|
*/ |
378
|
|
|
public function hex($sHexCode) |
379
|
|
|
{ |
380
|
|
|
$sHexChars = str_replace(self::HEX_HASH, '', $sHexCode); |
381
|
|
|
$iLength = strlen($sHexChars); |
382
|
|
|
|
383
|
|
|
return strpos($sHexCode, '#') !== false && $iLength >= self::MIN_HEX_LENGTH && $iLength <= self::MAX_HEX_LENGTH; |
384
|
|
|
} |
385
|
|
|
|
386
|
|
|
/** |
387
|
|
|
* Validate Name. |
388
|
|
|
* |
389
|
|
|
* @param string $sName |
390
|
|
|
* @param int $iMin Default 2 |
391
|
|
|
* @param int $iMax Default 20 |
392
|
|
|
* |
393
|
|
|
* @return bool |
394
|
|
|
*/ |
395
|
|
|
public function name($sName, $iMin = self::MIN_NAME_LENGTH, $iMax = self::MAX_NAME_LENGTH) |
396
|
|
|
{ |
397
|
|
|
// Check the length |
398
|
|
|
if ($this->oStr->length($sName) < $iMin || $this->oStr->length($sName) > $iMax) { |
399
|
|
|
return false; |
400
|
|
|
} |
401
|
|
|
|
402
|
|
|
// Check the name pattern. Name cannot contain any of the below characters |
403
|
|
|
if (preg_match(static::REGEX_NOT_NAME_PATTERN, $sName)) { |
404
|
|
|
return false; |
405
|
|
|
} |
406
|
|
|
|
407
|
|
|
return true; |
408
|
|
|
} |
409
|
|
|
|
410
|
|
|
/* |
411
|
|
|
/** |
412
|
|
|
* Check Email with test for check if the host email is valid. |
413
|
|
|
* |
414
|
|
|
* @param string $sEmail |
415
|
|
|
* |
416
|
|
|
* @return bool |
417
|
|
|
*/ |
418
|
|
|
/* |
|
|
|
|
419
|
|
|
public function emailHost($sEmail) |
420
|
|
|
{ |
421
|
|
|
// The email address must be properly formatted |
422
|
|
|
if (!$this->email($sEmail)) |
423
|
|
|
return false; |
424
|
|
|
|
425
|
|
|
// It gets domain |
426
|
|
|
list(, $sDomain ) = explode('@', $sEmail); |
427
|
|
|
// We look for MX records in DNS |
428
|
|
|
if (getmxrr($sDomain, $aMxHost)) |
429
|
|
|
$sConnectAddress = $aMxHost[0]; |
430
|
|
|
else |
431
|
|
|
$sConnectAddress = $sDomain; |
432
|
|
|
// We created the connection on SMTP port (25) |
433
|
|
|
if ($rConnect = @fsockopen($sConnectAddress, 25, $iErrno, $sErrStr)) |
434
|
|
|
{ |
435
|
|
|
if (preg_match("/^220/", $sOut = fgets($rConnect, 1024))) |
436
|
|
|
{ |
437
|
|
|
fputs($rConnect, "HELO {$_SERVER['HTTP_HOST']}\r\n"); |
438
|
|
|
$sOut = fgets($rConnect, 1024); |
439
|
|
|
fputs($rConnect, "MAIL FROM: <{$sEmail}>\r\n"); |
440
|
|
|
$sFrom = fgets($rConnect, 1024); |
441
|
|
|
fputs($rConnect, "RCPT TO: <{$sEmail}>\r\n"); |
442
|
|
|
$sTo = fgets($rConnect, 1024); |
443
|
|
|
fputs($rConnect, "QUIT\r\n"); |
444
|
|
|
fclose($rConnect); |
445
|
|
|
// If the code returned by the RCPT TO is 250 or 251 (cf: RFC) |
446
|
|
|
// Then the address exists |
447
|
|
|
if (!preg_match("/^250/", $sTo) && !preg_match("/^251/", $sTo)) |
448
|
|
|
// Address rejected by the serve |
449
|
|
|
return false; |
450
|
|
|
else |
451
|
|
|
// Accepted by the server address |
452
|
|
|
return true; |
453
|
|
|
} |
454
|
|
|
else |
455
|
|
|
{ |
456
|
|
|
// The server did not respond |
457
|
|
|
return false; |
458
|
|
|
} |
459
|
|
|
} |
460
|
|
|
else |
461
|
|
|
{ |
462
|
|
|
// You can display an error message by uncommenting the following two lines or leave the return value of the false boolean. |
463
|
|
|
// echo "Cannot connect to the mail server\n"; |
464
|
|
|
// echo "$iErrno - $sErrStr\n"; |
465
|
|
|
return false; |
466
|
|
|
} |
467
|
|
|
} |
468
|
|
|
//*/ |
469
|
|
|
|
470
|
|
|
/** |
471
|
|
|
* Get option for some filter_var(). |
472
|
|
|
* |
473
|
|
|
* @param float|int $mMin Minimum range. |
474
|
|
|
* @param float|int $mMax Maximum range. |
475
|
|
|
* |
476
|
|
|
* @return array |
477
|
|
|
*/ |
478
|
|
|
protected static function getFilterOption($mMin, $mMax) |
479
|
|
|
{ |
480
|
|
|
return ['options' => ['min_range' => $mMin, 'max_range' => $mMax]]; |
481
|
|
|
} |
482
|
|
|
|
483
|
|
|
} |
484
|
|
|
|
The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.
The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.
To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.