YetiForceCompany /
YetiForceCRM
This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include, or for example
via PHP's auto-loading mechanism.
| 1 | <?php |
||||
| 2 | /** |
||||
| 3 | * Purifier file. |
||||
| 4 | * |
||||
| 5 | * @package App |
||||
| 6 | * |
||||
| 7 | * @license YetiForce Public License 6.5 (licenses/LicenseEN.txt or yetiforce.com) |
||||
| 8 | * @copyright YetiForce S.A. |
||||
| 9 | * @author Mariusz Krzaczkowski <[email protected]> |
||||
| 10 | * @author Radosław Skrzypczak <[email protected]> |
||||
| 11 | */ |
||||
| 12 | |||||
| 13 | namespace App; |
||||
| 14 | |||||
| 15 | /** |
||||
| 16 | * Purifier basic class. |
||||
| 17 | */ |
||||
| 18 | class Purifier |
||||
| 19 | { |
||||
| 20 | /** @var string Purify type date in user format. */ |
||||
| 21 | public const DATE_USER_FORMAT = 'DateInUserFormat'; |
||||
| 22 | |||||
| 23 | /** @var string Purify type integer. */ |
||||
| 24 | public const INTEGER = 'Integer'; |
||||
| 25 | |||||
| 26 | /** @var string Purify type standard. */ |
||||
| 27 | public const STANDARD = 'Standard'; |
||||
| 28 | |||||
| 29 | /** @var string Purify type sql. */ |
||||
| 30 | public const SQL = 'Sql'; |
||||
| 31 | |||||
| 32 | /** @var string Purify type text. */ |
||||
| 33 | public const TEXT = 'Text'; |
||||
| 34 | |||||
| 35 | /** @var string Purify type number. */ |
||||
| 36 | public const NUMBER = 'Number'; |
||||
| 37 | |||||
| 38 | /** @var string Purify type html. */ |
||||
| 39 | public const HTML = 'Html'; |
||||
| 40 | |||||
| 41 | /** @var string Purify type boolean. */ |
||||
| 42 | public const BOOL = 'Bool'; |
||||
| 43 | |||||
| 44 | /** @var string Purify type url. */ |
||||
| 45 | public const URL = 'Url'; |
||||
| 46 | |||||
| 47 | /** @var string Purify type Alnum. */ |
||||
| 48 | public const ALNUM = 'Alnum'; |
||||
| 49 | |||||
| 50 | /** @var string Purify type Alnum 2 (A-Za-z0-9\/\+\-). */ |
||||
| 51 | public const ALNUM2 = 'AlnumType2'; |
||||
| 52 | |||||
| 53 | /** @var string Purify type AlnumExtended. */ |
||||
| 54 | public const ALNUM_EXTENDED = 'AlnumExtended'; |
||||
| 55 | |||||
| 56 | /** @var string Purify type Digits. */ |
||||
| 57 | public const DIGITS = 'Digits'; |
||||
| 58 | |||||
| 59 | /** @var string Purify type HTML text parser */ |
||||
| 60 | public const HTML_TEXT_PARSER = 'HtmlTextParser'; |
||||
| 61 | |||||
| 62 | /** @var string Purify type Path. */ |
||||
| 63 | public const PATH = 'Path'; |
||||
| 64 | |||||
| 65 | /** @var string Purify type email. */ |
||||
| 66 | public const EMAIL = 'Email'; |
||||
| 67 | |||||
| 68 | /** |
||||
| 69 | * Default charset. |
||||
| 70 | * |
||||
| 71 | * @var string |
||||
| 72 | */ |
||||
| 73 | public static $defaultCharset; |
||||
| 74 | |||||
| 75 | /** |
||||
| 76 | * Cache for purify instance. |
||||
| 77 | * |
||||
| 78 | * @var bool|\HTMLPurifier |
||||
| 79 | */ |
||||
| 80 | private static $purifyInstanceCache = false; |
||||
| 81 | |||||
| 82 | /** |
||||
| 83 | * Cache for Html purify instance. |
||||
| 84 | * |
||||
| 85 | * @var bool|\HTMLPurifier |
||||
| 86 | */ |
||||
| 87 | private static $purifyHtmlInstanceCache = false; |
||||
| 88 | |||||
| 89 | /** @var bool|\HTMLPurifier Cache for Html template purify instance. */ |
||||
| 90 | private static $purifyTextParserInstanceCache = false; |
||||
| 91 | 52 | ||||
| 92 | /** |
||||
| 93 | 52 | * Html events attributes. |
|||
| 94 | 3 | * |
|||
| 95 | * @var string |
||||
| 96 | 51 | */ |
|||
| 97 | 51 | private static $htmlEventAttributes = 'onerror|onblur|onchange|oncontextmenu|onfocus|oninput|oninvalid|onreset|onsearch|onselect|onsubmit|onkeydown|onkeypress|onkeyup|' . |
|||
| 98 | 51 | 'onclick|ondblclick|ondrag|ondragend|ondragenter|ondragleave|ondragover|ondragstart|ondrop|onmousedown|onmousemove|onmouseout|onmouseover|onbeforepaste|onresizestart|onactivate|' . |
|||
| 99 | 51 | 'onmouseup|onmousewheel|onscroll|onwheel|oncopy|oncut|onpaste|onload|onselectionchange|onabort|onselectstart|ondragdrop|onmouseleave|onmouseenter|onunload|onresize|onmessage|' . |
|||
| 100 | 9 | 'onpropertychange|onfilterchange|onstart|onfinish|onbounce|onrowsinserted|onrowsdelete|onrowexit|onrowenter|ondatasetcomplete|ondatasetchanged|ondataavailable|oncellchange|' . |
|||
| 101 | 'onbeforeupdate|onafterupdate|onerrorupdate|onhelp|onbeforeprint|onafterprint|oncontrolselect|onfocusout|onfocusin|ondeactivate|onbeforeeditfocus|onbeforedeactivate|onbeforeactivate|' . |
||||
| 102 | 'onresizeend|onmovestart|onmoveend|onmove|onbeforecopy|onbeforecut|onbeforeunload|onhashchange|onoffline|ononline|onreadystatechange|onstop|onlosecapture'; |
||||
| 103 | |||||
| 104 | 46 | /** |
|||
| 105 | * Remove unnecessary code list. |
||||
| 106 | * |
||||
| 107 | * @var string[] |
||||
| 108 | */ |
||||
| 109 | private static $removeUnnecessaryCode = [ |
||||
| 110 | 'href="javascript:window.history.back();"', |
||||
| 111 | 'href="javascript:void(0);"', |
||||
| 112 | 46 | ]; |
|||
| 113 | |||||
| 114 | 46 | /** |
|||
| 115 | 2 | * Purify (Cleanup) malicious snippets of code from the input. |
|||
| 116 | 2 | * |
|||
| 117 | 2 | * @param string $input |
|||
| 118 | * @param bool $loop Purify values in the loop |
||||
| 119 | 46 | * |
|||
| 120 | 45 | * @return string |
|||
| 121 | 43 | */ |
|||
| 122 | 43 | public static function purify($input, $loop = true) |
|||
| 123 | 43 | { |
|||
| 124 | 43 | if (empty($input)) { |
|||
| 125 | 43 | return $input; |
|||
| 126 | 43 | } |
|||
| 127 | $value = $input; |
||||
| 128 | if (!\is_array($input)) { |
||||
|
0 ignored issues
–
show
introduced
by
Loading history...
|
|||||
| 129 | 43 | $cacheKey = md5($input); |
|||
| 130 | if (Cache::has('purify', $cacheKey)) { |
||||
| 131 | return Cache::get('purify', $cacheKey); |
||||
| 132 | 44 | } |
|||
| 133 | } |
||||
| 134 | // Initialize the instance if it has not yet done |
||||
| 135 | if (!static::$purifyInstanceCache) { |
||||
|
0 ignored issues
–
show
|
|||||
| 136 | $config = \HTMLPurifier_Config::createDefault(); |
||||
| 137 | $config->set('Core.Encoding', static::$defaultCharset); |
||||
| 138 | $config->set('Cache.SerializerPermissions', 0775); |
||||
| 139 | $config->set('Cache.SerializerPath', ROOT_DIRECTORY . \DIRECTORY_SEPARATOR . 'cache' . \DIRECTORY_SEPARATOR . 'vtlib'); |
||||
| 140 | $config->set('HTML.Allowed', ''); |
||||
| 141 | static::$purifyInstanceCache = new \HTMLPurifier($config); |
||||
| 142 | } |
||||
| 143 | 22 | if (static::$purifyInstanceCache) { |
|||
| 144 | // Composite type |
||||
| 145 | 22 | if (\is_array($input)) { |
|||
|
0 ignored issues
–
show
|
|||||
| 146 | 1 | $value = []; |
|||
| 147 | foreach ($input as $k => $v) { |
||||
| 148 | 21 | $value[$k] = static::purify($v); |
|||
| 149 | 21 | } |
|||
| 150 | 21 | } elseif (\is_string($input)) { |
|||
|
0 ignored issues
–
show
|
|||||
| 151 | 8 | $input = str_replace(["\r\n"], "\n", $input); |
|||
| 152 | static::purifyHtmlEventAttributes($input); |
||||
| 153 | $value = static::$purifyInstanceCache->purify(static::decodeHtml($input)); |
||||
| 154 | 19 | if ($loop) { |
|||
| 155 | 1 | $last = ''; |
|||
| 156 | 1 | while ($last !== $value) { |
|||
| 157 | $last = $value; |
||||
| 158 | 19 | $value = static::purify($value, false); |
|||
| 159 | 19 | } |
|||
| 160 | 19 | } |
|||
| 161 | 16 | Cache::save('purify', $cacheKey, $value, Cache::SHORT); |
|||
|
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
|
|||||
| 162 | 16 | } |
|||
| 163 | 16 | } |
|||
| 164 | 14 | return $value; |
|||
| 165 | 14 | } |
|||
| 166 | |||||
| 167 | /** |
||||
| 168 | 16 | * Purify HTML (Cleanup) malicious snippets of code from the input. |
|||
| 169 | 16 | * |
|||
| 170 | * @param string $input |
||||
| 171 | 16 | * @param bool $loop Purify values in the loop |
|||
| 172 | * |
||||
| 173 | * @return string |
||||
| 174 | */ |
||||
| 175 | public static function purifyHtml(string $input, $loop = true): string |
||||
| 176 | { |
||||
| 177 | if (empty($input)) { |
||||
| 178 | return $input; |
||||
| 179 | 61 | } |
|||
| 180 | $value = $input; |
||||
| 181 | 61 | $cacheKey = md5($input); |
|||
| 182 | 5 | if (Cache::has('purifyHtml', $cacheKey)) { |
|||
| 183 | 5 | return Cache::get('purifyHtml', $cacheKey); |
|||
| 184 | } |
||||
| 185 | 56 | ||||
| 186 | // Initialize the instance if it has not yet done |
||||
| 187 | if (!static::$purifyHtmlInstanceCache) { |
||||
|
0 ignored issues
–
show
|
|||||
| 188 | $config = static::getHtmlConfig(); |
||||
| 189 | static::$purifyHtmlInstanceCache = new \HTMLPurifier($config); |
||||
| 190 | } |
||||
| 191 | if (static::$purifyHtmlInstanceCache) { |
||||
| 192 | 1 | $input = str_replace(["\r\n"], "\n", $input); |
|||
| 193 | $value = static::$purifyHtmlInstanceCache->purify($input); |
||||
| 194 | 1 | $value = static::removeUnnecessaryCode($value); |
|||
| 195 | 1 | if ($loop) { |
|||
| 196 | 1 | $last = ''; |
|||
| 197 | 1 | while ($last !== $value) { |
|||
| 198 | 1 | $last = $value; |
|||
| 199 | 1 | $value = static::purifyHtml($value, false); |
|||
| 200 | 1 | } |
|||
| 201 | 1 | } |
|||
| 202 | 1 | static::purifyHtmlEventAttributes(static::decodeHtml($value)); |
|||
| 203 | 1 | $value = preg_replace("/(^[\r\n]*|[\r\n]+)[\\s\t]*[\r\n]+/", "\n", $value); |
|||
| 204 | 1 | Cache::save('purifyHtml', $cacheKey, $value, Cache::SHORT); |
|||
| 205 | 1 | } |
|||
| 206 | 1 | return $value; |
|||
| 207 | 1 | } |
|||
| 208 | 1 | ||||
| 209 | /** |
||||
| 210 | * Purify HTML (Cleanup) malicious snippets of code from text parser. |
||||
| 211 | * |
||||
| 212 | * @param string $input |
||||
| 213 | * @param bool $loop Purify values in the loop |
||||
| 214 | * |
||||
| 215 | * @return string |
||||
| 216 | */ |
||||
| 217 | 1 | public static function purifyTextParser($input, $loop = true): string |
|||
| 218 | 1 | { |
|||
| 219 | 1 | if (empty($input)) { |
|||
| 220 | 1 | return $input; |
|||
| 221 | 1 | } |
|||
| 222 | 1 | $cacheKey = md5($input); |
|||
| 223 | 1 | if (Cache::has('purifyTextParser', $cacheKey)) { |
|||
| 224 | 1 | return Cache::get('purifyTextParser', $cacheKey); |
|||
| 225 | 1 | } |
|||
| 226 | 1 | if (!static::$purifyTextParserInstanceCache) { |
|||
|
0 ignored issues
–
show
|
|||||
| 227 | 1 | $config = static::getHtmlConfig(['directives' => ['HTML.AllowedCommentsRegexp' => '/^(\s+{% |{% )[\s\S]+( %}| %}\s+)$/u']]); |
|||
| 228 | 1 | static::$purifyTextParserInstanceCache = new \HTMLPurifier($config); |
|||
| 229 | 1 | } |
|||
| 230 | $value = static::$purifyTextParserInstanceCache->purify($input); |
||||
| 231 | $value = static::removeUnnecessaryCode($value); |
||||
| 232 | static::purifyHtmlEventAttributes($value); |
||||
| 233 | if ($loop) { |
||||
| 234 | $last = ''; |
||||
| 235 | while ($last !== $value) { |
||||
| 236 | $last = $value; |
||||
| 237 | 1 | $value = static::purifyTextParser($value, false); |
|||
| 238 | 1 | } |
|||
| 239 | } |
||||
| 240 | $value = preg_replace("/(^[\r\n]*|[\r\n]+)[\\s\t]*[\r\n]+/", "\n", $value); |
||||
| 241 | Cache::save('purifyTextParser', $cacheKey, $value, Cache::SHORT); |
||||
| 242 | return $value; |
||||
| 243 | 1 | } |
|||
| 244 | 1 | ||||
| 245 | /** |
||||
| 246 | * To purify malicious html event attributes. |
||||
| 247 | 1 | * |
|||
| 248 | 1 | * @param string $value |
|||
| 249 | 1 | */ |
|||
| 250 | 1 | public static function purifyHtmlEventAttributes(string $value): void |
|||
| 251 | 1 | { |
|||
| 252 | 1 | if (preg_match('#(<[^><]+?[\x00-\x20"\'])([^a-z_\\-]on\\w*|xmlns)(\\s*=\\s*[^><]*)([><]*)#i', $value, $matches)) { |
|||
| 253 | 1 | \App\Log::error('purifyHtmlEventAttributes: ' . $value, 'IllegalValue'); |
|||
| 254 | 1 | throw new Exceptions\IllegalValue('ERR_NOT_ALLOWED_VALUE|1|' . print_r($matches, true) . "||$value", 406); |
|||
|
0 ignored issues
–
show
Are you sure
print_r($matches, true) of type string|true can be used in concatenation?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
| 255 | } |
||||
| 256 | 1 | if (preg_match('#<([^><]+?)(' . static::$htmlEventAttributes . ')(\\s*=\\s*[^><]*)([>]*)#i', $value, $matches)) { |
|||
|
0 ignored issues
–
show
|
|||||
| 257 | 1 | \App\Log::error('purifyHtmlEventAttributes: ' . $value, 'IllegalValue'); |
|||
| 258 | throw new Exceptions\IllegalValue('ERR_NOT_ALLOWED_VALUE|2|' . print_r($matches, true) . "||$value", 406); |
||||
| 259 | 1 | } |
|||
| 260 | 1 | if (preg_match('#<([^><]+?)javascript:[\w\.]+\(([>]*)#i', $value, $matches)) { |
|||
| 261 | 1 | \App\Log::error('purifyHtmlEventAttributes: ' . $value, 'IllegalValue'); |
|||
| 262 | 1 | throw new Exceptions\IllegalValue('ERR_NOT_ALLOWED_VALUE|3|' . print_r($matches, true) . "||$value", 406); |
|||
| 263 | 1 | } |
|||
| 264 | 1 | } |
|||
| 265 | 1 | ||||
| 266 | 1 | /** |
|||
| 267 | 1 | * Remove unnecessary code. |
|||
| 268 | * |
||||
| 269 | 1 | * @param string $value |
|||
| 270 | 1 | * |
|||
| 271 | 1 | * @return string |
|||
| 272 | */ |
||||
| 273 | public static function removeUnnecessaryCode(string $value): string |
||||
| 274 | { |
||||
| 275 | foreach (self::$removeUnnecessaryCode as $code) { |
||||
| 276 | if (false !== stripos($value, $code)) { |
||||
| 277 | $value = str_ireplace($code, '', $value); |
||||
| 278 | } |
||||
| 279 | } |
||||
| 280 | return $value; |
||||
|
0 ignored issues
–
show
|
|||||
| 281 | } |
||||
| 282 | 3 | ||||
| 283 | /** |
||||
| 284 | 3 | * Get html config. |
|||
| 285 | 3 | * |
|||
| 286 | * @param array $options |
||||
| 287 | 1 | * |
|||
| 288 | 1 | * @return \HTMLPurifier_Config |
|||
| 289 | */ |
||||
| 290 | public static function getHtmlConfig(array $options = []) |
||||
| 291 | { |
||||
| 292 | $config = \HTMLPurifier_Config::createDefault(); |
||||
| 293 | $config->set('Core.Encoding', static::$defaultCharset); |
||||
| 294 | $config->set('Cache.SerializerPermissions', 0775); |
||||
| 295 | $config->set('Cache.SerializerPath', ROOT_DIRECTORY . \DIRECTORY_SEPARATOR . 'cache' . \DIRECTORY_SEPARATOR . 'vtlib'); |
||||
| 296 | $config->set('HTML.Doctype', 'HTML 4.01 Transitional'); |
||||
| 297 | $config->set('CSS.AllowTricky', true); |
||||
| 298 | $config->set('CSS.Proprietary', true); |
||||
| 299 | $config->set('CSS.Trusted', true); |
||||
| 300 | $config->set('Core.RemoveInvalidImg', true); |
||||
| 301 | $config->set('HTML.SafeIframe', true); |
||||
| 302 | $config->set('HTML.SafeEmbed', true); |
||||
| 303 | $config->set('URI.SafeIframeRegexp', '%^(http:|https:)?//(www\.youtube(?:-nocookie)?\.com/embed/|player\.vimeo\.com/video/)%'); |
||||
| 304 | $config->set('HTML.DefinitionRev', 1); |
||||
| 305 | $config->set('HTML.TargetBlank', true); |
||||
| 306 | 48 | $config->set('Attr.EnableID', true); |
|||
| 307 | $config->set('CSS.MaxImgLength', null); |
||||
| 308 | 48 | $config->set('URI.AllowedSchemes', [ |
|||
| 309 | 1 | 'http' => true, |
|||
| 310 | 1 | 'https' => true, |
|||
| 311 | 1 | 'mailto' => true, |
|||
| 312 | 'ftp' => true, |
||||
| 313 | 'nntp' => true, |
||||
| 314 | 48 | 'news' => true, |
|||
| 315 | 'tel' => true, |
||||
| 316 | 48 | 'data' => true, |
|||
| 317 | 45 | ]); |
|||
| 318 | 3 | foreach ($options['directives'] ?? [] as $key => $value) { |
|||
| 319 | 3 | $config->set($key, $value); |
|||
| 320 | 45 | } |
|||
| 321 | 43 | if ($def = $config->getHTMLDefinition(true)) { |
|||
| 322 | 4 | $def->addElement('section', 'Block', 'Flow', 'Common'); |
|||
| 323 | 4 | $def->addElement('nav', 'Block', 'Flow', 'Common'); |
|||
| 324 | 41 | $def->addElement('article', 'Block', 'Flow', 'Common'); |
|||
| 325 | 2 | $def->addElement('aside', 'Block', 'Flow', 'Common'); |
|||
| 326 | $def->addElement('header', 'Block', 'Flow', 'Common'); |
||||
| 327 | $def->addElement('footer', 'Block', 'Flow', 'Common'); |
||||
| 328 | 2 | $def->addElement('address', 'Block', 'Flow', 'Common'); |
|||
| 329 | 2 | $def->addElement('hgroup', 'Block', 'Required: h1 | h2 | h3 | h4 | h5 | h6', 'Common'); |
|||
| 330 | 39 | $def->addElement('figure', 'Block', 'Optional: (figcaption, Flow) | (Flow, figcaption) | Flow', 'Common'); |
|||
| 331 | 2 | $def->addElement('figcaption', 'Inline', 'Flow', 'Common'); |
|||
| 332 | 2 | $def->addElement('video', 'Block', 'Optional: (source, Flow) | (Flow, source) | Flow', 'Common', [ |
|||
| 333 | 37 | 'src' => 'URI', |
|||
| 334 | 'type' => 'Text', |
||||
| 335 | 'width' => 'Length', |
||||
| 336 | 37 | 'height' => 'Length', |
|||
| 337 | 1 | 'poster' => 'URI', |
|||
| 338 | 1 | 'preload' => 'Enum#auto,metadata,none', |
|||
| 339 | 36 | 'controls' => 'Bool', |
|||
| 340 | 2 | ]); |
|||
| 341 | 2 | $def->addElement('audio', 'Block', 'Optional: (source, Flow) | (Flow, source) | Flow', 'Common', [ |
|||
| 342 | 2 | 'src' => 'URI', |
|||
| 343 | 2 | 'type' => 'Text', |
|||
| 344 | 2 | 'preload' => 'Enum#auto,metadata,none', |
|||
| 345 | 1 | 'controls' => 'Bool', |
|||
| 346 | ]); |
||||
| 347 | $def->addElement('source', 'Block', 'Flow', 'Common', [ |
||||
| 348 | 2 | 'src' => 'URI', |
|||
| 349 | 1 | 'type' => 'Text', |
|||
| 350 | ]); |
||||
| 351 | 2 | $def->addElement('link', 'Block', 'Flow', 'Common', [ |
|||
| 352 | 34 | 'href' => 'URI', |
|||
| 353 | 2 | 'type' => 'Text', |
|||
| 354 | 2 | 'rel' => 'Text', |
|||
| 355 | 32 | ]); |
|||
| 356 | $def->addElement('yetiforce', 'Inline', 'Inline', 'Common', [ |
||||
| 357 | 'type' => 'Text', |
||||
| 358 | 32 | 'crm-id' => 'Length', |
|||
| 359 | 'attachment-id' => 'Length', |
||||
| 360 | 'height' => 'Length', |
||||
| 361 | 32 | 'width' => 'Length', |
|||
| 362 | 2 | ]); |
|||
| 363 | 2 | $def->addElement('s', 'Inline', 'Inline', 'Common'); |
|||
| 364 | 30 | $def->addElement('var', 'Inline', 'Inline', 'Common'); |
|||
| 365 | 2 | $def->addElement('sub', 'Inline', 'Inline', 'Common'); |
|||
| 366 | 2 | $def->addElement('sup', 'Inline', 'Inline', 'Common'); |
|||
| 367 | 1 | $def->addElement('mark', 'Inline', 'Inline', 'Common'); |
|||
| 368 | $def->addElement('wbr', 'Inline', 'Empty', 'Core'); |
||||
| 369 | 2 | $def->addElement('ins', 'Block', 'Flow', 'Common', ['cite' => 'URI', 'datetime' => 'CDATA']); |
|||
| 370 | 28 | $def->addElement('del', 'Block', 'Flow', 'Common', ['cite' => 'URI', 'datetime' => 'CDATA']); |
|||
| 371 | 1 | // TinyMCE |
|||
| 372 | 1 | $def->addAttribute('img', 'data-mce-src', 'Text'); |
|||
| 373 | 1 | $def->addAttribute('img', 'data-mce-json', 'Text'); |
|||
| 374 | // Others |
||||
| 375 | 1 | $def->addAttribute('iframe', 'allowfullscreen', 'Bool'); |
|||
| 376 | 28 | $def->addAttribute('table', 'height', 'Text'); |
|||
| 377 | $def->addAttribute('td', 'border', 'Text'); |
||||
| 378 | $def->addAttribute('th', 'border', 'Text'); |
||||
| 379 | $def->addAttribute('tr', 'width', 'Text'); |
||||
| 380 | $def->addAttribute('tr', 'height', 'Text'); |
||||
| 381 | 28 | $def->addAttribute('tr', 'border', 'Text'); |
|||
| 382 | $def->addAttribute('a', 'data-id', 'Text'); |
||||
| 383 | $def->addAttribute('a', 'data-module', 'Text'); |
||||
| 384 | 28 | } |
|||
| 385 | 1 | if ($uriDef = $config->getURIDefinition()) { |
|||
| 386 | 1 | $uriDef->addFilter(new Extension\HTMLPurifier\Domain(), $config); |
|||
| 387 | 28 | } |
|||
| 388 | 3 | return $config; |
|||
| 389 | 2 | } |
|||
| 390 | |||||
| 391 | 3 | /** |
|||
| 392 | 26 | * Function to return the valid SQl input. |
|||
| 393 | 2 | * |
|||
| 394 | 1 | * @param string $input |
|||
| 395 | * @param bool $skipEmpty Skip the check if string is empty |
||||
| 396 | 2 | * |
|||
| 397 | 24 | * @return bool|string |
|||
| 398 | 2 | */ |
|||
| 399 | 2 | public static function purifySql($input, $skipEmpty = true) |
|||
| 400 | 22 | { |
|||
| 401 | 2 | if ((empty($input) && $skipEmpty) || Validator::sql($input)) { |
|||
| 402 | 1 | return $input; |
|||
| 403 | } |
||||
| 404 | 2 | \App\Log::error('purifySql: ' . $input, 'IllegalValue'); |
|||
| 405 | 20 | throw new \App\Exceptions\IllegalValue('ERR_NOT_ALLOWED_VALUE||' . $input, 406); |
|||
| 406 | } |
||||
| 407 | |||||
| 408 | 20 | /** |
|||
| 409 | * Purify by data type. |
||||
| 410 | * |
||||
| 411 | 20 | * Type list: |
|||
| 412 | * Standard - only words |
||||
| 413 | 20 | * 1 - only words |
|||
| 414 | 18 | * Alnum - word and int |
|||
| 415 | * 2 - word and int |
||||
| 416 | 46 | * |
|||
| 417 | 13 | * @param mixed $input |
|||
| 418 | 13 | * @param string $type Data type that is only acceptable |
|||
| 419 | * @param mixed $convert |
||||
| 420 | * |
||||
| 421 | 33 | * @return mixed |
|||
| 422 | */ |
||||
| 423 | public static function purifyByType($input, $type, $convert = false) |
||||
| 424 | { |
||||
| 425 | if (\is_array($input)) { |
||||
| 426 | $value = []; |
||||
| 427 | foreach ($input as $k => $v) { |
||||
| 428 | $value[$k] = static::purifyByType($v, $type); |
||||
| 429 | } |
||||
| 430 | } else { |
||||
| 431 | 3 | $value = null; |
|||
| 432 | switch ($type) { |
||||
| 433 | 3 | case 'Standard': // only word |
|||
| 434 | case 1: |
||||
| 435 | $value = Validator::standard($input) ? $input : null; |
||||
| 436 | break; |
||||
| 437 | case 'Alnum': // word and int |
||||
| 438 | case 2: |
||||
| 439 | $value = Validator::alnum($input) ? $input : null; |
||||
| 440 | break; |
||||
| 441 | case 'AlnumExtended': |
||||
| 442 | $value = preg_match('/^[\sA-Za-z0-9\,\_\.\=\-]+$/', $input) ? $input : null; |
||||
| 443 | break; |
||||
| 444 | 5872 | case 'AlnumType2': |
|||
| 445 | $value = preg_match('/^[\sA-Za-z0-9\/\+\-]+$/', $input) ? $input : null; |
||||
| 446 | 5872 | break; |
|||
| 447 | case 'DateInUserFormat': // date in user format |
||||
| 448 | if (!$input) { |
||||
| 449 | return ''; |
||||
| 450 | } |
||||
| 451 | $value = Validator::dateInUserFormat($input) ? ($convert ? Fields\Date::formatToDB($input) : $input) : null; |
||||
| 452 | break; |
||||
| 453 | case 'TimeInUserFormat': |
||||
| 454 | $value = Validator::timeInUserFormat($input) ? ($convert ? Fields\Time::formatToDB($input) : $input) : null; |
||||
| 455 | break; |
||||
| 456 | 84 | case 'DateRangeUserFormat': // date range user format |
|||
| 457 | $dateFormat = User::getCurrentUserModel()->getDetail('date_format'); |
||||
| 458 | 84 | $v = []; |
|||
| 459 | foreach (explode(',', $input) as $i) { |
||||
| 460 | if (!Validator::dateInUserFormat($i)) { |
||||
| 461 | $v = []; |
||||
| 462 | break; |
||||
| 463 | } |
||||
| 464 | [$y, $m, $d] = Fields\Date::explode($i, $dateFormat); |
||||
| 465 | if (checkdate((int) $m, (int) $d, (int) $y) && is_numeric($y) && is_numeric($m) && is_numeric($d)) { |
||||
| 466 | $v[] = \DateTimeField::convertToDBFormat($i); |
||||
| 467 | } |
||||
| 468 | } |
||||
| 469 | if ($v) { |
||||
| 470 | $value = $v; |
||||
| 471 | } |
||||
| 472 | break; |
||||
| 473 | case 'DateTimeInIsoFormat': // date in base format yyyy-mm-dd |
||||
| 474 | $value = Validator::dateTimeInIsoFormat($input) ? date('Y-m-d H:i:s', strtotime($input)) : null; |
||||
| 475 | break; |
||||
| 476 | case 'Bool': |
||||
| 477 | $value = self::bool($input); |
||||
| 478 | break; |
||||
| 479 | case 'NumberInUserFormat': // number in user format |
||||
| 480 | $input = Fields\Double::formatToDb($rawInput = $input); |
||||
| 481 | if (is_numeric($input) && Fields\Double::formatToDisplay($input, false) === Fields\Double::truncateZeros($rawInput)) { |
||||
| 482 | $value = $input; |
||||
| 483 | } |
||||
| 484 | break; |
||||
| 485 | case 'Number': |
||||
| 486 | $dbFormat = Fields\Double::formatToDb($input); |
||||
| 487 | if (is_numeric($dbFormat) && Fields\Double::formatToDisplay($dbFormat, false) === Fields\Double::truncateZeros($input)) { |
||||
| 488 | $value = $input; |
||||
| 489 | } |
||||
| 490 | break; |
||||
| 491 | case 'Double': |
||||
| 492 | if (false !== ($input = filter_var($input, FILTER_VALIDATE_FLOAT))) { |
||||
| 493 | $value = $input; |
||||
| 494 | } |
||||
| 495 | break; |
||||
| 496 | case 'Phone': |
||||
| 497 | $value = preg_match('/^[\s0-9+\-()]+$/', $input) ? $input : null; |
||||
| 498 | break; |
||||
| 499 | case 'Email': |
||||
| 500 | if (!$input) { |
||||
| 501 | return ''; |
||||
| 502 | } |
||||
| 503 | $value = Validator::email($input) ? $input : null; |
||||
| 504 | break; |
||||
| 505 | case 'Html': |
||||
| 506 | $value = self::purifyHtml($input); |
||||
| 507 | break; |
||||
| 508 | case 'Integer': // Integer |
||||
| 509 | if (false !== ($input = filter_var($input, FILTER_VALIDATE_INT))) { |
||||
| 510 | $value = $input; |
||||
| 511 | } |
||||
| 512 | break; |
||||
| 513 | case 'Digits': // Digits - eg. 000523 |
||||
| 514 | if (false !== ($input = filter_var($input, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => '/^[0-9]+$/']]))) { |
||||
| 515 | $value = $input; |
||||
| 516 | } |
||||
| 517 | break; |
||||
| 518 | case 'Color': // colors |
||||
| 519 | $value = preg_match('/^(#[0-9a-fA-F]{6})$/', $input) ? $input : null; |
||||
| 520 | break; |
||||
| 521 | case 'Year': // 2018 etc |
||||
| 522 | if (is_numeric($input) && (int) $input >= 0 && (int) $input <= 3000 && 4 === \strlen((string) $input)) { |
||||
| 523 | $value = (string) $input; |
||||
| 524 | } |
||||
| 525 | break; |
||||
| 526 | case 'Version': |
||||
| 527 | $value = preg_match('/^[\.0-9]+$/', $input) ? $input : null; |
||||
| 528 | break; |
||||
| 529 | case self::PATH: |
||||
| 530 | $value = Validator::path($input) && Validator::path(static::purify($input)) ? $input : null; |
||||
| 531 | break; |
||||
| 532 | case 'Url': |
||||
| 533 | if (!$input) { |
||||
| 534 | return ''; |
||||
| 535 | } |
||||
| 536 | $value = Validator::url($input) ? $input : null; |
||||
| 537 | break; |
||||
| 538 | case 'MailId': |
||||
| 539 | $input = ltrim(rtrim(trim($input), '>'), '<'); |
||||
| 540 | $value = $input === strip_tags($input) ? $input : null; |
||||
| 541 | break; |
||||
| 542 | case 'ClassName': |
||||
| 543 | $value = preg_match('/^[a-z\\\_]+$/i', $input) ? $input : null; |
||||
| 544 | break; |
||||
| 545 | case self::SQL: |
||||
| 546 | $value = $input && Validator::sql($input) ? $input : null; |
||||
| 547 | break; |
||||
| 548 | case self::HTML_TEXT_PARSER: |
||||
| 549 | $value = self::purifyTextParser($input); |
||||
| 550 | break; |
||||
| 551 | case 'Text': |
||||
| 552 | $value = self::purify($input); |
||||
| 553 | break; |
||||
| 554 | default: |
||||
| 555 | if (method_exists('App\Validator', $type)) { |
||||
| 556 | if (Validator::{$type}($input)) { |
||||
| 557 | $value = $input; |
||||
| 558 | } |
||||
| 559 | } else { |
||||
| 560 | $value = self::purify($input); |
||||
| 561 | } |
||||
| 562 | break; |
||||
| 563 | } |
||||
| 564 | if (null === $value) { |
||||
| 565 | \App\Log::error('purifyByType: ' . $input, 'IllegalValue'); |
||||
| 566 | throw new \App\Exceptions\IllegalValue('ERR_NOT_ALLOWED_VALUE||' . $input, 406); |
||||
| 567 | } |
||||
| 568 | } |
||||
| 569 | return $value; |
||||
| 570 | } |
||||
| 571 | |||||
| 572 | /** |
||||
| 573 | * Function to convert the given value to bool. |
||||
| 574 | * |
||||
| 575 | * @param int|string $value |
||||
| 576 | * |
||||
| 577 | * @return bool|null |
||||
| 578 | */ |
||||
| 579 | public static function bool($value) |
||||
| 580 | { |
||||
| 581 | return filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE); |
||||
| 582 | } |
||||
| 583 | |||||
| 584 | /** |
||||
| 585 | * Function to convert the given string to html. |
||||
| 586 | * |
||||
| 587 | * @param string $string |
||||
| 588 | * @param bool $encode |
||||
| 589 | * |
||||
| 590 | * @return string |
||||
| 591 | */ |
||||
| 592 | public static function encodeHtml($string) |
||||
| 593 | { |
||||
| 594 | return $string !== null ? htmlspecialchars($string, ENT_QUOTES, static::$defaultCharset) : ''; |
||||
|
0 ignored issues
–
show
|
|||||
| 595 | } |
||||
| 596 | |||||
| 597 | /** |
||||
| 598 | * Function to decode html. |
||||
| 599 | * |
||||
| 600 | * @param string $string |
||||
| 601 | * |
||||
| 602 | * @return string |
||||
| 603 | */ |
||||
| 604 | public static function decodeHtml($string) |
||||
| 605 | { |
||||
| 606 | return $string !== null ? html_entity_decode($string, ENT_QUOTES, static::$defaultCharset) : ''; |
||||
|
0 ignored issues
–
show
|
|||||
| 607 | } |
||||
| 608 | } |
||||
| 609 | |||||
| 610 | Purifier::$defaultCharset = (string) \App\Config::main('default_charset', 'UTF-8'); |
||||
| 611 |