/FileExtraction/Java_unseen/dlitz_resin/modules/quercus/src/com/caucho/quercus/lib/zlib/ZlibModule.java
/*
 * 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.BinaryInput;
import com.caucho.quercus.lib.file.BinaryOutput;
import com.caucho.quercus.lib.file.BinaryStream;
import com.caucho.quercus.lib.file.FileModule;
import com.caucho.quercus.lib.OutputModule;
import com.caucho.quercus.module.AbstractQuercusModule;
import com.caucho.util.L10N;
import com.caucho.vfs.StreamImplOutputStream;
import com.caucho.vfs.TempBuffer;
import com.caucho.vfs.TempStream;
import com.caucho.vfs.WriteStream;

import java.io.IOException;
import java.io.InputStream;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.Adler32;
import java.util.zip.Deflater;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;

/**
 * PHP Zlib
 */
public class ZlibModule extends AbstractQuercusModule {
  private static final Logger log
    = Logger.getLogger(ZlibModule.class.getName());
  private static final L10N L = new L10N(ZlibModule.class);

  public static final int FORCE_GZIP = 0x1;
  public static final int FORCE_DEFLATE = 0x2;

  private int _dbg;

  public String []getLoadedExtensions()
  {
    return new String[] { "zlib" };
  }

  /**
   *
   * @param env
   * @param fileName
   * @param mode
   * @param useIncludePath always on
   * @return Zlib
   */
  @ReturnNullAsFalse
  public static BinaryStream gzopen(Env env,
                                    StringValue fileName,
                                    String mode,
                                    @Optional("false") boolean useIncludePath)
  {
    String filemode = getFileMode(mode);
    int compressionLevel = getCompressionLevel(mode);
    int compressionStrategy = getCompressionStrategy(mode);

    Object val = FileModule.fopen(env, fileName, mode, useIncludePath, null);

    if (val == null)
      return null;

    try {
      int ch = filemode.charAt(0);

      if (ch == 'r') {
        BinaryInput is = (BinaryInput) val;
        return new ZlibInputStream(env, is);
      }
      else if (ch == 'w') {
        return new ZlibOutputStream(((BinaryOutput) val).getOutputStream(),
                                    compressionLevel,
                                    compressionStrategy);
      }
      else if (ch == 'a') {
        return new ZlibOutputStream(((BinaryOutput) val).getOutputStream(),
                                    compressionLevel,
                                    compressionStrategy);
      }
      else if (ch == 'x') {
        return new ZlibOutputStream(((BinaryOutput) val).getOutputStream(),
                                    compressionLevel,
                                    compressionStrategy);
      }
    }
    catch (IOException e) {
      log.log(Level.FINE, e.getMessage(), e);
      env.warning(L.l(e.getMessage()));
    }

    return null;
  }

  /**
   *
   * @param env
   * @param fileName
   * @param useIncludePath
   * @return array of uncompressed lines from fileName
   */
  @ReturnNullAsFalse
  public static ArrayValue gzfile(Env env,
                                  StringValue fileName,
                                  @Optional boolean useIncludePath)
  {
    BinaryInput is = (BinaryInput) gzopen(env, fileName, "r", useIncludePath);

    if (is == null)
      return null;

    try {
      ArrayValue result = new ArrayValueImpl();

      StringValue line;
      while ((line = is.readLine(Integer.MAX_VALUE)) != null
          && line.length() > 0)
        result.put(line);

      return result;
    } catch (IOException e) {
      throw new QuercusModuleException(e);
    } finally {
      is.close();
    }
  }

  public static Value ob_gzhandler(Env env, StringValue buffer, int state)
  {
    return OutputModule.ob_gzhandler(env, buffer, state);
  }

  /**
   * outputs uncompressed bytes directly to browser, writes a warning message
   *   if an error has occured
   * Note: PHP5 is supposed to print an error message but it doesn't do it
   *
   * @param env
   * @param fileName
   * @param useIncludePath
   * @return number of bytes read from file, or FALSE if an error occurred
   */
  public static Value readgzfile(Env env,
                                 StringValue fileName,
                                 @Optional boolean useIncludePath)
  {
    BinaryInput is = (BinaryInput) gzopen(env, fileName, "r", useIncludePath);

    if (is == null)
      return BooleanValue.FALSE;

    try {
      return LongValue.create(env.getOut().writeStream(is.getInputStream()));
    } catch (IOException e) {
      throw new QuercusModuleException(e);
    } finally {
      is.close();
    }
  }

