336 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			336 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | |
| /**
 | |
|  * Class HTTP_Encoder  
 | |
|  * @package Minify
 | |
|  * @subpackage HTTP
 | |
|  */
 | |
|  
 | |
| /**
 | |
|  * Encode and send gzipped/deflated content
 | |
|  *
 | |
|  * The "Vary: Accept-Encoding" header is sent. If the client allows encoding, 
 | |
|  * Content-Encoding and Content-Length are added.
 | |
|  *
 | |
|  * <code>
 | |
|  * // Send a CSS file, compressed if possible
 | |
|  * $he = new HTTP_Encoder(array(
 | |
|  *     'content' => file_get_contents($cssFile)
 | |
|  *     ,'type' => 'text/css'
 | |
|  * ));
 | |
|  * $he->encode();
 | |
|  * $he->sendAll();
 | |
|  * </code>
 | |
|  *
 | |
|  * <code>
 | |
|  * // Shortcut to encoding output
 | |
|  * header('Content-Type: text/css'); // needed if not HTML
 | |
|  * HTTP_Encoder::output($css);
 | |
|  * </code>
 | |
|  * 
 | |
|  * <code>
 | |
|  * // Just sniff for the accepted encoding
 | |
|  * $encoding = HTTP_Encoder::getAcceptedEncoding();
 | |
|  * </code>
 | |
|  *
 | |
|  * For more control over headers, use getHeaders() and getData() and send your
 | |
|  * own output.
 | |
|  * 
 | |
|  * Note: If you don't need header mgmt, use PHP's native gzencode, gzdeflate, 
 | |
|  * and gzcompress functions for gzip, deflate, and compress-encoding
 | |
|  * respectively.
 | |
|  * 
 | |
|  * @package Minify
 | |
|  * @subpackage HTTP
 | |
|  * @author Stephen Clay <steve@mrclay.org>
 | |
|  */
 | |
