PHPIDSConverter::convertProprietaryEncodings()   B
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 33
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 33
rs 8.8571
cc 1
eloc 17
nc 1
nop 1
1
<?php
2
3
4
namespace Ionut\Sylar\Normalizers;
5
6
7
use Ionut\Sylar\NormalizedValue;
8
use Ionut\Sylar\NormalizedValueVariant;
9
10
class PHPIDSConverter implements NormalizerInterface
11
{
12
13
    /**
14
     * @var array
15
     */
16
    protected $notConverters = ['normalize', 'replacePairs'];
17
18
    /**
19
     * @param  array $parameters
20
     * @return array
21
     */
22
    public function normalize(array $parameters)
23
    {
24
        $converters = array_diff(get_class_methods(__CLASS__), $this->notConverters);
25
26
        array_walk_recursive($parameters, function (&$value) use ($converters) {
27
            $value = $value instanceof NormalizedValue ? $value : new NormalizedValue($value);
28
29
            $variant = $value->getOriginal();
30
            foreach ($converters as $method) {
31
                $variant = $this->$method($variant);
32
            }
33
34
            $value->variants[static::class] = new NormalizedValueVariant($variant);
35
        });
36
37
        return $parameters;
38
    }
39
40
    protected function replacePairs(array $patternReplacementPair, $value)
41
    {
42
        return preg_replace(
43
            array_keys($patternReplacementPair),
44
            array_values($patternReplacementPair),
45
            $value
46
        );
47
    }
48
49
    /**
50
     * Erases comments.
51
     *
52
     * @param  string $value
53
     * @return string
54
     */
55
    public function convertCommented($value)
56
    {
57
        if (preg_match('/(?:\<!-|-->|\/\*|\*\/|\/\/\W*\w+\s*$)|(?:--[^-]*-)/ms', $value)) {
58
            $pattern = [
59
                '/(?:(?:<!)(?:(?:--(?:[^-]*(?:-[^-]+)*)--\s*)*)(?:>))/ms',
60
                '/(?:(?:\/\*\/*[^\/\*]*)+\*\/)/ms',
61
                '/(?:--[^-]*-)/ms'
62
            ];
63
64
            return $value."\n".preg_replace($pattern, ';', $value);
65
        }
66
67
        return $value;
68
    }
69
70
    /**
71
     * @param  string $value
72
     * @return string
73
     */
74
    public function convertInlineComments($value)
75
    {
76
        return $this->replacePairs([
77
            '/(<\w+)\/+(\w+=?)/m'      => '$1/$2',
78
            '/[^\\\:]\/\/(.*)$/m'      => '/**/$1',
79
            '/([^\-&])#.*[\r\n\v\f]/m' => '$1',
80
            '/([^&\-])#.*\n/m'         => '$1',
81
            '/^#.*\n/m'                => ' '
82
        ], $value);
83
    }
84
85
    /**
86
     * Replaces newlines with semicolons.
87
     *
88
     * @param  string $value
89
     * @return string
90
     */
91
    public function convertWhiteSpace($value)
92
    {
93
        $value = str_replace(['\r', '\n', '\f', '\t', '\v'], ';', $value);
94
        $value = str_replace('�', ' ', $value);
95
        return preg_replace('/(?:\n|\r|\v)/m', '  ', $value);
96
    }
97
98
    /**
99
     * Decodes charcodes.
100
     *
101
     * @param  string $value
102
     * @return string
103
     */
104
    public function convertJSCharcode($value)
105
    {
106
        $matches = [];
107
108
        // Check for typical charcode pattern
109
        if (preg_match_all('/(?:[\d+-=\/\* ]+(?:\s?,\s?[\d+-=\/\* ]+)){4,}/ms', $value, $matches)) {
110
            $converted = '';
111
            $string = implode(',', $matches[0]);
112
            $string = preg_replace('/\s/', '', $string);
113
            $string = preg_replace('/\w+=/', '', $string);
114
            $charcode = explode(',', $string);
115
            foreach ($charcode as $char) {
116
                $char = preg_replace('/\W0/s', '', $char);
117
                if (preg_match_all('/\d*[+-\/\* ]\d+/', $char, $matches)) {
118
                    $match = preg_split('/(\W?\d+)/', implode('', $matches[0]), null, PREG_SPLIT_DELIM_CAPTURE);
119
                    if (array_sum($match) >= 20 && array_sum($match) <= 127) {
120
                        $converted .= chr(array_sum($match));
121
                    }
122
                } elseif (!empty($char) && $char >= 20 && $char <= 127) {
123
                    $converted .= chr($char);
124
                }
125
            }
126
            $value .= "\n".$converted;
127
        }
128
129
        // Check for octal charcode pattern
130
        if (preg_match_all('/(?:(?:[\\\]+\d+[ \t]*){8,})/ims', $value, $matches)) {
131
            $converted = '';
132
            $charcode = explode('\\', preg_replace('/\s/', '', implode(',', $matches[0])));
133
            foreach (array_map('octdec', array_filter($charcode)) as $char) {
134
                if (20 <= $char && $char <= 127) {
135
                    $converted .= chr($char);
136
                }
137
            }
138
            $value .= "\n".$converted;
139
        }
140
141
        // Check for hexadecimal charcode pattern
142
        if (preg_match_all('/(?:(?:[\\\]+\w+\s*){8,})/ims', $value, $matches)) {
143
            $converted = '';
144
            $charcode = explode('\\', preg_replace('/[ux]/', '', implode(',', $matches[0])));
145
            foreach (array_map('hexdec', array_filter($charcode)) as $char) {
146
                if (20 <= $char && $char <= 127) {
147
                    $converted .= chr($char);
148
                }
149
            }
150
            $value .= "\n".$converted;
151
        }
152
153
        return $value;
154
    }
155
156
    /**
157
     * Eliminate JS regex modifiers
158
     *
159
     * @param string $value the value to convert
160
     * @return string
161
     */
162
    public function convertJSRegexModifiers($value)
163
    {
164
        return preg_replace('/\/[gim]+/', '/', $value);
165
    }
166
167
    /**
168
     * Converts from hex/dec entities
169
     *
170
     * @param string $value the value to convert
171
     * @return string
172
     */
173
    public function convertEntities($value)
174
    {
175
        //deal with double encoded payload
176
        $value = preg_replace('/&amp;/', '&', $value);
177
        if (preg_match('/&#x?[\w]+/ms', $value)) {
178
            $converted = preg_replace('/(&#x?[\w]{2}\d?);?/ms', '$1;', $value);
179
            $converted = html_entity_decode($converted, ENT_QUOTES, 'UTF-8');
180
            $value .= "\n".str_replace(';;', ';', $converted);
181
        }
182
        // normalize obfuscated protocol handlers
183
        $value = preg_replace(
184
            '/(?: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',
185
            'javascript:',
186
            $value
187
        );
188
189
        return $value;
190
    }
191
192
    /**
193
     * Normalize quotes
194
     *
195
     * @param string $value the value to convert
196
     * @return string
197
     */
198
    public function convertQuotes($value)
199
    {
200
        // normalize different quotes to "
201
        $pattern = ['\'', '`', '´', '’', '‘'];
202
        $value = str_replace($pattern, '"', $value);
203
        //make sure harmless quoted strings don't generate false alerts
204
        $value = preg_replace('/^"([^"=\\!><~]+)"$/', '$1', $value);
205
206
        return $value;
207
    }
208
209
    /**
210
     * Converts SQLHEX to plain text
211
     *
212
     * @param string $value the value to convert
213
     * @return string
214
     */
215
    public function convertSQLHex($value)
216
    {
217
        $matches = [];
218
        if (preg_match_all('/(?:(?:\A|[^\d])0x[a-f\d]{3,}[a-f\d]*)+/im', $value, $matches)) {
219
            foreach ($matches[0] as $match) {
220
                $converted = '';
221
                foreach (str_split($match, 2) as $hex_index) {
222
                    if (preg_match('/[a-f\d]{2,3}/i', $hex_index)) {
223
                        $converted .= chr(hexdec($hex_index));
224
                    }
225
                }
226
                $value = str_replace($match, $converted, $value);
227
            }
228
        }
229
        // take care of hex encoded ctrl chars
230
        $value = preg_replace('/0x\d+/m', ' 1 ', $value);
231
232
        return $value;
233
    }
234
235
    /**
236
     * Converts basic SQL keywords and obfuscations
237
     *
238
     * @param string $value the value to convert
239
     * @return string
240
     */
241
    public function convertSQLKeywords($value)
242
    {
243
        $pattern = [
244
            '/(?:is\s+null)|(like\s+null)|'.
245
            '(?:(?:^|\W)in[+\s]*\([\s\d"]+[^()]*\))/ims'
246
        ];
247
        $value = preg_replace($pattern, '"=0', $value);
248
        $value = preg_replace('/[^\w\)]+\s*like\s*[^\w\s]+/ims', '1" OR "1"', $value);
249
        $value = preg_replace('/null([,"\s])/ims', '0$1', $value);
250
        $value = preg_replace('/\d+\./ims', ' 1', $value);
251
        $value = preg_replace('/,null/ims', ',0', $value);
252
        $value = preg_replace('/(?:between)/ims', 'or', $value);
253
        $value = preg_replace('/(?:and\s+\d+\.?\d*)/ims', '', $value);
254
        $value = preg_replace('/(?:\s+and\s+)/ims', ' or ', $value);
255
        $pattern = [
256
            '/(?:not\s+between)|(?:is\s+not)|(?:not\s+in)|'.
257
            '(?:xor|<>|rlike(?:\s+binary)?)|'.
258
            '(?:regexp\s+binary)|'.
259
            '(?:sounds\s+like)/ims'
260
        ];
261
        $value = preg_replace($pattern, '!', $value);
262
        $value = preg_replace('/"\s+\d/', '"', $value);
263
        $value = preg_replace('/(\W)div(\W)/ims', '$1 OR $2', $value);
264
        $value = preg_replace('/\/(?:\d+|null)/', null, $value);
265
266
        return $value;
267
    }
268
269
    /**
270
     * Detects nullbytes and controls chars via ord()
271
     *
272
     * @param string $value the value to convert
273
     * @return string
274
     */
275
    public function convertControlChars($value)
276
    {
277
        // critical ctrl values
278
        $search = [
279
            chr(0),
280
            chr(1),
281
            chr(2),
282
            chr(3),
283
            chr(4),
284
            chr(5),
285
            chr(6),
286
            chr(7),
287
            chr(8),
288
            chr(11),
289
            chr(12),
290
            chr(14),
291
            chr(15),
292
            chr(16),
293
            chr(17),
294
            chr(18),
295
            chr(19),
296
            chr(24),
297
            chr(25),
298
            chr(192),
299
            chr(193),
300
            chr(238),
301
            chr(255),
302
            '\\0'
303
        ];
304
        $value = str_replace($search, '%00', $value);
305
        //take care for malicious unicode characters
306
        $value = urldecode(
307
            preg_replace(
308
                '/(?:%E(?:2|3)%8(?:0|1)%(?:A|8|9)\w|%EF%BB%BF|%EF%BF%BD)|(?:&#(?:65|8)\d{3};?)/i',
309
                null,
310
                urlencode($value)
311
            )
312
        );
313
        $value = urlencode($value);
314
        $value = preg_replace('/(?:%F0%80%BE)/i', '>', $value);
315
        $value = preg_replace('/(?:%F0%80%BC)/i', '<', $value);
316
        $value = preg_replace('/(?:%F0%80%A2)/i', '"', $value);
317
        $value = preg_replace('/(?:%F0%80%A7)/i', '\'', $value);
318
        $value = urldecode($value);
319
        $value = preg_replace('/(?:%ff1c)/', '<', $value);
320
        $value = preg_replace('/(?:&[#x]*(200|820|200|820|zwn?j|lrm|rlm)\w?;?)/i', null, $value);
321
        $value = preg_replace(
322
            '/(?:&#(?:65|8)\d{3};?)|'.
323
            '(?:&#(?:56|7)3\d{2};?)|'.
324
            '(?:&#x(?:fe|20)\w{2};?)|'.
325
            '(?:&#x(?:d[c-f])\w{2};?)/i',
326
            null,
327
            $value
328
        );
329
        $value = str_replace(
330
            [
331
                '«',
332
                '〈',
333
                '<',
334
                '‹',
335
                '〈',
336
                '⟨'
337
            ],
338
            '<',
339
            $value
340
        );
341
        $value = str_replace(
342
            [
343
                '»',
344
                '〉',
345
                '>',
346
                '›',
347
                '〉',
348
                '⟩'
349
            ],
350
            '>',
351
            $value
352
        );
353
354
        return $value;
355
    }
356
357
    /**
358
     * This method matches and translates base64 strings and fragments
359
     * used in data URIs
360
     *
361
     * @param string $value the value to convert
362
     * @return string
363
     */
364
    public function convertNestedBase64($value)
365
    {
366
        $matches = [];
367
        preg_match_all('/(?:^|[,&?])\s*([a-z0-9]{50,}=*)(?:\W|$)/im', $value, $matches);
368
        foreach ($matches[1] as $item) {
369
            if (isset($item) && !preg_match('/[a-f0-9]{32}/i', $item)) {
370
                $base64_item = base64_decode($item);
371
                $value = str_replace($item, $base64_item, $value);
372
            }
373
        }
374
375
        return $value;
376
    }
377
378
    /**
379
     * Detects nullbytes and controls chars via ord()
380
     *
381
     * @param string $value the value to convert
382
     * @return string
383
     */
384
    public function convertOutOfRangeChars($value)
385
    {
386
        $values = str_split($value);
387
        foreach ($values as $item) {
388
            if (ord($item) >= 127) {
389
                $value = str_replace($item, ' ', $value);
390
            }
391
        }
392
393
        return $value;
394
    }
395
396
    /**
397
     * Strip XML patterns
398
     *
399
     * @param string $value the value to convert
400
     * @return string
401
     */
402
    public function convertXML($value)
403
    {
404
        $converted = strip_tags($value);
405
        if (!$converted || $converted === $value) {
406
            return $value;
407
        } else {
408
            return $value."\n".$converted;
409
        }
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
     * @return string
418
     */
419
    public function convertJSUnicode($value)
420
    {
421
        $matches = [];
422
        preg_match_all('/\\\u[0-9a-f]{4}/ims', $value, $matches);
423
        if (!empty($matches[0])) {
424
            foreach ($matches[0] as $match) {
425
                $chr = chr(hexdec(substr($match, 2, 4)));
426
                $value = str_replace($match, $chr, $value);
427
            }
428
            $value .= "\n\u0001";
429
        }
430
431
        return $value;
432
    }
433
434
    /**
435
     * Converts relevant UTF-7 tags to UTF-8
436
     *
437
     * @param string $value the value to convert
438
     * @return string
439
     */
440
    public function convertUTF7($value)
441
    {
442
        if (preg_match('/\+A\w+-?/m', $value)) {
443
            if (function_exists('mb_convert_encoding')) {
444
                if (version_compare(PHP_VERSION, '5.2.8', '<')) {
445
                    $tmp_chars = str_split($value);
446
                    $value = '';
447
                    foreach ($tmp_chars as $char) {
448
                        if (ord($char) <= 127) {
449
                            $value .= $char;
450
                        }
451
                    }
452
                }
453
                $value .= "\n".mb_convert_encoding($value, 'UTF-8', 'UTF-7');
454
            } else {
455
                //list of all critical UTF7 codepoints
456
                $schemes = [
457
                    '+ACI-'      => '"',
458
                    '+ADw-'      => '<',
459
                    '+AD4-'      => '>',
460
                    '+AFs-'      => '[',
461
                    '+AF0-'      => ']',
462
                    '+AHs-'      => '{',
463
                    '+AH0-'      => '}',
464
                    '+AFw-'      => '\\',
465
                    '+ADs-'      => ';',
466
                    '+ACM-'      => '#',
467
                    '+ACY-'      => '&',
468
                    '+ACU-'      => '%',
469
                    '+ACQ-'      => '$',
470
                    '+AD0-'      => '=',
471
                    '+AGA-'      => '`',
472
                    '+ALQ-'      => '"',
473
                    '+IBg-'      => '"',
474
                    '+IBk-'      => '"',
475
                    '+AHw-'      => '|',
476
                    '+ACo-'      => '*',
477
                    '+AF4-'      => '^',
478
                    '+ACIAPg-'   => '">',
479
                    '+ACIAPgA8-' => '">'
480
                ];
481
                $value = str_ireplace(
482
                    array_keys($schemes),
483
                    array_values($schemes),
484
                    $value
485
                );
486
            }
487
        }
488
489
        return $value;
490
    }
491
492
    /**
493
     * Converts basic concatenations
494
     *
495
     * @param string $value the value to convert
496
     * @return string
497
     */
498
    public function convertConcatenated($value)
499
    {
500
        //normalize remaining backslashes
501
        if ($value != preg_replace('/(\w)\\\/', "$1", $value)) {
502
            $value .= preg_replace('/(\w)\\\/', "$1", $value);
503
        }
504
505
        $compare = stripslashes($value);
506
        $pattern = [
507
            '/(?:<\/\w+>\+<\w+>)/s',
508
            '/(?:":\d+[^"[]+")/s',
509
            '/(?:"?"\+\w+\+")/s',
510
            '/(?:"\s*;[^"]+")|(?:";[^"]+:\s*")/s',
511
            '/(?:"\s*(?:;|\+).{8,18}:\s*")/s',
512
            '/(?:";\w+=)|(?:!""&&")|(?:~)/s',
513
            '/(?:"?"\+""?\+?"?)|(?:;\w+=")|(?:"[|&]{2,})/s',
514
            '/(?:"\s*\W+")/s',
515
            '/(?:";\w\s*\+=\s*\w?\s*")/s',
516
            '/(?:"[|&;]+\s*[^|&\n]*[|&]+\s*"?)/s',
517
            '/(?:";\s*\w+\W+\w*\s*[|&]*")/s',
518
            '/(?:"\s*"\s*\.)/s',
519
            '/(?:\s*new\s+\w+\s*[+",])/',
520
            '/(?:(?:^|\s+)(?:do|else)\s+)/',
521
            '/(?:[{(]\s*new\s+\w+\s*[)}])/',
522
            '/(?:(this|self)\.)/',
523
            '/(?:undefined)/',
524
            '/(?:in\s+)/'
525
        ];
526
        // strip out concatenations
527
        $converted = preg_replace($pattern, null, $compare);
528
        //strip object traversal
529
        $converted = preg_replace('/\w(\.\w\()/', "$1", $converted);
530
        // normalize obfuscated method calls
531
        $converted = preg_replace('/\)\s*\+/', ")", $converted);
532
        //convert JS special numbers
533
        $converted = preg_replace(
534
            '/(?:\(*[.\d]e[+-]*[^a-z\W]+\)*)|(?:NaN|Infinity)\W/ims',
535
            1,
536
            $converted
537
        );
538
        if ($converted && ($compare != $converted)) {
539
            $value .= "\n".$converted;
540
        }
541
542
        return $value;
543
    }
544
545
    /**
546
     * This method collects and decodes proprietary encoding types
547
     *
548
     * @param string $value the value to convert
549
     * @return string
550
     */
551
    public function convertProprietaryEncodings($value)
552
    {
553
        //Xajax error reportings
554
        $value = preg_replace('/<!\[CDATA\[(\W+)\]\]>/im', '$1', $value);
555
        //strip false alert triggering apostrophes
556
        $value = preg_replace('/(\w)\"(s)/m', '$1$2', $value);
557
        //strip quotes within typical search patterns
558
        $value = preg_replace('/^"([^"=\\!><~]+)"$/', '$1', $value);
559
        //OpenID login tokens
560
        $value = preg_replace('/{[\w-]{8,9}\}(?:\{[\w=]{8}\}){2}/', null, $value);
561
        //convert Content and \sdo\s to null
562
        $value = preg_replace('/Content|\Wdo\s/', null, $value);
563
        //strip emoticons
564
        $value = preg_replace(
565
            '/(?:\s[:;]-[)\/PD]+)|(?:\s;[)PD]+)|(?:\s:[)PD]+)|-\.-|\^\^/m',
566
            null,
567
            $value
568
        );
569
        //normalize separation char repetion
570
        $value = preg_replace('/([.+~=*_\-;])\1{2,}/m', '$1', $value);
571
        //normalize multiple single quotes
572
        $value = preg_replace('/"{2,}/m', '"', $value);
573
        //normalize quoted numerical values and asterisks
574
        $value = preg_replace('/"(\d+)"/m', '$1', $value);
575
        //normalize pipe separated request parameters
576
        $value = preg_replace('/\|(\w+=\w+)/m', '&$1', $value);
577
        //normalize ampersand listings
578
        $value = preg_replace('/(\w\s)&\s(\w)/', '$1$2', $value);
579
        //normalize escaped RegExp modifiers
580
        $value = preg_replace('/\/\\\(\w)/', '/$1', $value);
581
582
        return $value;
583
    }
584
585
    /**
586
     * This method removes encoded sql # comments
587
     *
588
     * @param string $value the value to convert
589
     * @return string
590
     */
591
    public function convertUrlencodeSqlComment($value)
592
    {
593
        if (preg_match_all('/(?:\%23.*?\%0a)/im', $value, $matches)) {
594
            $converted = $value;
595
            foreach ($matches[0] as $match) {
596
                $converted = str_replace($match, ' ', $converted);
597
            }
598
            $value .= "\n".$converted;
599
        }
600
601
        return $value;
602
    }
603
604
    /**
605
     * This method is the centrifuge prototype
606
     *
607
     * @param  string $value
608
     * @return string
609
     */
610
    public function runCentrifuge($value)
611
    {
612
        $threshold = 3.49;
613
        if (strlen($value) > 25) {
614
            //strip padding
615
            $tmp_value = preg_replace('/\s{4}|==$/m', null, $value);
616
            $tmp_value = preg_replace(
617
                '/\s{4}|[\p{L}\d\+\-=,.%()]{8,}/m',
618
                'aaa',
619
                $tmp_value
620
            );
621
            // Check for the attack char ratio
622
            $tmp_value = preg_replace('/([*.!?+-])\1{1,}/m', '$1', $tmp_value);
623
            $tmp_value = preg_replace('/"[\p{L}\d\s]+"/m', null, $tmp_value);
624
            $stripped_length = strlen(
625
                preg_replace(
626
                    '/[\d\s\p{L}\.:,%&\/><\-)!|]+/m',
627
                    null,
628
                    $tmp_value
629
                )
630
            );
631
            $overall_length = strlen(
632
                preg_replace(
633
                    '/([\d\s\p{L}:,\.]{3,})+/m',
634
                    'aaa',
635
                    preg_replace('/\s{2,}/m', null, $tmp_value)
636
                )
637
            );
638
            if ($stripped_length != 0 && $overall_length / $stripped_length <= $threshold) {
639
                $stats = [
0 ignored issues
show
Unused Code introduced by
$stats is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
640
                    'ratio'     => $overall_length / $stripped_length,
641
                    'threshold' => $threshold
642
                ];
643
644
                $value .= "\n$[!!!]";
645
            }
646
        }
647
        if (strlen($value) > 40) {
648
            // Replace all non-special chars
649
            $converted = preg_replace('/[\w\s\p{L},.:!]/', null, $value);
650
            // Split string into an array, unify and sort
651
            $array = str_split($converted);
652
            $array = array_unique($array);
653
            asort($array);
654
            // Normalize certain tokens
655
            $schemes = [
656
                '~' => '+',
657
                '^' => '+',
658
                '|' => '+',
659
                '*' => '+',
660
                '%' => '+',
661
                '&' => '+',
662
                '/' => '+'
663
            ];
664
            $converted = implode($array);
665
            $_keys = array_keys($schemes);
666
            $_values = array_values($schemes);
667
            $converted = str_replace($_keys, $_values, $converted);
668
            $converted = preg_replace('/[+-]\s*\d+/', '+', $converted);
669
            $converted = preg_replace('/[()[\]{}]/', '(', $converted);
670
            $converted = preg_replace('/[!?:=]/', ':', $converted);
671
            $converted = preg_replace('/[^:(+]/', null, stripslashes($converted));
672
            // Sort again and implode
673
            $array = str_split($converted);
674
            asort($array);
675
            $converted = implode($array);
676
            if (preg_match('/(?:\({2,}\+{2,}:{2,})|(?:\({2,}\+{2,}:+)|(?:\({3,}\++:{2,})/', $converted)) {
677
                return $value."\n".$converted;
678
            }
679
        }
680
681
        return $value;
682
    }
683
}