<?php

namespace Minify\Test;

use HTTP_Encoder;

class HTTPEncoderTest extends TestCase
{
    /**
     * @dataProvider ToIe6DataProvider
     * @preserveGlobals
     */
    public function testToIe6($ua, $ae, $exp, $desc)
    {
        HTTP_Encoder::$encodeToIe6 = true;

        $_SERVER['HTTP_USER_AGENT'] = $ua;
        $_SERVER['HTTP_ACCEPT_ENCODING'] = $ae;
        $ret = HTTP_Encoder::getAcceptedEncoding();
        $this->assertSame($exp, $ret, $desc);
    }

    public function ToIe6DataProvider()
    {
        return array(
            array(
                'ua' => 'Any browser',
                'ae' => 'compress, x-gzip',
                'exp' => array('gzip', 'x-gzip'),
                'desc' => 'recognize "x-gzip" as gzip',
            ),
            array(
                'ua' => 'Any browser',
                'ae' => 'compress, x-gzip;q=0.5',
                'exp' => array('gzip', 'x-gzip'),
                'desc' => 'gzip w/ non-zero q',
            ),
            array(
                'ua' => 'Any browser',
                'ae' => 'compress, x-gzip;q=0',
                'exp' => array('compress', 'compress'),
                'desc' => 'gzip w/ zero q',
            ),
            array(
                'ua' => 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)',
                'ae' => 'gzip, deflate',
                'exp' => array('', ''),
                'desc' => 'IE6 w/o "enhanced security"',
            ),
            array(
                'ua' => 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)',
                'ae' => 'gzip, deflate',
                'exp' => array('gzip', 'gzip'),
                'desc' => 'IE6 w/ "enhanced security"',
            ),
            array(
                'ua' => 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.01)',
                'ae' => 'gzip, deflate',
                'exp' => array('', ''),
                'desc' => 'IE5.5',
            ),
            array(
                'ua' => 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; en) Opera 9.25',
                'ae' => 'gzip,deflate',
                'exp' => array('gzip', 'gzip'),
                'desc' => 'Opera identifying as IE6',
            ),
        );
    }

    /**
     * @dataProvider EncodeNonIeDataProvider
     */
    public function testEncodeNonIe($ua, $ae, $exp, $desc)
    {
        HTTP_Encoder::$encodeToIe6 = false;

        $_SERVER['HTTP_USER_AGENT'] = $ua;
        $_SERVER['HTTP_ACCEPT_ENCODING'] = $ae;
        $ret = HTTP_Encoder::getAcceptedEncoding();
        $this->assertSame($exp, $ret, $desc);
    }

    public function EncodeNonIeDataProvider()
    {
        return array(
            array(
                'ua' => 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)',
                'ae' => 'gzip, deflate',
                'exp' => array('', ''),
                'desc' => 'IE6 w/ "enhanced security"',
            )
        );
    }

    public function testZlibEncode()
    {
        $have_zlib = function_exists('gzdeflate');
        if (!$have_zlib) {
            $this->markTestSkipped('Zlib support is not present in PHP. Encoding cannot be performed/tested.');
        }

        // test compression of varied content (HTML,JS, & CSS)
        $variedContent = file_get_contents(self::$test_files . '/html/before.html')
            . file_get_contents(self::$test_files . '/css/subsilver.css')
            . file_get_contents(self::$test_files . '/js/jquery-1.2.3.js');
        $variedLength = $this->countBytes($variedContent);

        $encodingTests = array(
            array('method' => 'deflate', 'inv' => 'gzinflate', 'exp' => 32268),
            array('method' => 'gzip', 'inv' => __NAMESPACE__ . '\\_gzdecode', 'exp' => 32286),
            array('method' => 'compress', 'inv' => 'gzuncompress', 'exp' => 32325),
        );

        foreach ($encodingTests as $test) {
            $e = new HTTP_Encoder(array(
                'content' => $variedContent,
                'method' => $test['method'],
            ));
            $e->encode(9);
            $ret = $this->countBytes($e->getContent());

            // test uncompression
            $roundTrip = call_user_func($test['inv'], $e->getContent());
            $desc = "{$test['method']} : uncompress possible";
            $this->assertSame($variedContent, $roundTrip, $desc);

            // test expected compressed size
            $desc = "{$test['method']} : compressed to "
                . sprintf('%4.2f%% of original', $ret / $variedLength * 100);
            $this->assertLessThan(100, abs($ret - $test['exp']), $desc);
        }

        HTTP_Encoder::$encodeToIe6 = true;
        $_SERVER['HTTP_ACCEPT_ENCODING'] = 'identity';
        $he = new HTTP_Encoder(array('content' => 'Hello'));
        $he->encode();
        $headers = $he->getHeaders();
        $this->assertTrue(isset($headers['Vary']), 'Vary always sent to good browsers');

        $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)';
        $he = new HTTP_Encoder(array('content' => 'Hello'));
        $he->encode();
        $headers = $he->getHeaders();
        $this->assertTrue(!isset($headers['Vary']), 'Vary not sent to bad IE (Issue 126)');
    }
}

function _gzdecode($data)
{
    $filename = $error = '';
    return _phpman_gzdecode($data, $filename, $error);
}