  /**
   * Writes a string to the gzip stream.
   */
  public static int gzwrite(@NotNull BinaryOutput os,
                            InputStream is,
                            @Optional("0x7fffffff") int length)
  {
    if (os == null)
      return 0;

    try {
      return os.write(is, length);
    } catch (IOException e) {
      throw new QuercusModuleException(e);
    }
  }

  /**
   *
   * @param env
   * @param zp
   * @param s
   * @param length
   * @return alias of gzwrite
   */
  public int gzputs(Env env,
                    @NotNull BinaryOutput os,
                    InputStream is,
                    @Optional("0x7ffffff") int length)
  {
    if (os == null)
      return 0;

    try {
      return os.write(is, length);
    } catch (IOException e) {
      throw new QuercusModuleException(e);
    }
  }

  /**
   * Closes the stream.
   */
  public boolean gzclose(@NotNull BinaryStream os)
  {
    if (os == null)
      return false;
    
    os.close();

    return true;
  }

  /**
   * Returns true if the GZip stream is ended.
   */
  public boolean gzeof(@NotNull BinaryStream binaryStream)
  {
    if (binaryStream == null)
      return true;

    return binaryStream.isEOF();
  }

  /**
   * Reads a character from the stream.
   */
  public static Value gzgetc(Env env, @NotNull BinaryInput is)
  {
    if (is == null)
      return BooleanValue.FALSE;

    try {
      int ch = is.read();

      if (ch < 0)
        return BooleanValue.FALSE;
      else {
        StringValue sb = env.createBinaryBuilder(1);

        sb.appendByte(ch);

        return sb;
      }
    } catch (IOException e) {
      throw new QuercusModuleException(e);
    }
  }

  /**
   * Reads a chunk of data from the gzip stream.
   */
  public Value gzread(@NotNull BinaryInput is, int length)
  {
    if (is == null)
      return BooleanValue.FALSE;

    try {
      return is.read(length);
    } catch (IOException e) {
      throw new QuercusModuleException(e);
    }
  }

  /**
   * Reads a line from the input stream.
   */
  public static Value gzgets(Env env,
                              @NotNull BinaryInput is,
                              int length)
  {
    return FileModule.fgets(env, is, length);
  }

  /**
   * Reads a line from the zip stream, stripping tags.
   */
  public static Value gzgetss(Env env,
                              @NotNull BinaryInput is,
                              int length,
                              @Optional Value allowedTags)
  {
    return FileModule.fgetss(env, is, length, allowedTags);
  }

  /**
   * Rewinds the stream to the very beginning
   */
  public boolean gzrewind(@NotNull BinaryStream binaryStream)
  {
    if (binaryStream == null)
      return false;

    return binaryStream.setPosition(0);
  }

  /**
   * Set stream position to the offset
   * @param offset absolute position to set stream to
   * @param whence if set, changes the interpretation of offset like fseek
   * @return 0 upon success, else -1 for error
   */
  public int gzseek(@NotNull BinaryStream binaryStream,
                    long offset,
                    @Optional("FileModule.SEEK_SET") int whence)
  {
    if (binaryStream == null)
      return -1;

    if (binaryStream.seek(offset, whence) == -1)
      return -1;

    return 0;
  }

  /**
   * Gets the current position in the stream
   * @return the position in the stream, or FALSE for error
   */
  public Value gztell(@NotNull BinaryStream binaryStream)
  {
    if (binaryStream == null)
      return BooleanValue.FALSE;
    return LongValue.create(binaryStream.getPosition());
  }

  /**
   * Prints out the remaining data in the stream to stdout
   */
  public Value gzpassthru(Env env, @NotNull BinaryInput is)
  {
    WriteStream out = env.getOut();
    TempBuffer tempBuf = TempBuffer.allocate();
    byte[] buffer = tempBuf.getBuffer();

    int length = 0;
    try {
      int sublen = is.read(buffer, 0, buffer.length);
      while (sublen > 0) {
        out.write(buffer, 0, sublen);
        length += sublen;
        sublen = is.read(buffer, 0, buffer.length);
      }

      return LongValue.create(length);

    } catch (IOException e) {
      log.log(Level.FINE, e.toString(), e);

      return BooleanValue.FALSE;

    } finally {
      TempBuffer.free(tempBuf);
    }
  }

