PageRenderTime 46ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/IDS/Converter.php

https://github.com/csk83/PHPIDS
PHP | 759 lines | 430 code | 94 blank | 235 comment | 37 complexity | 939d6f8254243634757ee232d73f560a MD5 | raw file
Possible License(s): LGPL-3.0
  1. <?php
  2. /**
  3. * PHPIDS
  4. *
  5. * Requirements: PHP5, SimpleXML
  6. *
  7. * Copyright (c) 2008 PHPIDS group (https://phpids.org)
  8. *
  9. * PHPIDS is free software; you can redistribute it and/or modify
  10. * it under the terms of the GNU Lesser General Public License as published by
  11. * the Free Software Foundation, version 3 of the License, or
  12. * (at your option) any later version.
  13. *
  14. * PHPIDS is distributed in the hope that it will be useful,
  15. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. * GNU Lesser General Public License for more details.
  18. *
  19. * You should have received a copy of the GNU Lesser General Public License
  20. * along with PHPIDS. If not, see <http://www.gnu.org/licenses/>.
  21. *
  22. * PHP version 5.1.6+
  23. *
  24. * @category Security
  25. * @package PHPIDS
  26. * @author Mario Heiderich <mario.heiderich@gmail.com>
  27. * @author Christian Matthies <ch0012@gmail.com>
  28. * @author Lars Strojny <lars@strojny.net>
  29. * @license http://www.gnu.org/licenses/lgpl.html LGPL
  30. * @link http://php-ids.org/
  31. */
  32. /**
  33. * PHPIDS specific utility class to convert charsets manually
  34. *
  35. * Note that if you make use of IDS_Converter::runAll(), existing class
  36. * methods will be executed in the same order as they are implemented in the
  37. * class tree!
  38. *
  39. * @category Security
  40. * @package PHPIDS
  41. * @author Christian Matthies <ch0012@gmail.com>
  42. * @author Mario Heiderich <mario.heiderich@gmail.com>
  43. * @author Lars Strojny <lars@strojny.net>
  44. * @copyright 2007-2009 The PHPIDS Group
  45. * @license http://www.gnu.org/licenses/lgpl.html LGPL
  46. * @link http://php-ids.org/
  47. */
  48. namespace IDS;
  49. class Converter
  50. {
  51. /**
  52. * Runs all converter functions
  53. *
  54. * Note that if you make use of IDS_Converter::runAll(), existing class
  55. * methods will be executed in the same order as they are implemented in the
  56. * class tree!
  57. *
  58. * @param string $value the value to convert
  59. *
  60. * @static
  61. * @return string
  62. */
  63. public static function runAll($value)
  64. {
  65. foreach (get_class_methods(__CLASS__) as $method) {
  66. if (strpos($method, 'run') !== 0) {
  67. $value = self::$method($value);
  68. }
  69. }
  70. return $value;
  71. }
  72. /**
  73. * Check for comments and erases them if available
  74. *
  75. * @param string $value the value to convert
  76. *
  77. * @static
  78. * @return string
  79. */
  80. public static function convertFromCommented($value)
  81. {
  82. // check for existing comments
  83. if (preg_match('/(?:\<!-|-->|\/\*|\*\/|\/\/\W*\w+\s*$)|(?:--[^-]*-)/ms', $value)) {
  84. $pattern = array(
  85. '/(?:(?:<!)(?:(?:--(?:[^-]*(?:-[^-]+)*)--\s*)*)(?:>))/ms',
  86. '/(?:(?:\/\*\/*[^\/\*]*)+\*\/)/ms',
  87. '/(?:--[^-]*-)/ms'
  88. );
  89. $converted = preg_replace($pattern, ';', $value);
  90. $value .= "\n" . $converted;
  91. }
  92. //make sure inline comments are detected and converted correctly
  93. $value = preg_replace('/(<\w+)\/+(\w+=?)/m', '$1/$2', $value);
  94. $value = preg_replace('/[^\\\:]\/\/(.*)$/m', '/**/$1', $value);
  95. $value = preg_replace('/([^\-&])#.*[\r\n\v\f]/m', '$1', $value);
  96. $value = preg_replace('/([^&\-])#.*\n/m', '$1 ', $value);
  97. $value = preg_replace('/^#.*\n/m', ' ', $value);
  98. return $value;
  99. }
  100. /**
  101. * Strip newlines
  102. *
  103. * @param string $value the value to convert
  104. *
  105. * @static
  106. * @return string
  107. */
  108. public static function convertFromWhiteSpace($value)
  109. {
  110. //check for inline linebreaks
  111. $search = array('\r', '\n', '\f', '\t', '\v');
  112. $value = str_replace($search, ';', $value);
  113. // replace replacement characters regular spaces
  114. $value = str_replace('�', ' ', $value);
  115. //convert real linebreaks
  116. return preg_replace('/(?:\n|\r|\v)/m', ' ', $value);
  117. }
  118. /**
  119. * Checks for common charcode pattern and decodes them
  120. *
  121. * @param string $value the value to convert
  122. *
  123. * @static
  124. * @return string
  125. */
  126. public static function convertFromJSCharcode($value)
  127. {
  128. $matches = array();
  129. // check if value matches typical charCode pattern
  130. if (preg_match_all('/(?:[\d+-=\/\* ]+(?:\s?,\s?[\d+-=\/\* ]+)){4,}/ms', $value, $matches)) {
  131. $converted = '';
  132. $string = implode(',', $matches[0]);
  133. $string = preg_replace('/\s/', '', $string);
  134. $string = preg_replace('/\w+=/', '', $string);
  135. $charcode = explode(',', $string);
  136. foreach ($charcode as $char) {
  137. $char = preg_replace('/\W0/s', '', $char);
  138. if (preg_match_all('/\d*[+-\/\* ]\d+/', $char, $matches)) {
  139. $match = preg_split('/(\W?\d+)/', implode('', $matches[0]), null, PREG_SPLIT_DELIM_CAPTURE);
  140. if (array_sum($match) >= 20 && array_sum($match) <= 127) {
  141. $converted .= chr(array_sum($match));
  142. }
  143. } elseif (!empty($char) && $char >= 20 && $char <= 127) {
  144. $converted .= chr($char);
  145. }
  146. }
  147. $value .= "\n" . $converted;
  148. }
  149. // check for octal charcode pattern
  150. if (preg_match_all('/(?:(?:[\\\]+\d+[ \t]*){8,})/ims', $value, $matches)) {
  151. $converted = '';
  152. $charcode = explode('\\', preg_replace('/\s/', '', implode(',', $matches[0])));
  153. foreach (array_map('octdec', array_filter($charcode)) as $char) {
  154. if (20 <= $char && $char <= 127) {
  155. $converted .= chr($char);
  156. }
  157. }
  158. $value .= "\n" . $converted;
  159. }
  160. // check for hexadecimal charcode pattern
  161. if (preg_match_all('/(?:(?:[\\\]+\w+\s*){8,})/ims', $value, $matches)) {
  162. $converted = '';
  163. $charcode = explode('\\', preg_replace('/[ux]/', '', implode(',', $matches[0])));
  164. foreach (array_map('hexdec', array_filter($charcode)) as $char) {
  165. if (20 <= $char && $char <= 127) {
  166. $converted .= chr($char);
  167. }
  168. }
  169. $value .= "\n" . $converted;
  170. }
  171. return $value;
  172. }
  173. /**
  174. * Eliminate JS regex modifiers
  175. *
  176. * @param string $value the value to convert
  177. *
  178. * @static
  179. * @return string
  180. */
  181. public static function convertJSRegexModifiers($value)
  182. {
  183. return preg_replace('/\/[gim]+/', '/', $value);
  184. }
  185. /**
  186. * Converts from hex/dec entities
  187. *
  188. * @param string $value the value to convert
  189. *
  190. * @static
  191. * @return string
  192. */
  193. public static function convertEntities($value)
  194. {
  195. $converted = null;
  196. //deal with double encoded payload
  197. $value = preg_replace('/&amp;/', '&', $value);
  198. if (preg_match('/&#x?[\w]+/ms', $value)) {
  199. $converted = preg_replace('/(&#x?[\w]{2}\d?);?/ms', '$1;', $value);
  200. $converted = html_entity_decode($converted, ENT_QUOTES, 'UTF-8');
  201. $value .= "\n" . str_replace(';;', ';', $converted);
  202. }
  203. // normalize obfuscated protocol handlers
  204. $value = preg_replace(
  205. '/(?:j\s*a\s*v\s*a\s*s\s*c\s*r\s*i\s*p\s*t\s*:)|(d\s*a\s*t\s*a\s*:)/ms',
  206. 'javascript:',
  207. $value
  208. );
  209. return $value;
  210. }
  211. /**
  212. * Normalize quotes
  213. *
  214. * @param string $value the value to convert
  215. *
  216. * @static
  217. * @return string
  218. */
  219. public static function convertQuotes($value)
  220. {
  221. // normalize different quotes to "
  222. $pattern = array('\'', '`', '´', '’', '‘');
  223. $value = str_replace($pattern, '"', $value);
  224. //make sure harmless quoted strings don't generate false alerts
  225. $value = preg_replace('/^"([^"=\\!><~]+)"$/', '$1', $value);
  226. return $value;
  227. }
  228. /**
  229. * Converts SQLHEX to plain text
  230. *
  231. * @param string $value the value to convert
  232. *
  233. * @static
  234. * @return string
  235. */
  236. public static function convertFromSQLHex($value)
  237. {
  238. $matches = array();
  239. if (preg_match_all('/(?:(?:\A|[^\d])0x[a-f\d]{3,}[a-f\d]*)+/im', $value, $matches)) {
  240. foreach ($matches[0] as $match) {
  241. $converted = '';
  242. foreach (str_split($match, 2) as $hex_index) {
  243. if (preg_match('/[a-f\d]{2,3}/i', $hex_index)) {
  244. $converted .= chr(hexdec($hex_index));
  245. }
  246. }
  247. $value = str_replace($match, $converted, $value);
  248. }
  249. }
  250. // take care of hex encoded ctrl chars
  251. $value = preg_replace('/0x\d+/m', ' 1 ', $value);
  252. return $value;
  253. }
  254. /**
  255. * Converts basic SQL keywords and obfuscations
  256. *
  257. * @param string $value the value to convert
  258. *
  259. * @static
  260. * @return string
  261. */
  262. public static function convertFromSQLKeywords($value)
  263. {
  264. $pattern = array(
  265. '/(?:is\s+null)|(like\s+null)|' .
  266. '(?:(?:^|\W)in[+\s]*\([\s\d"]+[^()]*\))/ims'
  267. );
  268. $value = preg_replace($pattern, '"=0', $value);
  269. $value = preg_replace('/[^\w\)]+\s*like\s*[^\w\s]+/ims', '1" OR "1"', $value);
  270. $value = preg_replace('/null([,"\s])/ims', '0$1', $value);
  271. $value = preg_replace('/\d+\./ims', ' 1', $value);
  272. $value = preg_replace('/,null/ims', ',0', $value);
  273. $value = preg_replace('/(?:between)/ims', 'or', $value);
  274. $value = preg_replace('/(?:and\s+\d+\.?\d*)/ims', '', $value);
  275. $value = preg_replace('/(?:\s+and\s+)/ims', ' or ', $value);
  276. $pattern = array(
  277. '/(?:not\s+between)|(?:is\s+not)|(?:not\s+in)|' .
  278. '(?:xor|<>|rlike(?:\s+binary)?)|' .
  279. '(?:regexp\s+binary)|' .
  280. '(?:sounds\s+like)/ims'
  281. );
  282. $value = preg_replace($pattern, '!', $value);
  283. $value = preg_replace('/"\s+\d/', '"', $value);
  284. $value = preg_replace('/(\W)div(\W)/ims', '$1 OR $2', $value);
  285. $value = preg_replace('/\/(?:\d+|null)/', null, $value);
  286. return $value;
  287. }
  288. /**
  289. * Detects nullbytes and controls chars via ord()
  290. *
  291. * @param string $value the value to convert
  292. *
  293. * @static
  294. * @return string
  295. */
  296. public static function convertFromControlChars($value)
  297. {
  298. // critical ctrl values
  299. $search = array(
  300. chr(0), chr(1), chr(2), chr(3), chr(4), chr(5),
  301. chr(6), chr(7), chr(8), chr(11), chr(12), chr(14),
  302. chr(15), chr(16), chr(17), chr(18), chr(19), chr(24),
  303. chr(25), chr(192), chr(193), chr(238), chr(255), '\\0'
  304. );
  305. $value = str_replace($search, '%00', $value);
  306. //take care for malicious unicode characters
  307. $value = urldecode(
  308. preg_replace(
  309. '/(?:%E(?:2|3)%8(?:0|1)%(?:A|8|9)\w|%EF%BB%BF|%EF%BF%BD)|(?:&#(?:65|8)\d{3};?)/i',
  310. null,
  311. urlencode($value)
  312. )
  313. );
  314. $value = urlencode($value);
  315. $value = preg_replace('/(?:%F0%80%BE)/i', '>', $value);
  316. $value = preg_replace('/(?:%F0%80%BC)/i', '<', $value);
  317. $value = preg_replace('/(?:%F0%80%A2)/i', '"', $value);
  318. $value = preg_replace('/(?:%F0%80%A7)/i', '\'', $value);
  319. $value = urldecode($value);
  320. $value = preg_replace('/(?:%ff1c)/', '<', $value);
  321. $value = preg_replace('/(?:&[#x]*(200|820|200|820|zwn?j|lrm|rlm)\w?;?)/i', null, $value);
  322. $value = preg_replace(
  323. '/(?:&#(?:65|8)\d{3};?)|' .
  324. '(?:&#(?:56|7)3\d{2};?)|' .
  325. '(?:&#x(?:fe|20)\w{2};?)|' .
  326. '(?:&#x(?:d[c-f])\w{2};?)/i',
  327. null,
  328. $value
  329. );
  330. $value = str_replace(
  331. array(
  332. '«',
  333. '〈',
  334. '<',
  335. '‹',
  336. '〈',
  337. '⟨'
  338. ),
  339. '<',
  340. $value
  341. );
  342. $value = str_replace(
  343. array(
  344. '»',
  345. '〉',
  346. '>',
  347. '›',
  348. '〉',
  349. '⟩'
  350. ),
  351. '>',
  352. $value
  353. );
  354. return $value;
  355. }
  356. /**
  357. * This method matches and translates base64 strings and fragments
  358. * used in data URIs
  359. *
  360. * @param string $value the value to convert
  361. *
  362. * @static
  363. * @return string
  364. */
  365. public static function convertFromNestedBase64($value)
  366. {
  367. $matches = array();
  368. preg_match_all('/(?:^|[,&?])\s*([a-z0-9]{50,}=*)(?:\W|$)/im', $value, $matches);
  369. foreach ($matches[1] as $item) {
  370. if (isset($item) && !preg_match('/[a-f0-9]{32}/i', $item)) {
  371. $base64_item = base64_decode($item);
  372. $value = str_replace($item, $base64_item, $value);
  373. }
  374. }
  375. return $value;
  376. }
  377. /**
  378. * Detects nullbytes and controls chars via ord()
  379. *
  380. * @param string $value the value to convert
  381. *
  382. * @static
  383. * @return string
  384. */
  385. public static function convertFromOutOfRangeChars($value)
  386. {
  387. $values = str_split($value);
  388. foreach ($values as $item) {
  389. if (ord($item) >= 127) {
  390. $value = str_replace($item, ' ', $value);
  391. }
  392. }
  393. return $value;
  394. }
  395. /**
  396. * Strip XML patterns
  397. *
  398. * @param string $value the value to convert
  399. *
  400. * @static
  401. * @return string
  402. */
  403. public static function convertFromXML($value)
  404. {
  405. $converted = strip_tags($value);
  406. if (!$converted || $converted === $value) {
  407. return $value;
  408. } else {
  409. return $value . "\n" . $converted;
  410. }
  411. }
  412. /**
  413. * This method converts JS unicode code points to
  414. * regular characters
  415. *
  416. * @param string $value the value to convert
  417. *
  418. * @static
  419. * @return string
  420. */
  421. public static function convertFromJSUnicode($value)
  422. {
  423. $matches = array();
  424. preg_match_all('/\\\u[0-9a-f]{4}/ims', $value, $matches);
  425. if (!empty($matches[0])) {
  426. foreach ($matches[0] as $match) {
  427. $chr = chr(hexdec(substr($match, 2, 4)));
  428. $value = str_replace($match, $chr, $value);
  429. }
  430. $value .= "\n\u0001";
  431. }
  432. return $value;
  433. }
  434. /**
  435. * Converts relevant UTF-7 tags to UTF-8
  436. *
  437. * @param string $value the value to convert
  438. *
  439. * @static
  440. * @return string
  441. */
  442. public static function convertFromUTF7($value)
  443. {
  444. if (preg_match('/\+A\w+-?/m', $value)) {
  445. if (function_exists('mb_convert_encoding')) {
  446. if (version_compare(PHP_VERSION, '5.2.8', '<')) {
  447. $tmp_chars = str_split($value);
  448. $value = '';
  449. foreach ($tmp_chars as $char) {
  450. if (ord($char) <= 127) {
  451. $value .= $char;
  452. }
  453. }
  454. }
  455. $value .= "\n" . mb_convert_encoding($value, 'UTF-8', 'UTF-7');
  456. } else {
  457. //list of all critical UTF7 codepoints
  458. $schemes = array(
  459. '+ACI-' => '"',
  460. '+ADw-' => '<',
  461. '+AD4-' => '>',
  462. '+AFs-' => '[',
  463. '+AF0-' => ']',
  464. '+AHs-' => '{',
  465. '+AH0-' => '}',
  466. '+AFw-' => '\\',
  467. '+ADs-' => ';',
  468. '+ACM-' => '#',
  469. '+ACY-' => '&',
  470. '+ACU-' => '%',
  471. '+ACQ-' => '$',
  472. '+AD0-' => '=',
  473. '+AGA-' => '`',
  474. '+ALQ-' => '"',
  475. '+IBg-' => '"',
  476. '+IBk-' => '"',
  477. '+AHw-' => '|',
  478. '+ACo-' => '*',
  479. '+AF4-' => '^',
  480. '+ACIAPg-' => '">',
  481. '+ACIAPgA8-' => '">'
  482. );
  483. $value = str_ireplace(
  484. array_keys($schemes),
  485. array_values($schemes),
  486. $value
  487. );
  488. }
  489. }
  490. return $value;
  491. }
  492. /**
  493. * Converts basic concatenations
  494. *
  495. * @param string $value the value to convert
  496. *
  497. * @static
  498. * @return string
  499. */
  500. public static function convertFromConcatenated($value)
  501. {
  502. //normalize remaining backslashes
  503. if ($value != preg_replace('/(\w)\\\/', "$1", $value)) {
  504. $value .= preg_replace('/(\w)\\\/', "$1", $value);
  505. }
  506. $compare = stripslashes($value);
  507. $pattern = array(
  508. '/(?:<\/\w+>\+<\w+>)/s',
  509. '/(?:":\d+[^"[]+")/s',
  510. '/(?:"?"\+\w+\+")/s',
  511. '/(?:"\s*;[^"]+")|(?:";[^"]+:\s*")/s',
  512. '/(?:"\s*(?:;|\+).{8,18}:\s*")/s',
  513. '/(?:";\w+=)|(?:!""&&")|(?:~)/s',
  514. '/(?:"?"\+""?\+?"?)|(?:;\w+=")|(?:"[|&]{2,})/s',
  515. '/(?:"\s*\W+")/s',
  516. '/(?:";\w\s*\+=\s*\w?\s*")/s',
  517. '/(?:"[|&;]+\s*[^|&\n]*[|&]+\s*"?)/s',
  518. '/(?:";\s*\w+\W+\w*\s*[|&]*")/s',
  519. '/(?:"\s*"\s*\.)/s',
  520. '/(?:\s*new\s+\w+\s*[+",])/',
  521. '/(?:(?:^|\s+)(?:do|else)\s+)/',
  522. '/(?:[{(]\s*new\s+\w+\s*[)}])/',
  523. '/(?:(this|self)\.)/',
  524. '/(?:undefined)/',
  525. '/(?:in\s+)/'
  526. );
  527. // strip out concatenations
  528. $converted = preg_replace($pattern, null, $compare);
  529. //strip object traversal
  530. $converted = preg_replace('/\w(\.\w\()/', "$1", $converted);
  531. // normalize obfuscated method calls
  532. $converted = preg_replace('/\)\s*\+/', ")", $converted);
  533. //convert JS special numbers
  534. $converted = preg_replace(
  535. '/(?:\(*[.\d]e[+-]*[^a-z\W]+\)*)|(?:NaN|Infinity)\W/ims',
  536. 1,
  537. $converted
  538. );
  539. if ($converted && ($compare != $converted)) {
  540. $value .= "\n" . $converted;
  541. }
  542. return $value;
  543. }
  544. /**
  545. * This method collects and decodes proprietary encoding types
  546. *
  547. * @param string $value the value to convert
  548. *
  549. * @static
  550. * @return string
  551. */
  552. public static function convertFromProprietaryEncodings($value)
  553. {
  554. //Xajax error reportings
  555. $value = preg_replace('/<!\[CDATA\[(\W+)\]\]>/im', '$1', $value);
  556. //strip false alert triggering apostrophes
  557. $value = preg_replace('/(\w)\"(s)/m', '$1$2', $value);
  558. //strip quotes within typical search patterns
  559. $value = preg_replace('/^"([^"=\\!><~]+)"$/', '$1', $value);
  560. //OpenID login tokens
  561. $value = preg_replace('/{[\w-]{8,9}\}(?:\{[\w=]{8}\}){2}/', null, $value);
  562. //convert Content and \sdo\s to null
  563. $value = preg_replace('/Content|\Wdo\s/', null, $value);
  564. //strip emoticons
  565. $value = preg_replace(
  566. '/(?:\s[:;]-[)\/PD]+)|(?:\s;[)PD]+)|(?:\s:[)PD]+)|-\.-|\^\^/m',
  567. null,
  568. $value
  569. );
  570. //normalize separation char repetion
  571. $value = preg_replace('/([.+~=*_\-;])\1{2,}/m', '$1', $value);
  572. //normalize multiple single quotes
  573. $value = preg_replace('/"{2,}/m', '"', $value);
  574. //normalize quoted numerical values and asterisks
  575. $value = preg_replace('/"(\d+)"/m', '$1', $value);
  576. //normalize pipe separated request parameters
  577. $value = preg_replace('/\|(\w+=\w+)/m', '&$1', $value);
  578. //normalize ampersand listings
  579. $value = preg_replace('/(\w\s)&\s(\w)/', '$1$2', $value);
  580. //normalize escaped RegExp modifiers
  581. $value = preg_replace('/\/\\\(\w)/', '/$1', $value);
  582. return $value;
  583. }
  584. /**
  585. * This method is the centrifuge prototype
  586. *
  587. * @param string $value the value to convert
  588. * @param Monitor $monitor the monitor object
  589. *
  590. * @static
  591. * @return string
  592. */
  593. public static function runCentrifuge($value, Monitor $monitor = null)
  594. {
  595. $threshold = 3.49;
  596. if (strlen($value) > 25) {
  597. //strip padding
  598. $tmp_value = preg_replace('/\s{4}|==$/m', null, $value);
  599. $tmp_value = preg_replace(
  600. '/\s{4}|[\p{L}\d\+\-=,.%()]{8,}/m',
  601. 'aaa',
  602. $tmp_value
  603. );
  604. // Check for the attack char ratio
  605. $tmp_value = preg_replace('/([*.!?+-])\1{1,}/m', '$1', $tmp_value);
  606. $tmp_value = preg_replace('/"[\p{L}\d\s]+"/m', null, $tmp_value);
  607. $stripped_length = strlen(
  608. preg_replace(
  609. '/[\d\s\p{L}\.:,%&\/><\-)!|]+/m',
  610. null,
  611. $tmp_value
  612. )
  613. );
  614. $overall_length = strlen(
  615. preg_replace(
  616. '/([\d\s\p{L}:,\.]{3,})+/m',
  617. 'aaa',
  618. preg_replace('/\s{2,}/m', null, $tmp_value)
  619. )
  620. );
  621. if ($stripped_length != 0 && $overall_length/$stripped_length <= $threshold) {
  622. $monitor->centrifuge['ratio'] = $overall_length / $stripped_length;
  623. $monitor->centrifuge['threshold'] =$threshold;
  624. $value .= "\n$[!!!]";
  625. }
  626. }
  627. if (strlen($value) > 40) {
  628. // Replace all non-special chars
  629. $converted = preg_replace('/[\w\s\p{L},.:!]/', null, $value);
  630. // Split string into an array, unify and sort
  631. $array = str_split($converted);
  632. $array = array_unique($array);
  633. asort($array);
  634. // Normalize certain tokens
  635. $schemes = array(
  636. '~' => '+',
  637. '^' => '+',
  638. '|' => '+',
  639. '*' => '+',
  640. '%' => '+',
  641. '&' => '+',
  642. '/' => '+'
  643. );
  644. $converted = implode($array);
  645. $_keys = array_keys($schemes);
  646. $_values = array_values($schemes);
  647. $converted = str_replace($_keys, $_values, $converted);
  648. $converted = preg_replace('/[+-]\s*\d+/', '+', $converted);
  649. $converted = preg_replace('/[()[\]{}]/', '(', $converted);
  650. $converted = preg_replace('/[!?:=]/', ':', $converted);
  651. $converted = preg_replace('/[^:(+]/', null, stripslashes($converted));
  652. // Sort again and implode
  653. $array = str_split($converted);
  654. asort($array);
  655. $converted = implode($array);
  656. if (preg_match('/(?:\({2,}\+{2,}:{2,})|(?:\({2,}\+{2,}:+)|(?:\({3,}\++:{2,})/', $converted)) {
  657. $monitor->centrifuge['converted'] = $converted;
  658. return $value . "\n" . $converted;
  659. }
  660. }
  661. return $value;
  662. }
  663. }