JDK-6588269 : cache: Applet Cache delivers wrong data
  • Type: Bug
  • Component: deploy
  • Sub-Component: plugin
  • Affected Version: 6
  • Priority: P4
  • Status: Closed
  • Resolution: Duplicate
  • OS: windows_xp
  • CPU: x86
  • Submitted: 2007-08-02
  • Updated: 2011-08-18
  • Resolved: 2011-08-18
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 7
7u2Resolved
Related Reports
Duplicate :  
Description
FULL PRODUCT VERSION :
Java Plug-in 1.6.0_01

Verwendung der JRE-Version 1.6.0_01 Java HotSpot(TM) Client VM

ADDITIONAL OS VERSION INFORMATION :
Microsoft Windows XP [Version 5.1.2600]

A DESCRIPTION OF THE PROBLEM :
Items that are requested with "Accept-Encoding: gzip" are delivered correctly the first time they are requested.
On subsequent requests, if these items are delivered from the Plugin Cache, the reponse still contains the HTTP header "Content-Encoding:gzip" and "Content-Length: [gzipped-content-length]", but the content itself then is already decompressed, so that if the client tries to read the content assuming content-length of gzipped-content-length and regarding the content as gzipped, it can neither read the full content (because the content length is not correct) nor apply any gzip depcompression (because the content is already decompressed)




STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
0.) extract the following files from the here provided sources (use markers to cut):

SimpleServer.java
PluginCacheTest.java
plugin_cache_test.html

1.) Compile the provided source files

SimpleServer.java and
PluginCacheTest.java

2. place the resulting file PluginCacheTest.class in a directory {user.home}

3.) start the Server (assumes that SimpleServer.class is in the classpath):
java SimpleServer

4.) activate and clean the Plugin Cache

5.) Specify "your_ip" in the file plugin_cache_test.html

6.) load the file plugin_cache_test.html in a browser that uses the specified java plugin (1.6)



EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
I expect that both requests for the resource /gzipped.txt have valid results. The message
"Could not create GZIPInputStream, using normal InputStream: Not in GZIP format" should not occur for the second response.
The second response should
__EITHER__
- be in gzip format (and so deliver Content-Encoding:gzip and Content-Length: gzip-contentlength) (which nevertheless would not be correct, since the client requested this without any Accept-Encoding header)
__OR__
- be uncompressed and
- deliver no Content-Encoding Header
- deliver the value of uncompressed content length as value of the HTTP Header Content-Length

ACTUAL -
see attachment





ERROR MESSAGES/STACK TRACES THAT OCCUR :
Not in GZIP format

is the message from the Exception that is thrown when a GZIPInputStream is created.

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
######################################### SimpleServer.java #########
import java.io.*;
import java.nio.channels.*;
import java.net.*;
import java.util.*;
import java.util.zip.GZIPOutputStream;
import java.text.*;


/**
 * Very minimalistic HTTP Server which servers 3 requests
 *
 * a) the zeroexpires.txt which contains an "Expires:0" response header
 * b) the gzipped.txt which can be delivered gzipped/not gzipped
 * c) the test Applet
 *
 * Note that this Program assumes the Applet's class file (PluginCacheTest.class)
 * in $user.home/Java/Prgs.
 *
 * @author Jan Kaiser <jan dot kaiser at interactivedata dot com>
 */
public class SimpleServer
{
  static int buf_current;
  static int buf_header_end;
  static byte[] buf = new byte[4096];
  static SimpleDateFormat expiresFormatter =
      new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z");
  
