| 1 |
<?php |
| 2 |
|
| 3 |
namespace Minify\Test; |
| 4 |
|
| 5 |
use HTTP_Encoder; |
| 6 |
|
| 7 |
class HTTPEncoderTest extends TestCase |
| 8 |
{ |
| 9 |
/** |
| 10 |
* @dataProvider ToIe6DataProvider |
| 11 |
* @preserveGlobals |
| 12 |
*/ |
| 13 |
public function testToIe6($ua, $ae, $exp, $desc) |
| 14 |
{ |
| 15 |
HTTP_Encoder::$encodeToIe6 = true; |
| 16 |
|
| 17 |
$_SERVER['HTTP_USER_AGENT'] = $ua; |
| 18 |
$_SERVER['HTTP_ACCEPT_ENCODING'] = $ae; |
| 19 |
$ret = HTTP_Encoder::getAcceptedEncoding(); |
| 20 |
$this->assertSame($exp, $ret, $desc); |
| 21 |
} |
| 22 |
|
| 23 |
public function ToIe6DataProvider() |
| 24 |
{ |
| 25 |
return array( |
| 26 |
array( |
| 27 |
'ua' => 'Any browser', |
| 28 |
'ae' => 'compress, x-gzip', |
| 29 |
'exp' => array('gzip', 'x-gzip'), |
| 30 |
'desc' => 'recognize "x-gzip" as gzip', |
| 31 |
), |
| 32 |
array( |
| 33 |
'ua' => 'Any browser', |
| 34 |
'ae' => 'compress, x-gzip;q=0.5', |
| 35 |
'exp' => array('gzip', 'x-gzip'), |
| 36 |
'desc' => 'gzip w/ non-zero q', |
| 37 |
), |
| 38 |
array( |
| 39 |
'ua' => 'Any browser', |
| 40 |
'ae' => 'compress, x-gzip;q=0', |
| 41 |
'exp' => array('compress', 'compress'), |
| 42 |
'desc' => 'gzip w/ zero q', |
| 43 |
), |
| 44 |
array( |
| 45 |
'ua' => 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)', |
| 46 |
'ae' => 'gzip, deflate', |
| 47 |
'exp' => array('', ''), |
| 48 |
'desc' => 'IE6 w/o "enhanced security"', |
| 49 |
), |
| 50 |
array( |
| 51 |
'ua' => 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)', |
| 52 |
'ae' => 'gzip, deflate', |
| 53 |
'exp' => array('gzip', 'gzip'), |
| 54 |
'desc' => 'IE6 w/ "enhanced security"', |
| 55 |
), |
| 56 |
array( |
| 57 |
'ua' => 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.01)', |
| 58 |
'ae' => 'gzip, deflate', |
| 59 |
'exp' => array('', ''), |
| 60 |
'desc' => 'IE5.5', |
| 61 |
), |
| 62 |
array( |
| 63 |
'ua' => 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; en) Opera 9.25', |
| 64 |
'ae' => 'gzip,deflate', |
| 65 |
'exp' => array('gzip', 'gzip'), |
| 66 |
'desc' => 'Opera identifying as IE6', |
| 67 |
), |
| 68 |
); |
| 69 |
} |
| 70 |
|
| 71 |
/** |
| 72 |
* @dataProvider EncodeNonIeDataProvider |
| 73 |
*/ |
| 74 |
public function testEncodeNonIe($ua, $ae, $exp, $desc) |
| 75 |
{ |
| 76 |
HTTP_Encoder::$encodeToIe6 = false; |
| 77 |
|
| 78 |
$_SERVER['HTTP_USER_AGENT'] = $ua; |
| 79 |
$_SERVER['HTTP_ACCEPT_ENCODING'] = $ae; |
| 80 |
$ret = HTTP_Encoder::getAcceptedEncoding(); |
| 81 |
$this->assertSame($exp, $ret, $desc); |
| 82 |
} |
| 83 |
|
| 84 |
public function EncodeNonIeDataProvider() |
| 85 |
{ |
| 86 |
return array( |
| 87 |
array( |
| 88 |
'ua' => 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)', |
| 89 |
'ae' => 'gzip, deflate', |
| 90 |
'exp' => array('', ''), |
| 91 |
'desc' => 'IE6 w/ "enhanced security"', |
| 92 |
) |
| 93 |
); |
| 94 |
} |
| 95 |
|
| 96 |
public function testZlibEncode() |
| 97 |
{ |
| 98 |
$have_zlib = function_exists('gzdeflate'); |
| 99 |
if (!$have_zlib) { |
| 100 |
$this->markTestSkipped('Zlib support is not present in PHP. Encoding cannot be performed/tested.'); |
| 101 |
} |
| 102 |
|
| 103 |
// test compression of varied content (HTML,JS, & CSS) |
| 104 |
$variedContent = file_get_contents(self::$test_files . '/html/before.html') |
| 105 |
. file_get_contents(self::$test_files . '/css/subsilver.css') |
| 106 |
. file_get_contents(self::$test_files . '/js/jquery-1.2.3.js'); |
| 107 |
$variedLength = $this->countBytes($variedContent); |
| 108 |
|
| 109 |
$encodingTests = array( |
| 110 |
array('method' => 'deflate', 'inv' => 'gzinflate', 'exp' => 32268), |
| 111 |
array('method' => 'gzip', 'inv' => __NAMESPACE__ . '\\_gzdecode', 'exp' => 32286), |
| 112 |
array('method' => 'compress', 'inv' => 'gzuncompress', 'exp' => 32325), |
| 113 |
); |
| 114 |
|
| 115 |
foreach ($encodingTests as $test) { |
| 116 |
$e = new HTTP_Encoder(array( |
| 117 |
'content' => $variedContent, |
| 118 |
'method' => $test['method'], |
| 119 |
)); |
| 120 |
$e->encode(9); |
| 121 |
$ret = $this->countBytes($e->getContent()); |
| 122 |
|
| 123 |
// test uncompression |
| 124 |
$roundTrip = call_user_func($test['inv'], $e->getContent()); |
| 125 |
$desc = "{$test['method']} : uncompress possible"; |
| 126 |
$this->assertSame($variedContent, $roundTrip, $desc); |
| 127 |
|
| 128 |
// test expected compressed size |
| 129 |
$desc = "{$test['method']} : compressed to " |
| 130 |
. sprintf('%4.2f%% of original', $ret / $variedLength * 100); |
| 131 |
$this->assertLessThan(100, abs($ret - $test['exp']), $desc); |
| 132 |
} |
| 133 |
|
| 134 |
HTTP_Encoder::$encodeToIe6 = true; |
| 135 |
$_SERVER['HTTP_ACCEPT_ENCODING'] = 'identity'; |
| 136 |
$he = new HTTP_Encoder(array('content' => 'Hello')); |
| 137 |
$he->encode(); |
| 138 |
$headers = $he->getHeaders(); |
| 139 |
$this->assertTrue(isset($headers['Vary']), 'Vary always sent to good browsers'); |
| 140 |
|
| 141 |
$_SERVER['HTTP_USER_AGENT'] = 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)'; |
| 142 |
$he = new HTTP_Encoder(array('content' => 'Hello')); |
| 143 |
$he->encode(); |
| 144 |
$headers = $he->getHeaders(); |
| 145 |
$this->assertTrue(!isset($headers['Vary']), 'Vary not sent to bad IE (Issue 126)'); |
| 146 |
} |
| 147 |
} |
| 148 |
|
| 149 |
function _gzdecode($data) |
| 150 |
{ |
| 151 |
$filename = $error = ''; |
| 152 |
return _phpman_gzdecode($data, $filename, $error); |
| 153 |
} |
| 154 |
|
| 155 |
// http://www.php.net/manual/en/function.gzdecode.php#82930 |
| 156 |
function _phpman_gzdecode($data, &$filename='', &$error='', $maxlength=null) |
| 157 |
{ |
| 158 |
$mbIntEnc = null; |
| 159 |
$hasMbOverload = (function_exists('mb_strlen') |
| 160 |
&& (ini_get('mbstring.func_overload') !== '') |
| 161 |
&& ((int)ini_get('mbstring.func_overload') & 2)); |
| 162 |
if ($hasMbOverload) { |
| 163 |
$mbIntEnc = mb_internal_encoding(); |
| 164 |
mb_internal_encoding('8bit'); |
| 165 |
} |
| 166 |
|
| 167 |
|
| 168 |
$len = strlen($data); |
| 169 |
if ($len < 18 || strcmp(substr($data, 0, 2), "\x1f\x8b")) { |
| 170 |
$error = "Not in GZIP format."; |
| 171 |
if ($mbIntEnc !== null) { |
| 172 |
mb_internal_encoding($mbIntEnc); |
| 173 |
} |
| 174 |
return null; // Not GZIP format (See RFC 1952) |
| 175 |
} |
| 176 |
$method = ord(substr($data, 2, 1)); // Compression method |
| 177 |
$flags = ord(substr($data, 3, 1)); // Flags |
| 178 |
if ($flags & 31 != $flags) { |
| 179 |
$error = "Reserved bits not allowed."; |
| 180 |
if ($mbIntEnc !== null) { |
| 181 |
mb_internal_encoding($mbIntEnc); |
| 182 |
} |
| 183 |
return null; |
| 184 |
} |
| 185 |
// NOTE: $mtime may be negative (PHP integer limitations) |
| 186 |
$mtime = unpack("V", substr($data, 4, 4)); |
| 187 |
$mtime = $mtime[1]; |
| 188 |
$xfl = substr($data, 8, 1); |
| 189 |
$os = substr($data, 8, 1); |
| 190 |
$headerlen = 10; |
| 191 |
$extralen = 0; |
| 192 |
$extra = ""; |
| 193 |
if ($flags & 4) { |
| 194 |
// 2-byte length prefixed EXTRA data in header |
| 195 |
if ($len - $headerlen - 2 < 8) { |
| 196 |
if ($mbIntEnc !== null) { |
| 197 |
mb_internal_encoding($mbIntEnc); |
| 198 |
} |
| 199 |
return false; // invalid |
| 200 |
} |
| 201 |
$extralen = unpack("v", substr($data, 8, 2)); |
| 202 |
$extralen = $extralen[1]; |
| 203 |
if ($len - $headerlen - 2 - $extralen < 8) { |
| 204 |
if ($mbIntEnc !== null) { |
| 205 |
mb_internal_encoding($mbIntEnc); |
| 206 |
} |
| 207 |
return false; // invalid |
| 208 |
} |
| 209 |
$extra = substr($data, 10, $extralen); |
| 210 |
$headerlen += 2 + $extralen; |
| 211 |
} |
| 212 |
$filenamelen = 0; |
| 213 |
$filename = ""; |
| 214 |
if ($flags & 8) { |
| 215 |
// C-style string |
| 216 |
if ($len - $headerlen - 1 < 8) { |
| 217 |
if ($mbIntEnc !== null) { |
| 218 |
mb_internal_encoding($mbIntEnc); |
| 219 |
} |
| 220 |
return false; // invalid |
| 221 |
} |
| 222 |
$filenamelen = strpos(substr($data, $headerlen), chr(0)); |
| 223 |
if ($filenamelen === false || $len - $headerlen - $filenamelen - 1 < 8) { |
| 224 |
if ($mbIntEnc !== null) { |
| 225 |
mb_internal_encoding($mbIntEnc); |
| 226 |
} |
| 227 |
return false; // invalid |
| 228 |
} |
| 229 |
$filename = substr($data, $headerlen, $filenamelen); |
| 230 |
$headerlen += $filenamelen + 1; |
| 231 |
} |
| 232 |
$commentlen = 0; |
| 233 |
$comment = ""; |
| 234 |
if ($flags & 16) { |
| 235 |
// C-style string COMMENT data in header |
| 236 |
if ($len - $headerlen - 1 < 8) { |
| 237 |
if ($mbIntEnc !== null) { |
| 238 |
mb_internal_encoding($mbIntEnc); |
| 239 |
} |
| 240 |
return false; // invalid |
| 241 |
} |
| 242 |
$commentlen = strpos(substr($data, $headerlen), chr(0)); |
| 243 |
if ($commentlen === false || $len - $headerlen - $commentlen - 1 < 8) { |
| 244 |
if ($mbIntEnc !== null) { |
| 245 |
mb_internal_encoding($mbIntEnc); |
| 246 |
} |
| 247 |
return false; // Invalid header format |
| 248 |
} |
| 249 |
$comment = substr($data, $headerlen, $commentlen); |
| 250 |
$headerlen += $commentlen + 1; |
| 251 |
} |
| 252 |
$headercrc = ""; |
| 253 |
if ($flags & 2) { |
| 254 |
// 2-bytes (lowest order) of CRC32 on header present |
| 255 |
if ($len - $headerlen - 2 < 8) { |
| 256 |
if ($mbIntEnc !== null) { |
| 257 |
mb_internal_encoding($mbIntEnc); |
| 258 |
} |
| 259 |
return false; // invalid |
| 260 |
} |
| 261 |
$calccrc = crc32(substr($data, 0, $headerlen)) & 0xffff; |
| 262 |
$headercrc = unpack("v", substr($data, $headerlen, 2)); |
| 263 |
$headercrc = $headercrc[1]; |
| 264 |
if ($headercrc != $calccrc) { |
| 265 |
$error = "Header checksum failed."; |
| 266 |
if ($mbIntEnc !== null) { |
| 267 |
mb_internal_encoding($mbIntEnc); |
| 268 |
} |
| 269 |
return false; // Bad header CRC |
| 270 |
} |
| 271 |
$headerlen += 2; |
| 272 |
} |
| 273 |
// GZIP FOOTER |
| 274 |
$datacrc = unpack("V", substr($data, -8, 4)); |
| 275 |
$datacrc = sprintf('%u', $datacrc[1] & 0xFFFFFFFF); |
| 276 |
$isize = unpack("V", substr($data, -4)); |
| 277 |
$isize = $isize[1]; |
| 278 |
// decompression: |
| 279 |
$bodylen = $len-$headerlen-8; |
| 280 |
if ($bodylen < 1) { |
| 281 |
// IMPLEMENTATION BUG! |
| 282 |
if ($mbIntEnc !== null) { |
| 283 |
mb_internal_encoding($mbIntEnc); |
| 284 |
} |
| 285 |
return null; |
| 286 |
} |
| 287 |
$body = substr($data, $headerlen, $bodylen); |
| 288 |
$data = ""; |
| 289 |
if ($bodylen > 0) { |
| 290 |
switch ($method) { |
| 291 |
case 8: |
| 292 |
// Currently the only supported compression method: |
| 293 |
$data = gzinflate($body, $maxlength); |
| 294 |
break; |
| 295 |
default: |
| 296 |
$error = "Unknown compression method."; |
| 297 |
if ($mbIntEnc !== null) { |
| 298 |
mb_internal_encoding($mbIntEnc); |
| 299 |
} |
| 300 |
return false; |
| 301 |
} |
| 302 |
} // zero-byte body content is allowed |
| 303 |
// Verifiy CRC32 |
| 304 |
$crc = sprintf("%u", crc32($data)); |
| 305 |
$crcOK = $crc == $datacrc; |
| 306 |
$lenOK = $isize == strlen($data); |
| 307 |
if (!$lenOK || !$crcOK) { |
| 308 |
$error = ($lenOK ? '' : 'Length check FAILED. ') . ($crcOK ? '' : 'Checksum FAILED.'); |
| 309 |
$ret = false; |
| 310 |
} |
| 311 |
$ret = $data; |
| 312 |
if ($mbIntEnc !== null) { |
| 313 |
mb_internal_encoding($mbIntEnc); |
| 314 |
} |
| 315 |
return $ret; |
| 316 |
} |
| 317 |
|