Home | History | Annotate | Download | only in spdy
      1 package com.squareup.okhttp.internal.spdy;
      2 
      3 import java.io.IOException;
      4 import java.util.ArrayList;
      5 import java.util.List;
      6 import java.util.zip.DataFormatException;
      7 import java.util.zip.Inflater;
      8 import okio.BufferedSource;
      9 import okio.ByteString;
     10 import okio.Deadline;
     11 import okio.InflaterSource;
     12 import okio.OkBuffer;
     13 import okio.Okio;
     14 import okio.Source;
     15 
     16 /**
     17  * Reads a SPDY/3 Name/Value header block. This class is made complicated by the
     18  * requirement that we're strict with which bytes we put in the compressed bytes
     19  * buffer. We need to put all compressed bytes into that buffer -- but no other
     20  * bytes.
     21  */
     22 class NameValueBlockReader {
     23   /** This source transforms compressed bytes into uncompressed bytes. */
     24   private final InflaterSource inflaterSource;
     25 
     26   /**
     27    * How many compressed bytes must be read into inflaterSource before
     28    * {@link #readNameValueBlock} returns.
     29    */
     30   private int compressedLimit;
     31 
     32   /** This source holds inflated bytes. */
     33   private final BufferedSource source;
     34 
     35   public NameValueBlockReader(final BufferedSource source) {
     36     // Limit the inflater input stream to only those bytes in the Name/Value
     37     // block. We cut the inflater off at its source because we can't predict the
     38     // ratio of compressed bytes to uncompressed bytes.
     39     Source throttleSource = new Source() {
     40       @Override public long read(OkBuffer sink, long byteCount)
     41           throws IOException {
     42         if (compressedLimit == 0) return -1; // Out of data for the current block.
     43         long read = source.read(sink, Math.min(byteCount, compressedLimit));
     44         if (read == -1) return -1;
     45         compressedLimit -= read;
     46         return read;
     47       }
     48 
     49       @Override public void close() throws IOException {
     50         source.close();
     51       }
     52 
     53       @Override public Source deadline(Deadline deadline) {
     54         source.deadline(deadline);
     55         return this;
     56       }
     57     };
     58 
     59     // Subclass inflater to install a dictionary when it's needed.
     60     Inflater inflater = new Inflater() {
     61       @Override public int inflate(byte[] buffer, int offset, int count)
     62           throws DataFormatException {
     63         int result = super.inflate(buffer, offset, count);
     64         if (result == 0 && needsDictionary()) {
     65           setDictionary(Spdy3.DICTIONARY);
     66           result = super.inflate(buffer, offset, count);
     67         }
     68         return result;
     69       }
     70     };
     71 
     72     this.inflaterSource = new InflaterSource(throttleSource, inflater);
     73     this.source = Okio.buffer(inflaterSource);
     74   }
     75 
     76   public List<Header> readNameValueBlock(int length) throws IOException {
     77     this.compressedLimit += length;
     78 
     79     int numberOfPairs = source.readInt();
     80     if (numberOfPairs < 0) throw new IOException("numberOfPairs < 0: " + numberOfPairs);
     81     if (numberOfPairs > 1024) throw new IOException("numberOfPairs > 1024: " + numberOfPairs);
     82 
     83     List<Header> entries = new ArrayList<Header>(numberOfPairs);
     84     for (int i = 0; i < numberOfPairs; i++) {
     85       ByteString name = readByteString().toAsciiLowercase();
     86       ByteString values = readByteString();
     87       if (name.size() == 0) throw new IOException("name.size == 0");
     88       entries.add(new Header(name, values));
     89     }
     90 
     91     doneReading();
     92     return entries;
     93   }
     94 
     95   private ByteString readByteString() throws IOException {
     96     int length = source.readInt();
     97     return source.readByteString(length);
     98   }
     99 
    100   private void doneReading() throws IOException {
    101     // Move any outstanding unread bytes into the inflater. One side-effect of
    102     // deflate compression is that sometimes there are bytes remaining in the
    103     // stream after we've consumed all of the content.
    104     if (compressedLimit > 0) {
    105       inflaterSource.refill();
    106       if (compressedLimit != 0) throw new IOException("compressedLimit > 0: " + compressedLimit);
    107     }
    108   }
    109 
    110   public void close() throws IOException {
    111     source.close();
    112   }
    113 }
    114