  public static void main(String[] args)
  {
    expiresFormatter.setTimeZone(TimeZone.getTimeZone("GMT"));
    try
    {
      ServerSocket mySocket = new ServerSocket(8080);
      while(true)
      {
        buf_current = 0;
        buf_header_end = 0;
        buf = new byte[4096];

        Socket clientSocket = mySocket.accept();
        System.out.println("New connection accepted " +
            clientSocket.getInetAddress() + ":" + clientSocket.getPort());
        OutputStream out = clientSocket.getOutputStream();
        InputStream in = clientSocket.getInputStream();
        while ((buf_header_end = findEndOfHeader(buf, 0, buf_current)) < 0)
        {
          buf_current += do_read(buf, buf_current, (buf.length - buf_current) - 1, in);
          if (buf_current == buf.length)
          {
            throw new IOException("Header too long");
          }
        }
        String header = new String(buf, 0, buf_header_end);
        StringTokenizer tokens = new StringTokenizer(header,"\r\n");
        String query = null;
        Hashtable headers = new Hashtable();
        while (tokens.hasMoreTokens())
        {
          String headerToken = tokens.nextToken();
          int index = -1;
          if ((index = headerToken.indexOf(":")) > -1)
          {
            String name = headerToken.substring(0,index);
            String value = headerToken.substring(index+1,headerToken.length());
            value = value.trim();
            System.out.println("header:" + name + ":" + value + " from " + headerToken);
            headers.put(name, value);
          }
          else // request
          {
            System.out.println("Query: " + headerToken);
            StringTokenizer queryTokens = new StringTokenizer(headerToken, " ");
            while (queryTokens.hasMoreTokens())
            {
              String queryToken = queryTokens.nextToken();
              if (queryToken.startsWith("/"))
              {
                query = queryToken;
                break;
              }
            }
          }
        }
        if (query == null)
        {
          throw new IOException("No query available");
        }
        if (headers.size() == 0)
        {
          throw new IOException("No Headers");
        }
        // read the request body if there is some
        if (headers.containsKey("ContentLength"))
        {
          int queryContentLength = Integer.parseInt((String)headers.get("Content-Length"));
          readBody(queryContentLength, in);
        }
        String output = null;

        // delivery of the gzipped content
        if (query.equals("/gzipped.txt"))
        {
          char[] chars = new char[3000];
          for (int i = 0 ; i < chars.length ; i++)
          {
            chars[i] = (char)(i % 10 == 0 ? '0' : '1');
          }
          String content = String.valueOf(Math.random()) + "\n" + new String(chars);
          System.out.println("chars is " + new String(chars));
          String expires = expiresFormatter.format(new Date(System.currentTimeMillis() + 86000000));
          int contentLength = -1;
          byte[] byteContent = null;
          String contentEncoding = null;
          String acceptEncoding = "identity";
          if (headers.containsKey("Accept-Encoding"))
          {
            acceptEncoding = (String)headers.get("Accept-Encoding");
          }
          if (acceptEncoding.equals("gzip"))
          {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            GZIPOutputStream gzOut = new GZIPOutputStream(baos);
            gzOut.write(content.getBytes());
            gzOut.flush();
            gzOut.close();
            baos.flush();
            byteContent = baos.toByteArray();
            baos.close();
            contentEncoding = "gzip";
          }
          else if (acceptEncoding.equals("identity"))
          {
            byteContent = content.getBytes();
            contentEncoding = "identity";
          }
          else
          {
            throw new IOException("not supported Accept-Encoding: [" + acceptEncoding + "]");
          }
          System.out.println("bytecontentlength:" + byteContent.length);
          contentLength = byteContent.length;
          output= "HTTP/1.0 200 OK\r\n" +
                       "Content-Length: "+ contentLength +"\r\n" +
                       "Expires: " + expires + "\r\n" +
                       "Content-Encoding: " + contentEncoding + "\r\n" +
                       "Content-Type: text/plain\r\n" +
                       "\r\n";
          System.out.println("[" + output + "]");
          out.write(output.getBytes());
          out.write(byteContent);
        }
        // delivery of the Applet itself
        else if (query.indexOf("/PluginCacheTest") != -1)
        {
          
          FileInputStream fileStream = new FileInputStream(System.getProperty("user.home") + "/Java/Prgs/PluginCacheTest.class");
          FileChannel channel = fileStream.getChannel();
          long size = channel.size();
          output = "HTTP/1.0 200 OK \r\n" +
                   "Content-Type: application/octet-stream\r\n" +
                   "Content-Length: " + size + "\r\n" +
                   "\r\n";
          out.write(output.getBytes());
          int read = -1;
          while ((read = fileStream.read()) != -1)
          {
            try
            {
              out.write(read);
            }
            catch(Exception ex)
            {
              System.out.println("Caught exception when trying to write on out");
            }
          }
        }
        else
        {
          String content = query + " Not Found";
          output = "HTTP/1.0 404 Not Found\r\n" +
                    "Content-Length: " + content.length() + "\r\n" +
                    "\r\n" +
                    content;
          out.write(output.getBytes());
        }
        out.flush();
        out.close();
      }
    }
    catch(Exception ex)
    {
      System.out.println("Exception in main " + ex.getMessage());
      ex.printStackTrace();
    }
  }
  
  static int do_read(byte[] buf, int start, int length, InputStream input) throws IOException
  {
    int have_read = -1;
    try
    {
      have_read = input.read(buf, start, length);
    }
    catch (InterruptedIOException ex)
    {
      throw new IOException("ReadTimeout");
    }
    if (have_read < 0)
    {
      throw new IOException("ServerSideClose");
    }
    else if (have_read == 0)
    {
      throw new IOException("ZeroBytesRead");
    }
    return have_read;
  }
  
  static byte[] do_read(byte[] buf, int current_body_length, InputStream i) throws IOException
  {
    int have_read = -1;
    byte[] ret;
    try
    {
      while (true)
      {
        have_read = i.read(buf, current_body_length, buf.length-current_body_length);
        if (have_read >= 0)
        {
          if (have_read == (buf.length-current_body_length))
          {
            byte[] newBuf = new byte[2*buf.length];
            System.arraycopy(buf, 0, newBuf, 0, buf.length);
            buf = newBuf;
          }
          current_body_length += have_read;
        }
        else
        {
          break;
        }
      }
      ret = new byte[current_body_length];
      System.arraycopy(buf, 0, ret, 0, current_body_length);
    }
    catch (InterruptedIOException ex)
    {
      throw new IOException("ReadTimeout");
    }
    return ret;
  }
  
