summaryrefslogtreecommitdiff
path: root/min/lib/HTTP/Encoder.php
diff options
context:
space:
mode:
Diffstat (limited to 'min/lib/HTTP/Encoder.php')
-rw-r--r--min/lib/HTTP/Encoder.php326
1 files changed, 326 insertions, 0 deletions
diff --git a/min/lib/HTTP/Encoder.php b/min/lib/HTTP/Encoder.php
new file mode 100644
index 0000000..05ca552
--- /dev/null
+++ b/min/lib/HTTP/Encoder.php
@@ -0,0 +1,326 @@
+<?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 = false;
+
+
+ /**
+ * 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)
+ *
+ * @return null
+ */
+ public function __construct($spec)
+ {
+ $this->_content = $spec['content'];
+ $this->_headers['Content-Length'] = (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()
+ *
+ * @return null
+ */
+ 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.
+ *
+ * @return null
+ */
+ 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)
+ {
+ $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'] = 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;
+ }
+
+ protected $_content = '';
+ protected $_headers = array();
+ protected $_encodeMethod = array('', '');
+
+ /**
+ * Is the browser an IE version earlier than 6 SP2?
+ */
+ protected static function _isBuggyIe()
+ {
+ $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);
+ }
+}