/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package org.apache.coyote.http11.filters;

import java.io.IOException;
import java.io.OutputStream;
import java.util.zip.Deflater;
import java.util.zip.GZIPOutputStream;

/**
 * Extension of {@link GZIPOutputStream} to workaround for a couple of long
 * standing JDK bugs
 * (<a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4255743">Bug
 * 4255743</a> and
 * <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4813885">Bug
 * 4813885</a>) so the GZIP'd output can be flushed.
 */
public class FlushableGZIPOutputStream extends GZIPOutputStream {
    public FlushableGZIPOutputStream(OutputStream os) throws IOException {
        super(os);
    }

    private static final byte[] EMPTYBYTEARRAY = new byte[0];
    private boolean hasData = false;

    /**
     * Here we make sure we have received data, so that the header has been for
     * sure written to the output stream already.
     */
    @Override
    public synchronized void write(byte[] bytes, int i, int i1)
            throws IOException {
        super.write(bytes, i, i1);
        hasData = true;
    }

    @Override
    public synchronized void write(int i) throws IOException {
        super.write(i);
        hasData = true;
    }

    @Override
    public synchronized void write(byte[] bytes) throws IOException {
        super.write(bytes);
        hasData = true;
    }

    @Override
    public synchronized void flush() throws IOException {
        if (!hasData) {
            return; // do not allow the gzip header to be flushed on its own
        }

        // trick the deflater to flush
        /**
         * Now this is tricky: We force the Deflater to flush its data by
         * switching compression level. As yet, a perplexingly simple workaround
         * for
         * http://developer.java.sun.com/developer/bugParade/bugs/4255743.html
         */
        if (!def.finished()) {
            def.setInput(EMPTYBYTEARRAY, 0, 0);

            def.setLevel(Deflater.NO_COMPRESSION);
            deflate();

            def.setLevel(Deflater.DEFAULT_COMPRESSION);
            deflate();

            out.flush();
        }

        hasData = false; // no more data to flush
    }

    /*
     * Keep on calling deflate until it runs dry. The default implementation
     * only does it once and can therefore hold onto data when they need to be
     * flushed out.
     */
    @Override
    protected void deflate() throws IOException {
        int len;
        do {
            len = def.deflate(buf, 0, buf.length);
            if (len > 0) {
                out.write(buf, 0, len);
            }
        } while (len != 0);
    }

}

@@ -1574,16 +1574,69 @@
 = false;
+%0A    private boolean needReenableCompression = false;
 %0A%0A    /*
@@ -1846,17 +1846,26 @@
 int 
-i, int i1
+offset, int length
 )%0A  
@@ -1907,58 +1907,115 @@
     
-super.write(bytes, i, i1);%0A        hasData = true;
+if (length %3E 0) %7B%0A            prepareForOutput();%0A            super.write(bytes, offset, length);%0A        %7D
 %0A   
@@ -2107,46 +2107,50 @@
     
-super.write(i);%0A        hasData = true
+prepareForOutput();%0A        super.write(i)
 ;%0A  
@@ -2238,38 +2238,32 @@
 ption %7B%0A        
-super.
 write(bytes);%0A  
@@ -2261,34 +2261,288 @@
 ytes
-);%0A        hasData = true;
+, 0, bytes.length);%0A    %7D%0A%0A    private void prepareForOutput() throws IOException %7B%0A        hasData = true;%0A        if (needReenableCompression) %7B%0A            def.setLevel(Deflater.DEFAULT_COMPRESSION);%0A            deflate();%0A            needReenableCompression = false;%0A        %7D
 %0A   
@@ -3212,73 +3212,106 @@
     
-def.setLevel(Deflater.DEFAULT_COMPRESSION);%0A            deflate()
+// Cannot reenable compression now. Must wait for data.%0A            needReenableCompression = true
 ;%0A%0A 

Fix https://issues.apache.org/bugzilla/show_bug.cgi?id=52121 FlushableGZIPOutputStream produces corrupted output on some input sequences The fix is to do not restore compression level right before the end of the stream. Restore it only before writing the next portion of data.