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