  /**
   * Returns the encoding type both allowed by the server
   *   and supported by the user's browser.
   */
  public Value zlib_get_coding_type(Env env)
  {
    String ini = env.getIniString("zlib.output_compression");

    if (ini == null || ini == "")
      return BooleanValue.FALSE;

    //zlib_get_coding_type can also be an integer > 0
    if (! ini.equalsIgnoreCase("on")) {
      int ch = ini.charAt(0);

      if (ch < '0' || ch > '9')
        return BooleanValue.FALSE;
    }

    ServerArrayValue sav = new ServerArrayValue(env);
    Value val = sav.get(env.createString("HTTP_ACCEPT_ENCODING"));

    if (!val.isset())
      return BooleanValue.FALSE;

    String s = val.toString();
    if (s.contains("gzip"))
      return env.createString("gzip");
    else if (s.contains("deflate"))
      return env.createString("deflate");
    else
      return BooleanValue.FALSE;
  }

  /**
   * compresses data using zlib
   *
   * @param data
   * @param level (default is Deflater.DEFAULT_COMPRESSION)
   * @return compressed string
   */
  public Value gzcompress(Env env,
                          InputStream data,
                          @Optional("6") int level)
  {
    TempBuffer tempBuf = TempBuffer.allocate();
    byte []buffer = tempBuf.getBuffer();

    Deflater deflater = null;
    
    try {
      deflater = new Deflater(level, true);
      Adler32 crc = new Adler32();

      boolean isFinished = false;

      StringValue out = env.createLargeBinaryBuilder();

      buffer[0] = (byte) 0x78;

      if (level <= 1)
        buffer[1] = (byte) 0x01;
      else if (level < 6)
        buffer[1] = (byte) 0x5e;
      else if (level == 6)
        buffer[1] = (byte) 0x9c;
      else
        buffer[1] = (byte) 0xda;

      out.append(buffer, 0, 2);

      int len;
      while (! isFinished) {
        while (! isFinished && deflater.needsInput()) {
          len = data.read(buffer, 0, buffer.length);

          if (len > 0) {
            crc.update(buffer, 0, len);
            deflater.setInput(buffer, 0, len);
          }
          else {
            isFinished = true;
            deflater.finish();
          }
        }

        while ((len = deflater.deflate(buffer, 0, buffer.length)) > 0) {
          out.append(buffer, 0, len);
        }
      }

      long value = crc.getValue();
    
      buffer[0] = (byte) (value >> 24);
      buffer[1] = (byte) (value >> 16);
      buffer[2] = (byte) (value >> 8);
      buffer[3] = (byte) (value >> 0);

      out.append(buffer, 0, 4);

      return out;
    } catch (Exception e) {
      throw QuercusModuleException.create(e);
    } finally {
      TempBuffer.free(tempBuf);

      if (deflater != null)
        deflater.end();
    }
  }

  /**
   *
   * @param data
   * @param length (maximum length of string returned)
   * @return uncompressed string
   */
  public Value gzuncompress(Env env,
                            InputStream is,
                            @Optional("0") long length)
  {
    TempBuffer tempBuf = TempBuffer.allocate();
    byte []buffer = tempBuf.getBuffer();

    InflaterInputStream in = null;
    try {
      if (length == 0)
        length = Long.MAX_VALUE;

      in = new InflaterInputStream(is);

      StringValue sb = env.createLargeBinaryBuilder();

      int len;
      while ((len = in.read(buffer, 0, buffer.length)) >= 0) {
        sb.append(buffer, 0, len);
      }

      return sb;
    } catch (Exception e) {
      throw QuercusModuleException.create(e);
    } finally {
      TempBuffer.free(tempBuf);

      try {
        if (in != null)
          in.close();
      } catch (Exception e) {
      }
    }
  }
  
