/* * Copyright (c) 1998-2011 Caucho Technology -- all rights reserved * * This file is part of Resin(R) Open Source * * Each copy or derived work must preserve the copyright notice and this * notice unmodified. * * Resin Open Source is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Resin Open Source is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty * of NON-INFRINGEMENT. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with Resin Open Source; if not, write to the * * Free Software Foundation, Inc. * 59 Temple Place, Suite 330 * Boston, MA 02111-1307 USA * * @author Charles Reich */ package com.caucho.quercus.lib.zlib; import com.caucho.quercus.QuercusModuleException; import com.caucho.quercus.annotation.NotNull; import com.caucho.quercus.annotation.Optional; import com.caucho.quercus.annotation.ReturnNullAsFalse; import com.caucho.quercus.env.*; import com.caucho.quercus.lib.file.FileValue; import com.caucho.quercus.lib.string.StringModule; import com.caucho.util.L10N; import com.caucho.vfs.TempBuffer; import java.io.IOException; import java.io.InputStream; import java.util.logging.Level; import java.util.logging.Logger; import java.util.zip.DataFormatException; import java.util.zip.Deflater; /** * Zlib object oriented API facade */ public class Zlib { private static final Logger log = Logger.getLogger(Zlib.class.getName()); private static final L10N L = new L10N(Zlib.class); private InputStream _in; private ZlibOutputStream _gzout; private FileValue _fileValue; private boolean _isGZIPInputStream; /** * XXX: todo - implement additional read/write modes (a,etc) * * Creates and sets GZIP stream if mode is 'w' * Also creates _fileValue. All write functions are wrappers around * the _fileValue functions using the private class GZFileValueWriter to * compress the byte stream. * * * @param fileName * @param mode (ie: "w9" or "r7f") * @param useIncludePath is always on */ public Zlib(Env env, String filename, String mode, boolean useIncludePath) { String filemode = getFileMode(mode); int compressionLevel = getCompressionLevel(mode); int compressionStrategy = getCompressionStrategy(mode); Value val = null; // FileModule.fopen(env, filename, mode, useIncludePath, null); if (val != BooleanValue.FALSE) _fileValue = (FileValue)val; /* try { if (filemode.equals("r")) { _in = getGZIPInputStream(); } else if (filemode.equals("w")) { _gzout = new ZlibOutputStream(_fileValue.getPath().openWrite(), compressionLevel, compressionStrategy); } else if (filemode.equals("a")) { _gzout = new ZlibOutputStream(_fileValue.getPath().openAppend(), compressionLevel, compressionStrategy); } else if (filemode.equals("x")) { _gzout = new ZlibOutputStream(_fileValue.getPath().openWrite(), compressionLevel, compressionStrategy); } } catch (IOException e) { log.log(Level.FINE, e.getMessage(), e); env.warning(L.l(e.getMessage())); } */ } /** * Reads from the input and writes to the gzip stream * @param s * @param length # of bytes to compress * @return # of uncompressed bytes */ public int gzwrite(Env env, InputStream is, @Optional("-1") int length) { if (_fileValue == null) { env.warning(L.l("file could not be open for writing")); return -1; } TempBuffer tb = TempBuffer.allocate(); byte[] buffer = tb.getBuffer(); int inputSize = 0; int sublen; if (length < 0) length = Integer.MAX_VALUE; try { while (length > 0) { if (buffer.length < length) sublen = buffer.length; else sublen = length; sublen = is.read(buffer, 0, sublen); if (sublen <= 0) break; _gzout.write(buffer, 0, sublen); inputSize += sublen; length -= sublen; } } catch (IOException e) { log.log(Level.FINE, e.getMessage(), e); env.warning(L.l(e.getMessage())); } TempBuffer.free(tb); return inputSize; } /** * Closes the gzip stream * @return true if successful, false otherwise */ public boolean gzclose() { if (_fileValue == null) { return false; } try { if (_gzout != null) { _gzout.close(); _gzout = null; } if (_in != null) { _in.close(); _in = null; } } catch (Exception e) { throw QuercusModuleException.create(e); } return true; } /** * alias of gzwrite * @param env * @param s * @param length * @return # of uncompressed bytes */ public int gzputs(Env env, @NotNull InputStream is, @Optional("-1") int length) { return gzwrite(env, is, length); } /** * * @return the next character or BooleanValue.FALSE */ public Value gzgetc(Env env) { try { int ch = _in.read(); if (ch >= 0) return env.createString(Character.toString((char) ch)); else return BooleanValue.FALSE; } catch (IOException e) { throw QuercusModuleException.create(e); } } /** * Gets a (uncompressed) string of up to 'length' bytes read * from the given file pointer. Reading ends when 'length' bytes * have been read, on a newline, or on EOF (whichever comes first). * * @param length * @return StringValue */ @ReturnNullAsFalse public StringValue gzgets(int length) { if (_in == null) return null; UnicodeBuilderValue sbv = new UnicodeBuilderValue(); int readChar; try { for (int i = 0; i < length - 1; i++) { readChar = _in.read(); if (readChar >= 0) { sbv.append((char) readChar); if (readChar == '\n' || readChar == '\r') break; } else break; } } catch (Exception e) { throw QuercusModuleException.create(e); } if (sbv.length() > 0) return sbv; else return null; } /** * helper function for ZlibModule.gzfile * need to have created a Zlib before calling this * * @return array of uncompressed lines * @throws IOException * @throws DataFormatException */ public ArrayValue gzfile() { Value line; int oldLength = 0; ArrayValue array = new ArrayValueImpl(); try { //read in String BuilderValue's initial capacity while ((line = gzgets(Integer.MAX_VALUE)) != BooleanValue.FALSE) { array.put(line); } return array; } catch (Exception e) { throw QuercusModuleException.create(e); } } /** * same as gzgets but does not stop at '\n' or '\r' * @param length * @return BinaryValue, an empty BinaryValue if no data read * @throws IOException * @throws DataFormatException */ public StringValue gzread(Env env, int length) { StringValue sb = env.createBinaryBuilder(); int readChar; if (_in == null) return sb; sb.appendReadAll(_in, length); return sb; } /** * * @return true if eof */ public boolean gzeof() { if (_isGZIPInputStream) return ((GZIPInputStream)_in).isEOS(); else { try { _in.mark(1); int ch = _in.read(); _in.reset(); return (ch == -1); } catch (IOException e) { throw new QuercusModuleException(e); } } } /** * * @param length * @param allowedTags * @return next line stripping tags * @throws IOException * @throws DataFormatException */ @ReturnNullAsFalse public StringValue gzgetss(int length, @Optional StringValue allowedTags) { try { if (_in == null) return null; UnicodeBuilderValue sbv = new UnicodeBuilderValue(); int readChar; for (int i = 0; i < length; i++) { readChar = _in.read(); if (readChar >= 0) { sbv.append((char)readChar); if (readChar == '\n' || readChar == '\r') break; } else break; } if (sbv.length() > 0) return StringModule.strip_tags(sbv, allowedTags); else return null; } catch (Exception e) { throw QuercusModuleException.create(e); } } /** * resets to the beginning of the file stream. * * @return always true * @throws IOException */ public boolean gzrewind() { try { if (_in != null) _in.close(); _in = getGZIPInputStream(); } catch (IOException e) { throw QuercusModuleException.create(e); } return true; } /** * helper function to open file for reading when necessary * * @throws IOException */ protected InputStream getGZIPInputStream() throws IOException { try { _isGZIPInputStream = true; return new GZIPInputStream(_fileValue.getPath().openRead()); } catch (IOException e) { //GZIPInputStream throws an Exception if not in gzip format //else open uncompressed stream _isGZIPInputStream = false; return _fileValue.getPath().openRead(); } } /** * Helper function to retrieve the filemode closest to the end * Note: PHP5 unexpectedly fails when 'x' is the mode. * * XXX todo: toss a warning if '+' is found (gzip cannot be open for * both reading and writing at the same time) * */ private String getFileMode(String input) { String modifier = ""; String filemode = input.substring(0, 1); for (int i = 1; i < input.length(); i++) { char ch = input.charAt(i); switch (ch) { case 'r': filemode = "r"; break; case 'w': filemode = "w"; break; case 'a': filemode = "a"; break; case 'b': modifier = "b"; break; case 't': modifier = "t"; break; } } return filemode + modifier; } /** * Helper function to retrieve the compression level like how PHP5 does it. * 1. finds the compression level nearest to the end and returns that */ private int getCompressionLevel(String input) { for (int i = input.length() - 1; i >= 0; i--) { char ch = input.charAt(i); if (ch >= '0' && ch <= '9') return ch - '0'; } return Deflater.DEFAULT_COMPRESSION; } /** * Helper function to retrieve the compression strategy like how PHP5 does it. * 1. finds the compression strategy nearest to the end and returns that */ private int getCompressionStrategy(String input) { for (int i = input.length() - 1; i >= 0; i--) { char ch = input.charAt(i); switch (ch) { case 'f': return Deflater.FILTERED; case 'h': return Deflater.HUFFMAN_ONLY; } } return Deflater.DEFAULT_STRATEGY; } public String toString() { return "Zlib[]"; } }