1<?php
2
3#################################################################
4#  Copyright notice
5#
6#  (c) 2013 Jérôme Schneider <mail@jeromeschneider.fr>
7#  All rights reserved
8#
9#  http://flake.codr.fr
10#
11#  This script is part of the Flake project. The Flake
12#  project is free software; you can redistribute it
13#  and/or modify it under the terms of the GNU General Public
14#  License as published by the Free Software Foundation; either
15#  version 2 of the License, or (at your option) any later version.
16#
17#  The GNU General Public License can be found at
18#  http://www.gnu.org/copyleft/gpl.html.
19#
20#  This script is distributed in the hope that it will be useful,
21#  but WITHOUT ANY WARRANTY; without even the implied warranty of
22#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23#  GNU General Public License for more details.
24#
25#  This copyright notice MUST APPEAR in all copies of the script!
26#################################################################
27
28namespace Flake\Util;
29
30class Tools extends \Flake\Core\FLObject {
31    private function __construct() {    # private constructor to force static class
32    }
33
34    static function getCurrentUrl() {
35        if (MONGOOSE_SERVER) {
36            $sUrl = $GLOBALS["_SERVER"]["REQUEST_URI"];
37            if (array_key_exists("QUERY_STRING", $GLOBALS["_SERVER"]) && trim($GLOBALS["_SERVER"]["QUERY_STRING"]) !== "") {
38                $sUrl .= "?" . $GLOBALS["_SERVER"]["QUERY_STRING"];
39            }
40        } else {
41            $sUrl = $GLOBALS["_SERVER"]["REQUEST_URI"];    # Would be REDIRECT_URL for ServerRewrite
42        }
43
44        return $sUrl;
45    }
46
47    static function getCurrentProtocol() {
48        if (isset($GLOBALS['_SERVER']['HTTP_X_FORWARDED_PROTO']) && !empty($GLOBALS['_SERVER']['HTTP_X_FORWARDED_PROTO'])) {
49            return $GLOBALS['_SERVER']['HTTP_X_FORWARDED_PROTO'];
50        }
51
52        if ((!empty($GLOBALS["_SERVER"]["HTTPS"]) && $GLOBALS["_SERVER"]['HTTPS'] !== 'off') || intval($_SERVER['SERVER_PORT']) === 443) {
53            return "https";
54        }
55
56        return "http";
57    }
58
59    static function deCamelCase($sString, $sGlue = " ") {
60        $sSep = md5(rand());
61        $sRes = preg_replace('/(?!^)[[:upper:]][[:lower:]]/', '$0', preg_replace('/(?!^)[[:upper:]]+/', $sSep . '$0', $sString));
62        if ($sGlue !== "" && preg_match('/^[[:upper:]].*/', $sRes)) {
63            $sRes = $sSep . $sRes;
64        }
65
66        return str_replace($sSep, $sGlue, $sRes);
67    }
68
69    static function serverToRelativeWebPath($sAbsPath) {
70        return "/" . str_replace(PROJECT_PATH_WWWROOT, "", $sAbsPath);
71    }
72
73    static function view_array($array_in) {
74        if (is_array($array_in)) {
75            $result = '<table border="1" cellpadding="1" cellspacing="0" bgcolor="white">';
76            if (!count($array_in)) {
77                $result .= '<tr><td><font face="Verdana,Arial" size="1"><b>' . htmlspecialchars("EMPTY!") . '</b></font></td></tr>';
78            }
79            foreach ($array_in as $key => $val) {
80                $result .= '<tr><td valign="top"><font face="Verdana,Arial" size="1">' . htmlspecialchars((string) $key) . '</font></td><td>';
81                if (is_array($array_in[$key])) {
82                    $result .= \Flake\Util\Tools::view_array($array_in[$key]);
83                } else {
84                    if (is_object($val)) {
85                        if (method_exists($val, "__toString")) {
86                            $sWhat = nl2br(htmlspecialchars((string) $val));
87                        } else {
88                            $sWhat = nl2br(htmlspecialchars(get_class($val)));
89                        }
90                    } elseif (is_bool($val)) {
91                        $sWhat = ($val === true ? "boolean:TRUE" : "boolean:FALSE");
92                    } else {
93                        $sWhat = nl2br(htmlspecialchars((string) $val));
94                    }
95
96                    $result .= '<font face="Verdana,Arial" size="1" color="red">' . $sWhat . '<br /></font>';
97                }
98
99                $result .= '</td></tr>';
100            }
101            $result .= '</table>';
102        } else {
103            $result = '<table border="1" cellpadding="1" cellspacing="0" bgcolor="white">
104				<tr>
105					<td><font face="Verdana,Arial" size="1" color="red">' . nl2br(htmlspecialchars((string) $array_in)) . '<br /></font></td>
106				</tr>
107			</table>';    // Output it as a string.
108        }
109
110        return $result;
111    }
112
113    static function debug($var = "", $brOrHeader = 0) {
114        if ($brOrHeader === 0) {
115            try {
116                $trail = debug_backtrace();
117                $trail = array_reverse($trail);
118                array_pop($trail);    // la ligne d'appel à debug
119                array_pop($trail);    // la ligne d'appel à debug
120                $aLastNode = array_pop($trail);    // l'appel qui nous intéresse
121
122                if (array_key_exists("class", $aLastNode)) {
123                    $sClass = @strval($aLastNode["class"]);
124                } else {
125                    $sClass = "";
126                }
127
128                if (array_key_exists("type", $aLastNode)) {
129                    $sType = @strval($aLastNode["type"]);
130                } else {
131                    $sType = "";
132                }
133
134                $brOrHeader = $sClass . $sType . @strval($aLastNode['function']);
135            } catch (\Exception $e) {
136                $brOrHeader = "Undetermined context";
137            }
138        }
139
140        if ($brOrHeader) {
141            echo '<table border="0" cellpadding="0" cellspacing="0" bgcolor="white" style="border:0px; margin-top:3px; margin-bottom:3px;"><tr><td style="background-color:#bbbbbb; font-family: verdana,arial; font-weight: bold; font-size: 10px;">' . htmlspecialchars((string) $brOrHeader) . '</td></tr><tr><td>';
142        }
143
144        if (is_array($var)) {
145            echo \Flake\Util\Tools::view_array($var);
146        } elseif (is_object($var)) {
147            echo '<b>|Object:<pre>';
148            print_r($var);
149            echo '</pre>|</b>';
150        } elseif ((string) $var != '') {
151            echo '<b>|' . htmlspecialchars((string) $var) . '|</b>';
152        } else {
153            echo '<b>| debug |</b>';
154        }
155
156        if ($brOrHeader) {
157            echo '</td></tr></table>';
158        }
159    }
160
161    static function debug_trail() {
162        $trail = debug_backtrace();
163        $trail = array_reverse($trail);
164        array_pop($trail);
165
166        $path = [];
167        foreach ($trail as $dat) {
168            $path[] = $dat['class'] . $dat['type'] . $dat['function'];
169        }
170
171        return implode(' // ', $path);
172    }
173
174    static function POST($sVar = false) {
175        if ($sVar !== false) {
176            $aData = \Flake\Util\Tools::POST();
177            if (array_key_exists($sVar, $aData)) {
178                return $aData[$sVar];
179            }
180
181            return "";
182        }
183
184        return is_array($GLOBALS["_POST"]) ? $GLOBALS["_POST"] : [];
185    }
186
187    static function GET($sVar = false) {
188        if ($sVar !== false) {
189            $aData = \Flake\Util\Tools::GET();
190            if (array_key_exists($sVar, $aData)) {
191                return $aData[$sVar];
192            }
193
194            return "";
195        }
196
197        return is_array($GLOBALS["_GET"]) ? $GLOBALS["_GET"] : [];
198    }
199
200    static function GP($sVar = false) {
201        if ($sVar !== false) {
202            $aData = \Flake\Util\Tools::GP();
203            if (array_key_exists($sVar, $aData)) {
204                return $aData[$sVar];
205            }
206
207            return "";
208        }
209
210        return array_merge(
211            \Flake\Util\Tools::GET(),
212            \Flake\Util\Tools::POST()
213        );
214    }
215
216    static function safelock($sString) {
217        return substr(md5(PROJECT_SAFEHASH_SALT . ":" . $sString), 0, 5);
218    }
219
220    static function redirect($sUrl) {
221        header("Location: " . $sUrl);
222        exit(0);
223    }
224
225    static function redirectUsingMeta($sUrl) {
226        $sDoc = "<html><head><meta http-equiv='refresh' content='0; url=" . $sUrl . "'></meta></head><body></body></html>";
227        echo $sDoc;
228        exit(0);
229    }
230
231    static function refreshPage() {
232        header("Location: " . \Flake\Util\Tools::getCurrentUrl());
233        exit(0);
234    }
235
236    static function validEmail($sEmail) {
237        return (filter_var($sEmail, FILTER_VALIDATE_EMAIL) !== false);
238    }
239
240    static function filterFormInput($sInput) {
241        return strip_tags($sInput);
242    }
243
244    static function getHumanDate($iStamp) {
245        return ucwords(strftime("%A, %d %B %Y", $iStamp));
246    }
247
248    static function getHumanTime($iStamp) {
249        return strftime("%Hh%M", $iStamp);
250    }
251
252    static function trimExplode($string, $delim = ",", $removeEmptyValues = false, $limit = 0) {
253        $explodedValues = explode($delim, $string);
254
255        $result = array_map('trim', $explodedValues);
256
257        if ($removeEmptyValues) {
258            $temp = [];
259            foreach ($result as $value) {
260                if ($value !== '') {
261                    $temp[] = $value;
262                }
263            }
264            $result = $temp;
265        }
266
267        if ($limit != 0) {
268            if ($limit < 0) {
269                $result = array_slice($result, 0, $limit);
270            } elseif (count($result) > $limit) {
271                $lastElements = array_slice($result, $limit - 1);
272                $result = array_slice($result, 0, $limit - 1);
273                $result[] = implode($delim, $lastElements);
274            }
275        }
276
277        return $result;
278    }
279
280    /**
281     * Taken from TYPO3
282     * Returns true if the first part of $str matches the string $partStr.
283     *
284     * @param	string		Full string to check
285     * @param	string		Reference string which must be found as the "first part" of the full string
286     *
287     * @return	bool		True if $partStr was found to be equal to the first part of $str
288     */
289    static function isFirstPartOfStr($str, $partStr) {
290        // Returns true, if the first part of a $str equals $partStr and $partStr is not ''
291        $psLen = strlen($partStr);
292        if ($psLen) {
293            return substr($str, 0, $psLen) == (string) $partStr;
294        } else {
295            return false;
296        }
297    }
298
299    /**
300     * Binary-reads a file.
301     *
302     * @param	string		$sPath: absolute server path to file
303     *
304     * @return	string		file contents
305     */
306    static function file_readBin($sPath) {
307        $sData = "";
308        $rFile = fopen($sPath, "rb");
309        while (!feof($rFile)) {
310            $sData .= fread($rFile, 1024);
311        }
312        fclose($rFile);
313
314        return $sData;
315    }
316
317    /**
318     * Binary-writes a file.
319     *
320     * @param	string		$sPath: absolute server path to file
321     * @param	string		$sData: file contents
322     * @param	bool		$bUTF8: add UTF8-BOM or not ?
323     *
324     * @return	void
325     */
326    static function file_writeBin($sPath, $sData) {
327        $rFile = fopen($sPath, "wb");
328        fputs($rFile, $sData);
329        fclose($rFile);
330    }
331
332    static function sendHtmlMail($sToAddress, $sSubject, $sBody, $sFromName, $sFromAddress, $sReplyToName, $sReplyToAddress) {
333        $sMessage = <<<TEST
334<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
335<html>
336	<head>
337		<title>Email</title>
338	</head>
339	<body>
340	{$sBody}
341	</body>
342</html>
343TEST;
344
345        $sHeaders = "From: " . $sFromName . "<" . $sFromAddress . ">" . "\r\n";
346        $sHeaders .= "Reply-To: " . $sReplyToName . "<" . $sReplyToAddress . ">" . "\r\n";
347        $sHeaders .= "Bcc: " . $sReplyToName . "<" . $sReplyToAddress . ">" . "\r\n";
348        $sHeaders .= "Content-Type: text/html" . "\r\n";
349
350        mail($sToAddress, $sSubject, $sMessage, $sHeaders);
351    }
352
353    static function shortMD5($sValue) {
354        return strtolower(substr(md5($sValue), 0, 5));
355    }
356
357    static function overrideFirstWithSecond($sFirst, $sSecond) {
358        if (trim($sSecond) !== "") {
359            return $sSecond;
360        }
361
362        return "" . $sFirst;
363    }
364
365    static function parseTemplateCode($sCode, $aMarkers) {
366        $tplName = md5($sCode);
367        $loader = new \Twig\Loader\ArrayLoader([$tplName => $sCode]);
368        $env = new \Twig\Environment($loader);
369        $env->setCache(false);
370
371        return $env->render($tplName, $aMarkers);
372    }
373
374    static function is_a($object, $class) {
375        if (is_object($object)) {
376            return $object instanceof $class;
377        }
378        if (is_string($object)) {
379            if (is_object($class)) {
380                $class = get_class($class);
381            }
382
383            if (class_exists($class, true)) {    # TRUE to autoload class
384                return @is_subclass_of($object, $class) || $object == $class;
385            }
386
387            if (interface_exists($class)) {
388                $reflect = new \ReflectionClass($object);
389
390                return $reflect->implementsInterface($class);
391            }
392        }
393
394        return false;
395    }
396
397    static function HTTPStatus($iCode, $sMessage) {
398        header("HTTP/1.1 404 Not Found");
399        header("Status: 404 Not Found");
400        exit("<h1>HTTP Status " . $iCode . " : " . $sMessage . "</h1>");
401    }
402
403    static function number2Rank($a) {
404        $a = intval($a);
405
406        if ($a === 1) {
407            return "premier";
408        } elseif ($a === 2) {
409            return "second";
410        }
411
412        $sNumber = self::number2Human($a);
413
414        $sLastLetter = substr($sNumber, -1, 1);
415        if ($sLastLetter === "e") {
416            $sNumber = substr($sNumber, 0, -1);
417        } elseif ($sLastLetter === "q") {
418            $sNumber = $sNumber . "u";
419        } elseif ($sLastLetter === "f") {
420            $sNumber = substr($sNumber, 0, -1) . "v";
421        }
422
423        return $sNumber . "ième";
424    }
425
426    static function number2Human($a) {
427        $temp = explode('.', $a);
428        if (isset($temp[1]) && $temp[1] != '') {
429            return self::number2Human($temp[0]) . ' virgule ' . self::number2Human($temp[1]);
430        }
431
432        if ($a < 0) {
433            return 'moins ' . self::number2Human(-$a);
434        }
435
436        if ($a < 17) {
437            switch ($a) {
438                case 0: return 'zero';
439                case 1: return 'un';
440                case 2: return 'deux';
441                case 3: return 'trois';
442                case 4: return 'quatre';
443                case 5: return 'cinq';
444                case 6: return 'six';
445                case 7: return 'sept';
446                case 8: return 'huit';
447                case 9: return 'neuf';
448                case 10: return 'dix';
449                case 11: return 'onze';
450                case 12: return 'douze';
451                case 13: return 'treize';
452                case 14: return 'quatorze';
453                case 15: return 'quinze';
454                case 16: return 'seize';
455            }
456        } elseif ($a < 20) {
457            return 'dix-' . self::number2Human($a - 10);
458        } elseif ($a < 100) {
459            if ($a % 10 == 0) {
460                switch ($a) {
461                    case 20: return 'vingt';
462                    case 30: return 'trente';
463                    case 40: return 'quarante';
464                    case 50: return 'cinquante';
465                    case 60: return 'soixante';
466                    case 70: return 'soixante-dix';
467                    case 80: return 'quatre-vingt';
468                    case 90: return 'quatre-vingt-dix';
469                }
470            } elseif (substr($a, -1) == 1) {
471                if (((int) ($a / 10) * 10) < 70) {
472                    return self::number2Human((int) ($a / 10) * 10) . '-et-un';
473                } elseif ($a == 71) {
474                    return 'soixante-et-onze';
475                } elseif ($a == 81) {
476                    return 'quatre-vingt-un';
477                } elseif ($a == 91) {
478                    return 'quatre-vingt-onze';
479                }
480            } elseif ($a < 70) {
481                return self::number2Human($a - $a % 10) . '-' . self::number2Human($a % 10);
482            } elseif ($a < 80) {
483                return self::number2Human(60) . '-' . self::number2Human($a % 20);
484            } else {
485                return self::number2Human(80) . '-' . self::number2Human($a % 20);
486            }
487        } elseif ($a == 100) {
488            return 'cent';
489        } elseif ($a < 200) {
490            return self::number2Human(100) . ' ' . self::number2Human($a % 100);
491        } elseif ($a < 1000) {
492            return self::number2Human((int) ($a / 100)) . ' ' . self::number2Human(100) . ' ' . self::number2Human($a % 100);
493        } elseif ($a == 1000) {
494            return 'mille';
495        } elseif ($a < 2000) {
496            return self::number2Human(1000) . ' ' . self::number2Human($a % 1000) . ' ';
497        } elseif ($a < 1000000) {
498            return self::number2Human((int) ($a / 1000)) . ' ' . self::number2Human(1000) . ' ' . self::number2Human($a % 1000);
499        }
500    }
501
502    static function stringToUrlToken($sString) {
503        # Taken from TYPO3 extension realurl
504
505        $space = "-";
506        $sString = strtr($sString, ' -+_\'', $space . $space . $space . $space . $space); // convert spaces
507
508        # De-activated; @see https://github.com/netgusto/Baikal/issues/244
509        #if(function_exists("iconv")) {
510        #	$sString = iconv('UTF-8', 'ASCII//TRANSLIT', $sString);
511        #}
512
513        $sString = strtolower($sString);
514
515        $sString = preg_replace('/[^a-zA-Z0-9\\' . $space . ']/', '', $sString);
516        $sString = preg_replace('/\\' . $space . '{2,}/', $space, $sString); // Convert multiple 'spaces' to a single one
517        $sString = trim($sString, $space);
518
519        return $sString;
520    }
521
522    static function isCliPhp() {
523        return strtolower(php_sapi_name()) === "cli";
524    }
525
526    static function getIP() {
527        $alt_ip = $_SERVER['REMOTE_ADDR'];
528
529        if (isset($_SERVER['HTTP_CLIENT_IP'])) {
530            $alt_ip = $_SERVER['HTTP_CLIENT_IP'];
531        } elseif (isset($_SERVER['HTTP_X_FORWARDED_FOR']) and preg_match_all('#\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}#s', $_SERVER['HTTP_X_FORWARDED_FOR'], $matches)) {
532            // make sure we dont pick up an internal IP defined by RFC1918
533            foreach ($matches[0] as $ip) {
534                if (!preg_match('#^(10|172\.16|192\.168)\.#', $ip)) {
535                    $alt_ip = $ip;
536                    break;
537                }
538            }
539        } elseif (isset($_SERVER['HTTP_FROM'])) {
540            $alt_ip = $_SERVER['HTTP_FROM'];
541        }
542
543        return $alt_ip;
544    }
545
546    static function getUserAgent() {
547        return $_SERVER['HTTP_USER_AGENT'];
548    }
549
550    ###########
551    static function appendSlash($sString) {
552        return self::appendString($sString, "/");
553    }
554
555    static function prependSlash($sString) {
556        return self::prependString($sString, "/");
557    }
558
559    static function stripBeginSlash($sString) {
560        return self::stripBeginString($sString, "/");
561    }
562
563    static function stripEndSlash($sString) {
564        return self::stripEndString($sString, "/");
565    }
566
567    static function trimSlashes($sString) {
568        return self::stripBeginSlash(self::stripEndSlash($sString));
569    }
570
571    ###########
572    static function appendString($sString, $sAppend) {
573        if (substr($sString, -1 * strlen($sAppend)) !== $sAppend) {
574            $sString .= $sAppend;
575        }
576
577        return $sString;
578    }
579
580    static function prependString($sString, $sAppend) {
581        if (substr($sString, 0, 1 * strlen($sAppend)) !== $sAppend) {
582            $sString = $sAppend . $sString;
583        }
584
585        return $sString;
586    }
587
588    static function stripBeginString($sString, $sAppend) {
589        if (substr($sString, 0, 1 * strlen($sAppend)) === $sAppend) {
590            $sString = substr($sString, strlen($sAppend));
591        }
592
593        return $sString;
594    }
595
596    static function stripEndString($sString, $sAppend) {
597        if (substr($sString, -1 * strlen($sAppend)) === $sAppend) {
598            $sString = substr($sString, 0, -1 * strlen($sAppend));
599        }
600
601        return $sString;
602    }
603
604    static function trimStrings($sString, $sAppend) {
605        return self::stripBeginString(self::stripEndString($sString, $sAppend), $sAppend);
606    }
607
608    static function stringEndsWith($sHaystack, $sNeedle) {
609        return substr($sHaystack, strlen($sNeedle) * -1) === $sNeedle;
610    }
611
612    ###########
613
614    static function router() {
615        return "\Flake\Util\Router\QuestionMarkRewrite";
616    }
617
618    static function arrayIsAssoc($aArray) {
619        if (!is_array($aArray)) {
620            throw new \Exception("\Flake\Util\Tools::arrayIsAssoc(): parameter has to be an array.");
621        }
622
623        # Taken from http://stackoverflow.com/questions/173400/php-arrays-a-good-way-to-check-if-an-array-is-associative-or-sequential#answer-4254008
624        # count() will return 0 if numeric, and > 0 if assoc, even partially
625        return (bool) count(array_filter(array_keys($aArray), 'is_string'));
626    }
627
628    static function arrayIsSeq($aArray) {
629        return !self::arrayIsAssoc($aArray);
630    }
631
632    static function echoAndCutClient($sMessage = '') {
633        ignore_user_abort(true);
634        #		set_time_limit(0);
635
636        header("Connection: close");
637        header("Content-Length: " . strlen($sMessage));
638        echo $sMessage;
639        echo str_repeat("\r\n", 10); // just to be sure
640        flush();
641    }
642
643    static function milliseconds() {
644        return intval((microtime(true) * 1000));
645    }
646
647    static function stopWatch($sWhat) {
648        #		return;
649        $iStop = \Flake\Util\Tools::milliseconds();
650
651        $trail = debug_backtrace();
652        $aLastNode = $trail[0];    // l'appel qui nous intéresse
653        $sFile = basename($aLastNode["file"]);
654        $iLine = intval($aLastNode["line"]);
655
656        if (!array_key_exists("FLAKE_STOPWATCHES", $GLOBALS)) {
657            $GLOBALS["FLAKE_STOPWATCHES"] = [];
658        }
659
660        if (!array_key_exists($sWhat, $GLOBALS["FLAKE_STOPWATCHES"])) {
661            $GLOBALS["FLAKE_STOPWATCHES"][$sWhat] = $iStop;
662        } else {
663            $iTime = $iStop - $GLOBALS["FLAKE_STOPWATCHES"][$sWhat];
664            echo "<h3 style='color: silver'><span style='display: inline-block; width: 400px;'>@" . $sFile . "+" . $iLine . ":</span>" . $sWhat . ":" . $iTime . " ms</h1>";
665            flush();
666        }
667    }
668
669    # Taken from http://www.php.net/manual/en/function.gzdecode.php#82930
670    static function gzdecode($data, &$filename = '', &$error = '', $maxlength = null) {
671        $len = strlen($data);
672        if ($len < 18 || strcmp(substr($data, 0, 2), "\x1f\x8b")) {
673            $error = "Not in GZIP format.";
674
675            return null;  // Not GZIP format (See RFC 1952)
676        }
677        $method = ord(substr($data, 2, 1));  // Compression method
678        $flags = ord(substr($data, 3, 1));  // Flags
679        if ($flags & 31 != $flags) {
680            $error = "Reserved bits not allowed.";
681
682            return null;
683        }
684        // NOTE: $mtime may be negative (PHP integer limitations)
685        $mtime = unpack("V", substr($data, 4, 4));
686        $mtime = $mtime[1];
687        $xfl = substr($data, 8, 1);
688        $os = substr($data, 8, 1);
689        $headerlen = 10;
690        $extralen = 0;
691        $extra = "";
692        if ($flags & 4) {
693            // 2-byte length prefixed EXTRA data in header
694            if ($len - $headerlen - 2 < 8) {
695                return false;  // invalid
696            }
697            $extralen = unpack("v", substr($data, 8, 2));
698            $extralen = $extralen[1];
699            if ($len - $headerlen - 2 - $extralen < 8) {
700                return false;  // invalid
701            }
702            $extra = substr($data, 10, $extralen);
703            $headerlen += 2 + $extralen;
704        }
705        $filenamelen = 0;
706        $filename = "";
707        if ($flags & 8) {
708            // C-style string
709            if ($len - $headerlen - 1 < 8) {
710                return false; // invalid
711            }
712            $filenamelen = strpos(substr($data, $headerlen), chr(0));
713            if ($filenamelen === false || $len - $headerlen - $filenamelen - 1 < 8) {
714                return false; // invalid
715            }
716            $filename = substr($data, $headerlen, $filenamelen);
717            $headerlen += $filenamelen + 1;
718        }
719        $commentlen = 0;
720        $comment = "";
721        if ($flags & 16) {
722            // C-style string COMMENT data in header
723            if ($len - $headerlen - 1 < 8) {
724                return false;    // invalid
725            }
726            $commentlen = strpos(substr($data, $headerlen), chr(0));
727            if ($commentlen === false || $len - $headerlen - $commentlen - 1 < 8) {
728                return false;    // Invalid header format
729            }
730            $comment = substr($data, $headerlen, $commentlen);
731            $headerlen += $commentlen + 1;
732        }
733        $headercrc = "";
734        if ($flags & 2) {
735            // 2-bytes (lowest order) of CRC32 on header present
736            if ($len - $headerlen - 2 < 8) {
737                return false;    // invalid
738            }
739            $calccrc = crc32(substr($data, 0, $headerlen)) & 0xffff;
740            $headercrc = unpack("v", substr($data, $headerlen, 2));
741            $headercrc = $headercrc[1];
742            if ($headercrc != $calccrc) {
743                $error = "Header checksum failed.";
744
745                return false;    // Bad header CRC
746            }
747            $headerlen += 2;
748        }
749        // GZIP FOOTER
750        $datacrc = unpack("V", substr($data, -8, 4));
751        $datacrc = sprintf('%u', $datacrc[1] & 0xFFFFFFFF);
752        $isize = unpack("V", substr($data, -4));
753        $isize = $isize[1];
754        // decompression:
755        $bodylen = $len - $headerlen - 8;
756        if ($bodylen < 1) {
757            // IMPLEMENTATION BUG!
758            return null;
759        }
760        $body = substr($data, $headerlen, $bodylen);
761        $data = "";
762        if ($bodylen > 0) {
763            switch ($method) {
764            case 8:
765                // Currently the only supported compression method:
766                $data = gzinflate($body, $maxlength);
767                break;
768            default:
769                $error = "Unknown compression method.";
770
771                return false;
772            }
773        }  // zero-byte body content is allowed
774        // Verifiy CRC32
775        $crc = sprintf("%u", crc32($data));
776        $crcOK = $crc == $datacrc;
777        $lenOK = $isize == strlen($data);
778        if (!$lenOK || !$crcOK) {
779            $error = ($lenOK ? '' : 'Length check FAILED. ') . ($crcOK ? '' : 'Checksum FAILED.');
780
781            return false;
782        }
783
784        return $data;
785    }
786}
787