// http://www.php.net/manual/en/function.gzdecode.php#82930
function _phpman_gzdecode($data, &$filename='', &$error='', $maxlength=null)
{
    $mbIntEnc = null;
    $hasMbOverload = (function_exists('mb_strlen')
                      && (ini_get('mbstring.func_overload') !== '')
                      && ((int)ini_get('mbstring.func_overload') & 2));
    if ($hasMbOverload) {
        $mbIntEnc = mb_internal_encoding();
        mb_internal_encoding('8bit');
    }


    $len = strlen($data);
    if ($len < 18 || strcmp(substr($data, 0, 2), "\x1f\x8b")) {
        $error = "Not in GZIP format.";
        if ($mbIntEnc !== null) {
            mb_internal_encoding($mbIntEnc);
        }
        return null;  // Not GZIP format (See RFC 1952)
    }
    $method = ord(substr($data, 2, 1));  // Compression method
    $flags  = ord(substr($data, 3, 1));  // Flags
    if ($flags & 31 != $flags) {
        $error = "Reserved bits not allowed.";
        if ($mbIntEnc !== null) {
            mb_internal_encoding($mbIntEnc);
        }
        return null;
    }
    // NOTE: $mtime may be negative (PHP integer limitations)
    $mtime = unpack("V", substr($data, 4, 4));
    $mtime = $mtime[1];
    $xfl   = substr($data, 8, 1);
    $os    = substr($data, 8, 1);
    $headerlen = 10;
    $extralen  = 0;
    $extra     = "";
    if ($flags & 4) {
        // 2-byte length prefixed EXTRA data in header
        if ($len - $headerlen - 2 < 8) {
            if ($mbIntEnc !== null) {
                mb_internal_encoding($mbIntEnc);
            }
            return false;  // invalid
        }
        $extralen = unpack("v", substr($data, 8, 2));
        $extralen = $extralen[1];
        if ($len - $headerlen - 2 - $extralen < 8) {
            if ($mbIntEnc !== null) {
                mb_internal_encoding($mbIntEnc);
            }
            return false;  // invalid
        }
        $extra = substr($data, 10, $extralen);
        $headerlen += 2 + $extralen;
    }
    $filenamelen = 0;
    $filename = "";
    if ($flags & 8) {
        // C-style string
        if ($len - $headerlen - 1 < 8) {
            if ($mbIntEnc !== null) {
                mb_internal_encoding($mbIntEnc);
            }
            return false; // invalid
        }
        $filenamelen = strpos(substr($data, $headerlen), chr(0));
        if ($filenamelen === false || $len - $headerlen - $filenamelen - 1 < 8) {
            if ($mbIntEnc !== null) {
                mb_internal_encoding($mbIntEnc);
            }
            return false; // invalid
        }
        $filename = substr($data, $headerlen, $filenamelen);
        $headerlen += $filenamelen + 1;
    }
    $commentlen = 0;
    $comment = "";
    if ($flags & 16) {
        // C-style string COMMENT data in header
        if ($len - $headerlen - 1 < 8) {
            if ($mbIntEnc !== null) {
                mb_internal_encoding($mbIntEnc);
            }
            return false;    // invalid
        }
        $commentlen = strpos(substr($data, $headerlen), chr(0));
        if ($commentlen === false || $len - $headerlen - $commentlen - 1 < 8) {
            if ($mbIntEnc !== null) {
                mb_internal_encoding($mbIntEnc);
            }
            return false;    // Invalid header format
        }
        $comment = substr($data, $headerlen, $commentlen);
        $headerlen += $commentlen + 1;
    }
    $headercrc = "";
    if ($flags & 2) {
        // 2-bytes (lowest order) of CRC32 on header present
        if ($len - $headerlen - 2 < 8) {
            if ($mbIntEnc !== null) {
                mb_internal_encoding($mbIntEnc);
            }
            return false;    // invalid
        }
        $calccrc = crc32(substr($data, 0, $headerlen)) & 0xffff;
        $headercrc = unpack("v", substr($data, $headerlen, 2));
        $headercrc = $headercrc[1];
        if ($headercrc != $calccrc) {
            $error = "Header checksum failed.";
            if ($mbIntEnc !== null) {
                mb_internal_encoding($mbIntEnc);
            }
            return false;    // Bad header CRC
        }
        $headerlen += 2;
    }
    // GZIP FOOTER
    $datacrc = unpack("V", substr($data, -8, 4));
    $datacrc = sprintf('%u', $datacrc[1] & 0xFFFFFFFF);
    $isize = unpack("V", substr($data, -4));
    $isize = $isize[1];
    // decompression:
    $bodylen = $len-$headerlen-8;
    if ($bodylen < 1) {
        // IMPLEMENTATION BUG!
        if ($mbIntEnc !== null) {
            mb_internal_encoding($mbIntEnc);
        }
        return null;
    }
    $body = substr($data, $headerlen, $bodylen);
    $data = "";
    if ($bodylen > 0) {
        switch ($method) {
        case 8:
            // Currently the only supported compression method:
            $data = gzinflate($body, $maxlength);
            break;
        default:
            $error = "Unknown compression method.";
            if ($mbIntEnc !== null) {
                mb_internal_encoding($mbIntEnc);
            }
            return false;
        }
    }  // zero-byte body content is allowed
    // Verifiy CRC32
    $crc   = sprintf("%u", crc32($data));
    $crcOK = $crc == $datacrc;
    $lenOK = $isize == strlen($data);
    if (!$lenOK || !$crcOK) {
        $error = ($lenOK ? '' : 'Length check FAILED. ') . ($crcOK ? '' : 'Checksum FAILED.');
        $ret = false;
    }
    $ret = $data;
    if ($mbIntEnc !== null) {
        mb_internal_encoding($mbIntEnc);
    }
    return $ret;
}