  static int findEndOfHeader(byte[] buf, int start, int length)
  {
    if (length < 2)
    {
      return -1;
    }
    int index = -1;
    if ((index = new String(buf, start, length).indexOf("\r\n\r\n")) > -1)
    {
      return index;
    }
    return -1;
  }
  
  static byte[] readBody(int contentLength, InputStream input) throws IOException
  {
    byte[] body = new byte[contentLength];
    int current_body_length = 0;
    if (buf_header_end < buf_current) // read more than header
    {
      current_body_length = buf_current - buf_header_end;
      if (current_body_length > contentLength)
      {
        current_body_length = contentLength;
        System.arraycopy(buf, buf_header_end, body, 0, current_body_length); // copy current_body_length
        int need_to_shift = buf_current - (buf_header_end + current_body_length);
        System.arraycopy(buf, buf_current - need_to_shift, buf, 0, need_to_shift);
        buf_current = need_to_shift;
      }
      else
      {
        System.arraycopy(buf, buf_header_end, body, 0, current_body_length);
        buf_current = 0;
      }
    }
    else
    {
      buf_current = 0;
    }
    buf_header_end = -1;
    return getBody(current_body_length, contentLength, body, input);
  }
  
  static byte[] getBody(int current_body_length, int contentLength, byte[] body, InputStream input)
  throws IOException
  {
    while (current_body_length < contentLength) // check wheter there is more to read
    {
      current_body_length += do_read(body, current_body_length, contentLength - current_body_length, input);
    }
    return body;
  }
}


############################## PluginCacheTest.java ########################


import java.applet.*;
import java.net.*;
import java.io.*;
import java.util.*;
import java.util.zip.*;

/**
 * This applet reproduces the following behaviour of the plugin cache in java 6:
 * - items that are delivered with the HTTP headers "Expires: 0" and "Last-Modified: [now]" are written in the cache and delivered
 *   from the cache on subsequent requests for the items
 * - items that are requested with "Accept-Encoding: gzip" are delivered correctly the first time they are requested.
 *   On subsequent requests, these items are delivered from the Plugin Cache with the header "Content-Encoding:gzip" and
 *   "Content-Length: [gzipped-content-length]", but with decompressed content, so that the client tries to read the
 *   content having a length of gzipped-content-length and regards the content as gzipped, but can neither read the full content
 *   (because the content length is not correct) nor apply any gzip depcompression (because the content is not gzipped)
 *
 * This applet assumes the SimpleServer program running on its codebase.
 *
 * @author Jan Kaiser <jan dot kaiser at interactivedata dot com>
 */
public class PluginCacheTest extends Applet
{
  public void start()
  {
    try
    {
      String url = getCodeBase() + "gzipped.txt";
      Hashtable headers = new Hashtable();
      headers.put("Accept-Encoding", "gzip");
      System.out.println("Content of the initial \"Accept-Encoding: gzip\" request");
      getContent(url, headers);
      System.out.println("Content of the subsequent (w/o \"Accept-Encoding\") request for the same resource");
      getContent(url, null);
    }
    catch(Exception ex)
    {
      reportException(ex);
    }
  }

  private void getContent(String url, Hashtable headers) throws Exception
  {
    URL u = new URL(url);
    URLConnection conn = u.openConnection();
    if (headers != null)
    {
      Enumeration keys = headers.keys();
      while (keys.hasMoreElements())
      {
        String key = (String)keys.nextElement();
        String value = (String)headers.get(key);
        conn.setRequestProperty(key, value);
      }
    }
    InputStream is = conn.getInputStream();
    boolean gzip = false;
    String encoding = conn.getContentEncoding();
    int contentLength = conn.getContentLength();
    String expires = conn.getHeaderField("Expires");
    long expiration = conn.getExpiration();
    System.out.println("encoding:"+encoding+", contentLength:"+contentLength+", expires:" + expires+"("+expiration+")");
    if (encoding != null && encoding.equals("gzip"))
    {
      gzip = true;
    }
    readStream(is, gzip);
  }

  private void readStream(InputStream is, boolean gzip) throws IOException
  {
    InputStream input = null;
    if (gzip)
    {
      try
      {
        input = new GZIPInputStream(is);
      }
      catch(Exception ex)
      {
        System.out.println("Could not create GZIPInputStream, using normal InputStream: " + ex.getMessage());
        input = is;
      }
    }
    else
    {
      input = is;
    }
    int c = -1;
    while ((c = input.read()) != -1)
    {
      System.out.print((char)c);
    }
    System.out.print("\n");
    input.close();
  }

  private void reportException(Exception ex)
  {
    System.out.println("caught exception");
    ex.printStackTrace();
  }
}



####################### plugin_cache_test.html ############


<html>

<head>

<title>Test Java 6 Plugin Cache Misbehaviour</title>

</head>

<body>

<applet code="PluginCacheTest" codebase="http://your_ip:8080/"></applet>

</body>

</html>


---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
1) Disable the Plugin Cache
or
2) send the HTTP headers
"Pragma: no-cache"  and
"Cache-Control: no-cache"
with the request, which unfortunately might disable caching even on futher intermediate caches (e.g. proxy servers).