  /**
   *
   * @param level
   * @return compressed using DEFLATE algorithm
   */
  public Value gzdeflate(Env env, InputStream data,
                         @Optional("6") int level)
  {
    TempBuffer tempBuf = TempBuffer.allocate();
    byte []buffer = tempBuf.getBuffer();
    Deflater deflater = null;
    
    try {
      deflater = new Deflater(level, true);

      boolean isFinished = false;
      TempStream out = new TempStream();

      int len;
      while (! isFinished) {
        if (! isFinished && deflater.needsInput()) {
          len = data.read(buffer, 0, buffer.length);

          if (len > 0)
            deflater.setInput(buffer, 0, len);
          else {
            isFinished = true;
            deflater.finish();
          }
        }

        while ((len = deflater.deflate(buffer, 0, buffer.length)) > 0) {
          out.write(buffer, 0, len, false);
        }
      }
      deflater.end();

      return env.createBinaryString(out.getHead());

    } catch (Exception e) {
      throw QuercusModuleException.create(e);
    } finally {
      TempBuffer.free(tempBuf);

      if (deflater != null)
        deflater.end();
    }
  }

  /**
   * @param data compressed using Deflate algorithm
   * @param length of data to decompress
   *
   * @return uncompressed string
   */
  public Value gzinflate(Env env,
                         InputStream data,
                         @Optional("0") int length)
  {
    if (length <= 0)
      length = Integer.MAX_VALUE;
    
    TempBuffer tempBuf = TempBuffer.allocate();
    byte []buffer = tempBuf.getBuffer();
    Inflater inflater = null;

    try {
      inflater = new Inflater(true);
      StringValue sb = env.createBinaryBuilder();

      while (true) {
        int sublen = Math.min(length, buffer.length);
          
        sublen = data.read(buffer, 0, sublen);

        if (sublen > 0) {
          inflater.setInput(buffer, 0, sublen);
          length -= sublen;
          
          int inflatedLength;
          while ((inflatedLength = inflater.inflate(buffer, 0, sublen)) > 0) {
            sb.append(buffer, 0, inflatedLength);
          }
        }
        else
          break;
      }

      return sb;
    } catch (OutOfMemoryError e) {
      env.warning(e);
      return BooleanValue.FALSE;
    } catch (Exception e) {
      env.warning(e);
      return BooleanValue.FALSE;
    } finally {
      TempBuffer.free(tempBuf);

      if (inflater != null)
        inflater.end();
    }
  }

  /**
   *
   * Compresses data using the Deflate algorithm, output is
   * compatible with gzwrite's output
   *
   * @param data compressed with the Deflate algorithm
   * @param level Deflate compresion level [0-9]
   * @param encodingMode CRC32 trailer is not written if encoding mode
   *    is FORCE_DEFLATE, default is to write CRC32
   * @return StringValue with gzip header and trailer
   */
  public Value gzencode(Env env, InputStream is,
                        @Optional("6") int level,
                        @Optional("1") int encodingMode)
  {
    TempBuffer tempBuf = TempBuffer.allocate();
    byte[] buffer = tempBuf.getBuffer();

    TempStream ts = new TempStream();
    StreamImplOutputStream out = new StreamImplOutputStream(ts);

    ZlibOutputStream gzOut = null;

    try {
      gzOut = new ZlibOutputStream(out, level,
                                   Deflater.DEFAULT_STRATEGY,
                                   encodingMode);

      int len;
      while ((len = is.read(buffer, 0, buffer.length)) > 0) {
        gzOut.write(buffer, 0, len);
      }
      gzOut.close();

      StringValue sb = env.createBinaryBuilder();
      for (TempBuffer ptr = ts.getHead(); ptr != null; ptr = ptr.getNext())
        sb.append(ptr.getBuffer(), 0, ptr.getLength());

      return sb;
    } catch (IOException e) {
      throw QuercusModuleException.create(e);
    } finally {
      TempBuffer.free(tempBuf);

      ts.destroy();

      if (gzOut != null)
        gzOut.close();
    }
  }

  /**
   * 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 static 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
   *     - finds the compression level nearest to the end and returns that
   */
  private static 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.
   *     - finds the compression strategy nearest to the end and returns that
   */
  private static 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;
  }
}
