* Merging all changes from TYPO3_4-0 branch back into HEAD
[Packages/TYPO3.CMS.git] / t3lib / class.gzip_encode.php
1 <?php
2 /**
3 * News: I had once said that when PHP 4.0.5 comes out I will reccomend the built in
4 * ob_gzhandler over my code unless you are generating flash or images on the fly.
5 *
6 * I was wrong. PHP 4.0.5 is out and ob_gzhandler doesn't work for me.
7 *
8 * Note: This is rather cool: http://leknor.com/code/gziped.php
9 * It will calculate the effects of this class on a page.
10 * compression level, cpu time, download time, etc
11 *
12 * Note: this may be better for some sites:
13 * http://www.remotecommunications.com/apache/mod_gzip/
14 * I've read that the above doesn't work with PHP output.
15 *
16 * Changes compared to the upstream version:
17 *
18 * 2005-12-09 Peter Niederlag <peter@niederlag.de>
19 * - Fixed bug #1976: PHP5 type-conversion of string 'true' and boolean
20 *
21 * $Id$
22 *
23 * @author Sandy McArthur, Jr. <leknor@leknor.com>
24 */
25 class gzip_encode {
26 /*
27 * gzip_encode - a class to gzip encode php output
28 *
29 * By Sandy McArthur, Jr. <Leknor@Leknor.com>
30 *
31 * Copyright 2001 (c) All Rights Reserved, All Responsibility Yours.
32 * One very slight modification 2005 for PHP5 compatibility reasons for TYPO3 port by Peter Niederlag
33 *
34 * This code is released under the GNU LGPL Go read it over here:
35 * http://www.gnu.org/copyleft/lesser.html
36 *
37 * I do make one optional request, I would like an account on or a
38 * copy of where this code is used. If that is not possible then
39 * an email would be cool.
40 *
41 * How to use:
42 * 1. Output buffering has to be turned on. You can do this with ob_start()
43 * <http://php.net/manual/function.ob-start.php> or in the php config
44 * file. Nothing bad happens if output buffering isn't turned on, your
45 * page just won't get compressed.
46 * 2. Include the class file.
47 * 3. At the _very_ end of your script create an instance of the encode
48 * class.
49 *
50 * eg:
51 * ------------Start of file----------
52 * |<?php
53 * | ob_start();
54 * | include('class.gzip_encode.php');
55 * |?>
56 * |<HTML>
57 * |... the page ...
58 * |</HTML>
59 * |<?php
60 * | new gzip_encode();
61 * |?>
62 * -------------End of file-----------
63 *
64 * Things to note:
65 * 1. There is no space before the beginning of the file and the '<?php ' tag
66 * 2. The ob_start() line is optional if output buffering is turned on in
67 * the main config file.
68 * 3. Turning on and off output buffering just won't work.
69 * 4. There must be nothing after the last '?>' tag at the end of the file.
70 * Be careful of a space hiding there.
71 * 5. There are better ways to compress served content but I think this is
72 * the only way to compress php output.
73 * 6. Your auto_prepend_file is a good place for the ob_start() and
74 * your auto_append_file is a good place for new gzip_encode().
75 * 7. If you put new gzip_encode() in your auto.append file then you can
76 * call ob_end_flush() in your script to disable compression.
77 *
78 * This was written from scratch from info freely available on the web.
79 *
80 * These site(s) were useful to me:
81 * http://www.php.net/manual/
82 * http://www.ietf.org/rfc/rfc2616.txt (Sections: 3.5, 14.3, 14.11)
83 *
84 * Requirments:
85 * PHP 4.0.1+: I use the '===' operator, and output buffering, crc32();
86 * zlib: Needed for the gzip encoding. (Odds are you have it)
87 *
88 * Benchmarks:
89 * Take a look at http://Leknor.com/code/gziped.php and feed it a page to
90 * get an idea of how it will preform on your data or page.
91 *
92 * To Do:
93 * 1. I have reports of no content errors. I can't seem to duplicate this.
94 * Please visit my discussion boards if you think you may be able to help
95 * 2. The Accept-Encoding isn't handled to spec. Check out 14.3 in RFC 2616
96 * to see how it should be done.
97 *
98 * Change Log:
99 * 0.66: Big bug fix. It wouldn't compress when it should.
100 * 0.65: Fix for PHP-4.0.5 suddenly removing the connection_timeout() function.
101 * 0.62: Fixed a typo
102 * 0.61: Detect file types more like described in the magic number files, also
103 * added detection for gzip and pk zip files.
104 * 0.6: Detect common file types that shouldn't be compressed, mainly
105 * for images and swf (Shockwave Flash doesn't really accept gzip)
106 * 0.53: Made gzip_accepted() method so everyone can detect if a page
107 * will be gzip'ed with ease.
108 * 0.52: Detection and graceful handling of improper install/missing libs
109 * 0.51: Added FreeBSD load average detection.
110 * 0.5: Passing true as the first parameter will try to calculate the
111 * compression level from the server's load average. Passing true
112 * as the second parameter will turn on debugging.
113 * 0.4: No longer uses a temp file to compress the output. Should speed
114 * thing up a bit and reduce wear on your hard disk. Also test if
115 * the http headers have been sent.
116 * 0.31: Made a small change to the tempnam() line to hopefully be more
117 * portable.
118 * 0.3: Added code for the 'x-gzip'. This is untested, I don't know of
119 * any browser that uses it but the RFC said to look out for it.
120 * 0.2: Checks for 'gzip' in the Accept-Encoding header
121 * 0.1: First working version.
122 *
123 * Thanks To (Suggestions and stuff):
124 * ?@boas.anthro.mnsu.edu http://php.net/manual/function.gzcompress.php
125 * Kaoslord <kaoslord@chaos-productions.com>
126 * Michael R. Gile <gilem@wsg.net>
127 * Christian Hamm <chh@admaster.de>
128 *
129 * The most recent version is available at:
130 * http://Leknor.com/code/
131 *
132 */
133
134 var $_version = 0.66; // Version of the gzip_encode class
135
136 var $level; // Compression level
137 var $encoding; // Encoding type
138 var $crc; // crc of the output
139 var $size; // size of the uncompressed content
140 var $gzsize; // size of the compressed content
141
142 /*
143 * gzip_encode constructor - gzip encodes the current output buffer
144 * if the browser supports it.
145 *
146 * Note: all arguments are optionial.
147 *
148 * You can specify one of the following for the first argument:
149 * 0: No compression
150 * 1: Min compression
151 * ... Some compression (integer from 1 to 9)
152 * 9: Max compression
153 * true: Determin the compression level from the system load. The
154 * higher the load the less the compression.
155 *
156 * You can specify one of the following for the second argument:
157 * true: Don't actully output the compressed form but run as if it
158 * had. Used for debugging.
159 */
160 function gzip_encode($level = 3, $debug = false, $outputCompressedSizes=0) {
161 if (!function_exists('gzcompress')) {
162 trigger_error('gzcompress not found, ' .
163 'zlib needs to be installed for gzip_encode',
164 E_USER_WARNING);
165 return;
166 }
167 if (!function_exists('crc32')) {
168 trigger_error('crc32() not found, ' .
169 'PHP >= 4.0.1 needed for gzip_encode', E_USER_WARNING);
170 return;
171 }
172 if (headers_sent()) return;
173 if (connection_status() !== 0) return;
174 $encoding = $this->gzip_accepted();
175 if (!$encoding) return;
176 $this->encoding = $encoding;
177
178 if (strtolower($level) == 'true' || $level === true) {
179 $level = $this->get_complevel();
180 }
181 $this->level = $level;
182
183 $contents = ob_get_contents();
184 if ($contents === false) return;
185
186 $gzdata = "\x1f\x8b\x08\x00\x00\x00\x00\x00"; // gzip header
187
188 // By Kasper Skaarhoj, start
189 if ($outputCompressedSizes) {
190 $contents.=chr(10)."<!-- Compressed, level ".$level.", original size was ".strlen($contents)." bytes. New size is ".strlen(gzcompress($contents, $level))." bytes -->";
191 $size = strlen($contents); // Must set again!
192 }
193 // By Kasper Skaarhoj, end
194
195 $size = strlen($contents);
196 $crc = crc32($contents);
197 $gzdata .= gzcompress($contents, $level);
198 $gzdata = substr($gzdata, 0, strlen($gzdata) - 4); // fix crc bug
199 $gzdata .= pack("V",$crc) . pack("V", $size);
200
201 $this->size = $size;
202 $this->crc = $crc;
203 $this->gzsize = strlen($gzdata);
204
205 if ($debug) {
206 return;
207 }
208
209 ob_end_clean();
210 Header('Content-Encoding: ' . $encoding);
211 Header('Content-Length: ' . strlen($gzdata));
212 Header('X-Content-Encoded-By: class.gzip_encode '.$this->_version);
213
214 echo $gzdata;
215 }
216
217
218 /*
219 * gzip_accepted() - Test headers for Accept-Encoding: gzip
220 *
221 * Returns: if proper headers aren't found: false
222 * if proper headers are found: 'gzip' or 'x-gzip'
223 *
224 * Tip: using this function you can test if the class will gzip the output
225 * without actually compressing it yet, eg:
226 * if (gzip_encode::gzip_accepted()) {
227 * echo "Page will be gziped";
228 * }
229 * note the double colon syntax, I don't know where it is documented but
230 * somehow it got in my brain.
231 */
232 function gzip_accepted() {
233 if (strpos(getenv("HTTP_ACCEPT_ENCODING"), 'gzip') === false) return false;
234 if (strpos(getenv("HTTP_ACCEPT_ENCODING"), 'x-gzip') === false) {
235 $encoding = 'gzip';
236 } else {
237 $encoding = 'x-gzip';
238 }
239
240 // Test file type. I wish I could get HTTP response headers.
241 $magic = substr(ob_get_contents(),0,4);
242 if (substr($magic,0,2) === '^_') {
243 // gzip data
244 $encoding = false;
245 } else if (substr($magic,0,3) === 'GIF') {
246 // gif images
247 $encoding = false;
248 } else if (substr($magic,0,2) === "\xFF\xD8") {
249 // jpeg images
250 $encoding = false;
251 } else if (substr($magic,0,4) === "\x89PNG") {
252 // png images
253 $encoding = false;
254 } else if (substr($magic,0,3) === 'FWS') {
255 // Don't gzip Shockwave Flash files. Flash on windows incorrectly
256 // claims it accepts gzip'd content.
257 $encoding = false;
258 } else if (substr($magic,0,2) === 'PK') {
259 // pk zip file
260 $encoding = false;
261 }
262
263 return $encoding;
264 }
265
266 /*
267 * get_complevel() - The level of compression we should use.
268 *
269 * Returns an int between 0 and 9 inclusive.
270 *
271 * Tip: $gzleve = gzip_encode::get_complevel(); to get the compression level
272 * that will be used with out actually compressing the output.
273 *
274 * Help: if you use an OS other then linux please send me code to make
275 * this work with your OS - Thanks
276 */
277 function get_complevel() {
278 $uname = posix_uname();
279 switch ($uname['sysname']) {
280 case 'Linux':
281 $cl = (1 - $this->linux_loadavg()) * 10;
282 $level = (int)max(min(9, $cl), 0);
283 break;
284 case 'FreeBSD':
285 $cl = (1 - $this->freebsd_loadavg()) * 10;
286 $level = (int)max(min(9, $cl), 0);
287 break;
288 default:
289 $level = 3;
290 break;
291 }
292 return $level;
293 }
294
295 /*
296 * linux_loadavg() - Gets the max() system load average from /proc/loadavg
297 *
298 * The max() Load Average will be returned
299 */
300 function linux_loadavg() {
301 $buffer = "0 0 0";
302 $f = fopen("/proc/loadavg","rb");
303 if (!feof($f)) {
304 $buffer = fgets($f, 1024);
305 }
306 fclose($f);
307 $load = explode(" ",$buffer);
308 return max((float)$load[0], (float)$load[1], (float)$load[2]);
309 }
310
311 /*
312 * freebsd_loadavg() - Gets the max() system load average from uname(1)
313 *
314 * The max() Load Average will be returned
315 *
316 * I've been told the code below will work on solaris too, anyone wanna
317 * test it?
318 */
319 function freebsd_loadavg() {
320 $buffer= `uptime`;
321 $load = array();
322 ereg("averag(es|e): ([0-9][.][0-9][0-9]), ([0-9][.][0-9][0-9]), ([0-9][.][0-9][0-9]*)", $buffer, $load);
323
324 return max((float)$load[2], (float)$load[3], (float)$load[4]);
325 }
326 }
327
328 ?>