| class HTTP_Encoder {
 | |
| 
 | |
|     /**
 | |
|      * Should the encoder allow HTTP encoding to IE6? 
 | |
|      * 
 | |
|      * If you have many IE6 users and the bandwidth savings is worth troubling 
 | |
|      * some of them, set this to true.
 | |
|      * 
 | |
|      * By default, encoding is only offered to IE7+. When this is true,
 | |
|      * getAcceptedEncoding() will return an encoding for IE6 if its user agent
 | |
|      * string contains "SV1". This has been documented in many places as "safe",
 | |
|      * but there seem to be remaining, intermittent encoding bugs in patched 
 | |
|      * IE6 on the wild web.
 | |
|      * 
 | |
|      * @var bool
 | |
|      */
 | |
|     public static $encodeToIe6 = true;
 | |
|     
 | |
|     
 | |
|     /**
 | |
|      * Default compression level for zlib operations
 | |
|      * 
 | |
|      * This level is used if encode() is not given a $compressionLevel
 | |
|      * 
 | |
|      * @var int
 | |
|      */
 | |
|     public static $compressionLevel = 6;
 | |
|     
 | |
| 
 | |
|     /**
 | |
|      * Get an HTTP Encoder object
 | |
|      * 
 | |
|      * @param array $spec options
 | |
|      * 
 | |
|      * 'content': (string required) content to be encoded
 | |
|      * 
 | |
|      * 'type': (string) if set, the Content-Type header will have this value.
 | |
|      * 
 | |
|      * 'method: (string) only set this if you are forcing a particular encoding
 | |
|      * method. If not set, the best method will be chosen by getAcceptedEncoding()
 | |
|      * The available methods are 'gzip', 'deflate', 'compress', and '' (no
 | |
|      * encoding)
 | |
|      */
 | |
|     public function __construct($spec) 
 | |
|     {
 | |
|         $this->_useMbStrlen = (function_exists('mb_strlen')
 | |
|                                && (ini_get('mbstring.func_overload') !== '')
 | |
|                                && ((int)ini_get('mbstring.func_overload') & 2));
 | |
|         $this->_content = $spec['content'];
 | |
|         $this->_headers['Content-Length'] = $this->_useMbStrlen
 | |
|             ? (string)mb_strlen($this->_content, '8bit')
 | |
|             : (string)strlen($this->_content);
 | |
|         if (isset($spec['type'])) {
 | |
|             $this->_headers['Content-Type'] = $spec['type'];
 | |
|         }
 | |
|         if (isset($spec['method'])
 | |
|             && in_array($spec['method'], array('gzip', 'deflate', 'compress', '')))
 | |
|         {
 | |
|             $this->_encodeMethod = array($spec['method'], $spec['method']);
 | |
|         } else {
 | |
|             $this->_encodeMethod = self::getAcceptedEncoding();
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Get content in current form
 | |
|      * 
 | |
|      * Call after encode() for encoded content.
 | |
|      * 
 | |
|      * @return string
 | |
|      */
 | |
|     public function getContent() 
 | |
|     {
 | |
|         return $this->_content;
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Get array of output headers to be sent
 | |
|      * 
 | |
|      * E.g.
 | |
|      * <code>
 | |
|      * array(
 | |
|      *     'Content-Length' => '615'
 | |
|      *     ,'Content-Encoding' => 'x-gzip'
 | |
|      *     ,'Vary' => 'Accept-Encoding'
 | |
|      * )
 | |
|      * </code>
 | |
|      *
 | |
|      * @return array 
 | |
|      */
 | |
|     public function getHeaders()
 | |
|     {
 | |
|         return $this->_headers;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Send output headers
 | |
|      * 
 | |
|      * You must call this before headers are sent and it probably cannot be
 | |
|      * used in conjunction with zlib output buffering / mod_gzip. Errors are
 | |
|      * not handled purposefully.
 | |
|      * 
 | |
|      * @see getHeaders()
 | |
|      */
 | |
|     public function sendHeaders()
 | |
|     {
 | |
|         foreach ($this->_headers as $name => $val) {
 | |
|             header($name . ': ' . $val);
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Send output headers and content
 | |
|      * 
 | |
|      * A shortcut for sendHeaders() and echo getContent()
 | |
|      *
 | |
|      * You must call this before headers are sent and it probably cannot be
 | |
|      * used in conjunction with zlib output buffering / mod_gzip. Errors are
 | |
|      * not handled purposefully.
 | |
|      */
 | |
|     public function sendAll()
 | |
|     {
 | |
|         $this->sendHeaders();
 | |
|         echo $this->_content;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Determine the client's best encoding method from the HTTP Accept-Encoding 
 | |
|      * header.
 | |
|      * 
 | |
|      * If no Accept-Encoding header is set, or the browser is IE before v6 SP2,
 | |
|      * this will return ('', ''), the "identity" encoding.
 | |
|      * 
 | |
|      * A syntax-aware scan is done of the Accept-Encoding, so the method must
 | |
|      * be non 0. The methods are favored in order of gzip, deflate, then 
 | |
|      * compress. Deflate is always smallest and generally faster, but is 
 | |
|      * rarely sent by servers, so client support could be buggier.
 | |
|      * 
 | |
|      * @param bool $allowCompress allow the older compress encoding
 | |
|      * 
 | |
|      * @param bool $allowDeflate allow the more recent deflate encoding
 | |
|      * 
 | |
|      * @return array two values, 1st is the actual encoding method, 2nd is the
 | |
|      * alias of that method to use in the Content-Encoding header (some browsers
 | |
|      * call gzip "x-gzip" etc.)
 | |
|      */
 | |
|     public static function getAcceptedEncoding($allowCompress = true, $allowDeflate = true)
 | |
|     {
 | |
|         // @link http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
 | |
|         
 | |
|         if (! isset($_SERVER['HTTP_ACCEPT_ENCODING'])
 | |
|             || self::isBuggyIe())
 | |
|         {
 | |
|             return array('', '');
 | |
|         }
 | |
|         $ae = $_SERVER['HTTP_ACCEPT_ENCODING'];
 | |
|         // gzip checks (quick)
 | |
|         if (0 === strpos($ae, 'gzip,')             // most browsers
 | |
|             || 0 === strpos($ae, 'deflate, gzip,') // opera
 | |
|         ) {
 | |
|             return array('gzip', 'gzip');
 | |
|         }
 | |
|         // gzip checks (slow)
 | |
|         if (preg_match(
 | |
|                 '@(?:^|,)\\s*((?:x-)?gzip)\\s*(?:$|,|;\\s*q=(?:0\\.|1))@'
 | |
|                 ,$ae
 | |
|                 ,$m)) {
 | |
|             return array('gzip', $m[1]);
 | |
|         }
 | |
|         if ($allowDeflate) {
 | |
|             // deflate checks    
 | |
|             $aeRev = strrev($ae);
 | |
|             if (0 === strpos($aeRev, 'etalfed ,') // ie, webkit
 | |
|                 || 0 === strpos($aeRev, 'etalfed,') // gecko
 | |
|                 || 0 === strpos($ae, 'deflate,') // opera
 | |
|                 // slow parsing
 | |
|                 || preg_match(
 | |
|                     '@(?:^|,)\\s*deflate\\s*(?:$|,|;\\s*q=(?:0\\.|1))@', $ae)) {
 | |
|                 return array('deflate', 'deflate');
 | |
|             }
 | |
|         }
 | |
|         if ($allowCompress && preg_match(
 | |
|                 '@(?:^|,)\\s*((?:x-)?compress)\\s*(?:$|,|;\\s*q=(?:0\\.|1))@'
 | |
|                 ,$ae
 | |
|                 ,$m)) {
 | |
|             return array('compress', $m[1]);
 | |
|         }
 | |
|         return array('', '');
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Encode (compress) the content
 | |
|      * 
 | |
|      * If the encode method is '' (none) or compression level is 0, or the 'zlib'
 | |
|      * extension isn't loaded, we return false.
 | |
|      * 
 | |
|      * Then the appropriate gz_* function is called to compress the content. If
 | |
|      * this fails, false is returned.
 | |
|      * 
 | |
|      * The header "Vary: Accept-Encoding" is added. If encoding is successful, 
 | |
|      * the Content-Length header is updated, and Content-Encoding is also added.
 | |
|      * 
 | |
|      * @param int $compressionLevel given to zlib functions. If not given, the
 | |
|      * class default will be used.
 | |
|      * 
 | |
|      * @return bool success true if the content was actually compressed
 | |
|      */
 | |
|     public function encode($compressionLevel = null)
 | |
|     {
 | |
|         if (! self::isBuggyIe()) {
 | |
|             $this->_headers['Vary'] = 'Accept-Encoding';
 | |
|         }
 | |
|         if (null === $compressionLevel) {
 | |
|             $compressionLevel = self::$compressionLevel;
 | |
|         }
 | |
|         if ('' === $this->_encodeMethod[0]
 | |
|             || ($compressionLevel == 0)
 | |
|             || !extension_loaded('zlib'))
 | |
|         {
 | |
|             return false;
 | |
|         }
 | |
|         if ($this->_encodeMethod[0] === 'deflate') {
 | |
|             $encoded = gzdeflate($this->_content, $compressionLevel);
 | |
|         } elseif ($this->_encodeMethod[0] === 'gzip') {
 | |
|             $encoded = gzencode($this->_content, $compressionLevel);
 | |
|         } else {
 | |
|             $encoded = gzcompress($this->_content, $compressionLevel);
 | |
|         }
 | |
|         if (false === $encoded) {
 | |
|             return false;
 | |
|         }
 | |
|         $this->_headers['Content-Length'] = $this->_useMbStrlen
 | |
|             ? (string)mb_strlen($encoded, '8bit')
 | |
|             : (string)strlen($encoded);
 | |
|         $this->_headers['Content-Encoding'] = $this->_encodeMethod[1];
 | |
|         $this->_content = $encoded;
 | |
|         return true;
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Encode and send appropriate headers and content
 | |
|      *
 | |
|      * This is a convenience method for common use of the class
 | |
|      * 
 | |
|      * @param string $content
 | |
|      * 
 | |
|      * @param int $compressionLevel given to zlib functions. If not given, the
 | |
|      * class default will be used.
 | |
|      * 
 | |
|      * @return bool success true if the content was actually compressed
 | |
|      */
 | |
|     public static function output($content, $compressionLevel = null)
 | |
|     {
 | |
|         if (null === $compressionLevel) {
 | |
|             $compressionLevel = self::$compressionLevel;
 | |
|         }
 | |
|         $he = new HTTP_Encoder(array('content' => $content));
 | |
|         $ret = $he->encode($compressionLevel);
 | |
|         $he->sendAll();
 | |
|         return $ret;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Is the browser an IE version earlier than 6 SP2?
 | |
|      *
 | |
|      * @return bool
 | |
|      */
 | |
|     public static function isBuggyIe()
 | |
|     {
 | |
|         if (empty($_SERVER['HTTP_USER_AGENT'])) {
 | |
|             return false;
 | |
|         }
 | |
|         $ua = $_SERVER['HTTP_USER_AGENT'];
 | |
|         // quick escape for non-IEs
 | |
|         if (0 !== strpos($ua, 'Mozilla/4.0 (compatible; MSIE ')
 | |
|             || false !== strpos($ua, 'Opera')) {
 | |
|             return false;
 | |
|         }
 | |
|         // no regex = faaast
 | |
|         $version = (float)substr($ua, 30);
 | |
|         return self::$encodeToIe6
 | |
|             ? ($version < 6 || ($version == 6 && false === strpos($ua, 'SV1')))
 | |
|             : ($version < 7);
 | |
|     }
 | |
|     
 | |
|     protected $_content = '';
 | |
|     protected $_headers = array();
 | |
|     protected $_encodeMethod = array('', '');
 | |
|     protected $_useMbStrlen = false;
 | |
| }
 | 
