001: /*
002: * Copyright (c) 1998-2008 Caucho Technology -- all rights reserved
003: *
004: * This file is part of Resin(R) Open Source
005: *
006: * Each copy or derived work must preserve the copyright notice and this
007: * notice unmodified.
008: *
009: * Resin Open Source is free software; you can redistribute it and/or modify
010: * it under the terms of the GNU General Public License as published by
011: * the Free Software Foundation; either version 2 of the License, or
012: * (at your option) any later version.
013: *
014: * Resin Open Source is distributed in the hope that it will be useful,
015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
017: * of NON-INFRINGEMENT. See the GNU General Public License for more
018: * details.
019: *
020: * You should have received a copy of the GNU General Public License
021: * along with Resin Open Source; if not, write to the
022: *
023: * Free Software Foundation, Inc.
024: * 59 Temple Place, Suite 330
025: * Boston, MA 02111-1307 USA
026: *
027: * @author Charles Reich
028: */
029:
030: package com.caucho.quercus.lib.zlib;
031:
032: import com.caucho.quercus.QuercusModuleException;
033: import com.caucho.quercus.annotation.NotNull;
034: import com.caucho.quercus.annotation.Optional;
035: import com.caucho.quercus.annotation.ReturnNullAsFalse;
036: import com.caucho.quercus.env.*;
037: import com.caucho.quercus.lib.file.BinaryInput;
038: import com.caucho.quercus.lib.file.BinaryOutput;
039: import com.caucho.quercus.lib.file.BinaryStream;
040: import com.caucho.quercus.lib.file.FileModule;
041: import com.caucho.quercus.lib.OutputModule;
042: import com.caucho.quercus.module.AbstractQuercusModule;
043: import com.caucho.util.L10N;
044: import com.caucho.vfs.StreamImplOutputStream;
045: import com.caucho.vfs.TempBuffer;
046: import com.caucho.vfs.TempStream;
047: import com.caucho.vfs.WriteStream;
048:
049: import java.io.IOException;
050: import java.io.InputStream;
051: import java.util.logging.Level;
052: import java.util.logging.Logger;
053: import java.util.zip.Adler32;
054: import java.util.zip.Deflater;
055: import java.util.zip.Inflater;
056: import java.util.zip.InflaterInputStream;
057:
058: /**
059: * PHP Zlib
060: */
061: public class ZlibModule extends AbstractQuercusModule {
062: private static final Logger log = Logger.getLogger(ZlibModule.class
063: .getName());
064: private static final L10N L = new L10N(ZlibModule.class);
065:
066: public static final int FORCE_GZIP = 0x1;
067: public static final int FORCE_DEFLATE = 0x2;
068:
069: public String[] getLoadedExtensions() {
070: return new String[] { "zlib" };
071: }
072:
073: /**
074: *
075: * @param env
076: * @param fileName
077: * @param mode
078: * @param useIncludePath always on
079: * @return Zlib
080: */
081: @ReturnNullAsFalse
082: public static BinaryStream gzopen(Env env, StringValue fileName,
083: String mode, @Optional("false")
084: boolean useIncludePath) {
085: String filemode = getFileMode(mode);
086: int compressionLevel = getCompressionLevel(mode);
087: int compressionStrategy = getCompressionStrategy(mode);
088:
089: Object val = FileModule.fopen(env, fileName, mode,
090: useIncludePath, null);
091:
092: if (val == null)
093: return null;
094:
095: try {
096: int ch = filemode.charAt(0);
097:
098: if (ch == 'r') {
099: BinaryInput is = (BinaryInput) val;
100: return new ZlibInputStream(is);
101: } else if (ch == 'w') {
102: return new ZlibOutputStream(((BinaryOutput) val)
103: .getOutputStream(), compressionLevel,
104: compressionStrategy);
105: } else if (ch == 'a') {
106: return new ZlibOutputStream(((BinaryOutput) val)
107: .getOutputStream(), compressionLevel,
108: compressionStrategy);
109: } else if (ch == 'x') {
110: return new ZlibOutputStream(((BinaryOutput) val)
111: .getOutputStream(), compressionLevel,
112: compressionStrategy);
113: }
114: } catch (IOException e) {
115: log.log(Level.FINE, e.getMessage(), e);
116: env.warning(L.l(e.getMessage()));
117: }
118:
119: return null;
120: }
121:
122: /**
123: *
124: * @param env
125: * @param fileName
126: * @param useIncludePath
127: * @return array of uncompressed lines from fileName
128: */
129: @ReturnNullAsFalse
130: public static ArrayValue gzfile(Env env, StringValue fileName,
131: @Optional
132: boolean useIncludePath) {
133: BinaryInput is = (BinaryInput) gzopen(env, fileName, "r",
134: useIncludePath);
135:
136: if (is == null)
137: return null;
138:
139: try {
140: ArrayValue result = new ArrayValueImpl();
141:
142: StringValue line;
143: while ((line = is.readLine(Integer.MAX_VALUE)) != null
144: && line.length() > 0)
145: result.put(line);
146:
147: return result;
148: } catch (IOException e) {
149: throw new QuercusModuleException(e);
150: } finally {
151: is.close();
152: }
153: }
154:
155: public static Value ob_gzhandler(Env env, StringValue buffer,
156: int state) {
157: return OutputModule.ob_gzhandler(env, buffer, state);
158: }
159:
160: /**
161: * outputs uncompressed bytes directly to browser, writes a warning message
162: * if an error has occured
163: * Note: PHP5 is supposed to print an error message but it doesn't do it
164: *
165: * @param env
166: * @param fileName
167: * @param useIncludePath
168: * @return number of bytes read from file, or FALSE if an error occurred
169: */
170: public static Value readgzfile(Env env, StringValue fileName,
171: @Optional
172: boolean useIncludePath) {
173: BinaryInput is = (BinaryInput) gzopen(env, fileName, "r",
174: useIncludePath);
175:
176: if (is == null)
177: return BooleanValue.FALSE;
178:
179: try {
180: return LongValue.create(env.getOut().writeStream(
181: is.getInputStream()));
182: } catch (IOException e) {
183: throw new QuercusModuleException(e);
184: } finally {
185: is.close();
186: }
187: }
188:
189: /**
190: * Writes a string to the gzip stream.
191: */
192: public static int gzwrite(@NotNull
193: BinaryOutput os, InputStream is, @Optional("0x7fffffff")
194: int length) {
195: if (os == null)
196: return 0;
197:
198: try {
199: return os.write(is, length);
200: } catch (IOException e) {
201: throw new QuercusModuleException(e);
202: }
203: }
204:
205: /**
206: *
207: * @param env
208: * @param zp
209: * @param s
210: * @param length
211: * @return alias of gzwrite
212: */
213: public int gzputs(Env env, @NotNull
214: BinaryOutput os, InputStream is, @Optional("0x7ffffff")
215: int length) {
216: if (os == null)
217: return 0;
218:
219: try {
220: return os.write(is, length);
221: } catch (IOException e) {
222: throw new QuercusModuleException(e);
223: }
224: }
225:
226: /**
227: * Closes the stream.
228: */
229: public boolean gzclose(@NotNull
230: BinaryStream os) {
231: if (os == null)
232: return false;
233:
234: os.close();
235:
236: return true;
237: }
238:
239: /**
240: * Returns true if the GZip stream is ended.
241: */
242: public boolean gzeof(@NotNull
243: BinaryStream binaryStream) {
244: if (binaryStream == null)
245: return true;
246:
247: return binaryStream.isEOF();
248: }
249:
250: /**
251: * Reads a character from the stream.
252: */
253: public static Value gzgetc(Env env, @NotNull
254: BinaryInput is) {
255: if (is == null)
256: return BooleanValue.FALSE;
257:
258: try {
259: int ch = is.read();
260:
261: if (ch < 0)
262: return BooleanValue.FALSE;
263: else {
264: StringValue sb = env.createBinaryBuilder(1);
265:
266: sb.appendByte(ch);
267:
268: return sb;
269: }
270: } catch (IOException e) {
271: throw new QuercusModuleException(e);
272: }
273: }
274:
275: /**
276: * Reads a chunk of data from the gzip stream.
277: */
278: @ReturnNullAsFalse
279: public StringValue gzread(@NotNull
280: BinaryInput is, int length) {
281: if (is == null)
282: return null;
283:
284: try {
285: return is.read(length);
286: } catch (IOException e) {
287: throw new QuercusModuleException(e);
288: }
289: }
290:
291: /**
292: * Reads a line from the input stream.
293: */
294: public static Value gzgets(Env env, @NotNull
295: BinaryInput is, int length) {
296: return FileModule.fgets(env, is, length);
297: }
298:
299: /**
300: * Reads a line from the zip stream, stripping tags.
301: */
302: public static Value gzgetss(Env env, @NotNull
303: BinaryInput is, int length, @Optional
304: String allowedTags) {
305: return FileModule.fgetss(env, is, length, allowedTags);
306: }
307:
308: /**
309: * Rewinds the stream to the very beginning
310: */
311: public boolean gzrewind(@NotNull
312: BinaryStream binaryStream) {
313: if (binaryStream == null)
314: return false;
315:
316: return binaryStream.setPosition(0);
317: }
318:
319: /**
320: * Set stream position to the offset
321: * @param offset absolute position to set stream to
322: * @return 0 upon success, else -1 for error
323: */
324: public int gzseek(@NotNull
325: BinaryStream binaryStream, long offset) {
326: if (binaryStream == null)
327: return -1;
328: if (binaryStream.setPosition(offset) == false)
329: return -1;
330: if (binaryStream.getPosition() != offset)
331: return -1;
332:
333: return 0;
334: }
335:
336: /**
337: * Gets the current position in the stream
338: * @return the position in the stream, or FALSE for error
339: */
340: public Value gztell(@NotNull
341: BinaryStream binaryStream) {
342: if (binaryStream == null)
343: return BooleanValue.FALSE;
344: return new LongValue(binaryStream.getPosition());
345: }
346:
347: /**
348: * Prints out the remaining data in the stream to stdout
349: */
350: public Value gzpassthru(Env env, @NotNull
351: BinaryInput is) {
352: WriteStream out = env.getOut();
353: TempBuffer tempBuf = TempBuffer.allocate();
354: byte[] buffer = tempBuf.getBuffer();
355:
356: int length = 0;
357: try {
358: int sublen = is.read(buffer, 0, buffer.length);
359: while (sublen > 0) {
360: out.write(buffer, 0, sublen);
361: length += sublen;
362: sublen = is.read(buffer, 0, buffer.length);
363: }
364:
365: return new LongValue(length);
366:
367: } catch (IOException e) {
368: log.log(Level.FINE, e.toString(), e);
369:
370: return BooleanValue.FALSE;
371:
372: } finally {
373: TempBuffer.free(tempBuf);
374: }
375: }
376:
377: /**
378: * Returns the encoding type both allowed by the server
379: * and supported by the user's browser.
380: */
381: public Value zlib_get_coding_type(Env env) {
382: String ini = env.getIniString("zlib.output_compression");
383:
384: if (ini == null || ini == "")
385: return BooleanValue.FALSE;
386:
387: //zlib_get_coding_type can also be an integer > 0
388: if (!ini.equalsIgnoreCase("on")) {
389: int ch = ini.charAt(0);
390:
391: if (ch < '0' || ch > '9')
392: return BooleanValue.FALSE;
393: }
394:
395: ServerArrayValue sav = new ServerArrayValue(env);
396: Value val = sav.get(env.createString("HTTP_ACCEPT_ENCODING"));
397:
398: if (!val.isset())
399: return BooleanValue.FALSE;
400:
401: String s = val.toString();
402: if (s.contains("gzip"))
403: return env.createString("gzip");
404: else if (s.contains("deflate"))
405: return env.createString("deflate");
406: else
407: return BooleanValue.FALSE;
408: }
409:
410: /**
411: * compresses data using zlib
412: *
413: * @param data
414: * @param level (default is Deflater.DEFAULT_COMPRESSION)
415: * @return compressed string
416: */
417: public Value gzcompress(Env env, InputStream data, @Optional("6")
418: int level) {
419: TempBuffer tempBuf = TempBuffer.allocate();
420: byte[] buffer = tempBuf.getBuffer();
421:
422: try {
423: Deflater deflater = new Deflater(level, true);
424: Adler32 crc = new Adler32();
425:
426: boolean isFinished = false;
427:
428: StringValue out = env.createLargeBinaryBuilder();
429:
430: buffer[0] = (byte) 0x78;
431:
432: if (level <= 1)
433: buffer[1] = (byte) 0x01;
434: else if (level < 6)
435: buffer[1] = (byte) 0x5e;
436: else if (level == 6)
437: buffer[1] = (byte) 0x9c;
438: else
439: buffer[1] = (byte) 0xda;
440:
441: out.append(buffer, 0, 2);
442:
443: int len;
444: while (!isFinished) {
445: while (!isFinished && deflater.needsInput()) {
446: len = data.read(buffer, 0, buffer.length);
447:
448: if (len > 0) {
449: crc.update(buffer, 0, len);
450: deflater.setInput(buffer, 0, len);
451: } else {
452: isFinished = true;
453: deflater.finish();
454: }
455: }
456:
457: while ((len = deflater
458: .deflate(buffer, 0, buffer.length)) > 0) {
459: out.append(buffer, 0, len);
460: }
461: }
462:
463: long value = crc.getValue();
464:
465: buffer[0] = (byte) (value >> 24);
466: buffer[1] = (byte) (value >> 16);
467: buffer[2] = (byte) (value >> 8);
468: buffer[3] = (byte) (value >> 0);
469:
470: out.append(buffer, 0, 4);
471:
472: return out;
473: } catch (Exception e) {
474: throw QuercusModuleException.create(e);
475: } finally {
476: TempBuffer.free(tempBuf);
477: }
478: }
479:
480: /**
481: *
482: * @param data
483: * @param length (maximum length of string returned)
484: * @return uncompressed string
485: */
486: public Value gzuncompress(Env env, InputStream is, @Optional("0")
487: long length) {
488: TempBuffer tempBuf = TempBuffer.allocate();
489: byte[] buffer = tempBuf.getBuffer();
490:
491: try {
492: if (length == 0)
493: length = Long.MAX_VALUE;
494:
495: InflaterInputStream in = new InflaterInputStream(is);
496:
497: StringValue sb = env.createLargeBinaryBuilder();
498:
499: int len;
500: while ((len = in.read(buffer, 0, buffer.length)) >= 0) {
501: sb.append(buffer, 0, len);
502: }
503:
504: in.close();
505:
506: return sb;
507: } catch (Exception e) {
508: throw QuercusModuleException.create(e);
509: } finally {
510: TempBuffer.free(tempBuf);
511: }
512: }
513:
514: private int _dbg;
515:
516: /**
517: *
518: * @param level
519: * @return compressed using DEFLATE algorithm
520: */
521: public Value gzdeflate(Env env, InputStream data, @Optional("6")
522: int level) {
523: TempBuffer tempBuf = TempBuffer.allocate();
524: byte[] buffer = tempBuf.getBuffer();
525:
526: try {
527: Deflater deflater = new Deflater(level, true);
528:
529: boolean isFinished = false;
530: TempStream out = new TempStream();
531:
532: int len;
533: while (!isFinished) {
534: if (!isFinished && deflater.needsInput()) {
535: len = data.read(buffer, 0, buffer.length);
536:
537: if (len > 0)
538: deflater.setInput(buffer, 0, len);
539: else {
540: isFinished = true;
541: deflater.finish();
542: }
543: }
544:
545: while ((len = deflater
546: .deflate(buffer, 0, buffer.length)) > 0) {
547: out.write(buffer, 0, len, false);
548: }
549: }
550: deflater.end();
551:
552: return env.createBinaryString(out.getHead());
553:
554: } catch (Exception e) {
555: throw QuercusModuleException.create(e);
556: } finally {
557: TempBuffer.free(tempBuf);
558: }
559: }
560:
561: /**
562: * @param data compressed using Deflate algorithm
563: * @param length of data to decompress
564: *
565: * @return uncompressed string
566: */
567: public Value gzinflate(Env env, InputStream data, @Optional("0")
568: int length) {
569: if (length <= 0)
570: length = Integer.MAX_VALUE;
571:
572: TempBuffer tempBuf = TempBuffer.allocate();
573: byte[] buffer = tempBuf.getBuffer();
574:
575: try {
576: Inflater inflater = new Inflater(true);
577: StringValue sb = env.createBinaryBuilder();
578:
579: while (true) {
580: int sublen = Math.min(length, buffer.length);
581:
582: sublen = data.read(buffer, 0, sublen);
583:
584: if (sublen > 0) {
585: inflater.setInput(buffer, 0, sublen);
586: length -= sublen;
587:
588: int inflatedLength;
589: while ((inflatedLength = inflater.inflate(buffer,
590: 0, sublen)) > 0) {
591: sb.append(buffer, 0, inflatedLength);
592: }
593: } else
594: break;
595: }
596:
597: inflater.end();
598:
599: return sb;
600: } catch (Exception e) {
601: env.warning(e);
602: return BooleanValue.FALSE;
603: } finally {
604: TempBuffer.free(tempBuf);
605: }
606: }
607:
608: /**
609: *
610: * Compresses data using the Deflate algorithm, output is
611: * compatible with gzwrite's output
612: *
613: * @param data compressed with the Deflate algorithm
614: * @param level Deflate compresion level [0-9]
615: * @param encodingMode CRC32 trailer is not written if encoding mode
616: * is FORCE_DEFLATE, default is to write CRC32
617: * @return StringValue with gzip header and trailer
618: */
619: public Value gzencode(Env env, InputStream is, @Optional("6")
620: int level, @Optional("1")
621: int encodingMode) {
622: TempBuffer tempBuf = TempBuffer.allocate();
623: byte[] buffer = tempBuf.getBuffer();
624:
625: TempStream ts = new TempStream();
626: StreamImplOutputStream out = new StreamImplOutputStream(ts);
627:
628: try {
629: ZlibOutputStream gzOut;
630:
631: gzOut = new ZlibOutputStream(out, level,
632: Deflater.DEFAULT_STRATEGY, encodingMode);
633:
634: int len;
635: while ((len = is.read(buffer, 0, buffer.length)) > 0) {
636: gzOut.write(buffer, 0, len);
637: }
638: gzOut.close();
639:
640: StringValue sb = env.createBinaryBuilder();
641: for (TempBuffer ptr = ts.getHead(); ptr != null; ptr = ptr
642: .getNext())
643: sb.append(ptr.getBuffer(), 0, ptr.getLength());
644:
645: return sb;
646: } catch (IOException e) {
647: throw QuercusModuleException.create(e);
648: } finally {
649: TempBuffer.free(tempBuf);
650:
651: ts.destroy();
652: }
653: }
654:
655: /**
656: * Helper function to retrieve the filemode closest to the end
657: * Note: PHP5 unexpectedly fails when 'x' is the mode.
658: *
659: * XXX todo: toss a warning if '+' is found
660: * (gzip cannot be open for both reading and writing at the same time)
661: *
662: */
663: private static String getFileMode(String input) {
664: String modifier = "";
665: String filemode = input.substring(0, 1);
666:
667: for (int i = 1; i < input.length(); i++) {
668: char ch = input.charAt(i);
669: switch (ch) {
670: case 'r':
671: filemode = "r";
672: break;
673: case 'w':
674: filemode = "w";
675: break;
676: case 'a':
677: filemode = "a";
678: break;
679: case 'b':
680: modifier = "b";
681: break;
682: case 't':
683: modifier = "t";
684: break;
685: }
686: }
687: return filemode + modifier;
688: }
689:
690: /**
691: * Helper function to retrieve the compression level
692: * - finds the compression level nearest to the end and returns that
693: */
694: private static int getCompressionLevel(String input) {
695: for (int i = input.length() - 1; i >= 0; i--) {
696: char ch = input.charAt(i);
697:
698: if (ch >= '0' && ch <= '9')
699: return ch - '0';
700: }
701:
702: return Deflater.DEFAULT_COMPRESSION;
703: }
704:
705: /**
706: * Helper function to retrieve the compression strategy.
707: * - finds the compression strategy nearest to the end and returns that
708: */
709: private static int getCompressionStrategy(String input) {
710: for (int i = input.length() - 1; i >= 0; i--) {
711: char ch = input.charAt(i);
712:
713: switch (ch) {
714: case 'f':
715: return Deflater.FILTERED;
716:
717: case 'h':
718: return Deflater.HUFFMAN_ONLY;
719: }
720: }
721:
722: return Deflater.DEFAULT_STRATEGY;
723: }
724: }
|