1 /*
2  * Copyright (C) 2014 Square, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16 package okio;
17
18 import java.io.EOFException;
19 import java.io.IOException;
20 import java.io.InputStream;
21 import java.nio.ByteBuffer;
22 import java.nio.charset.Charset;
23 import javax.annotation.Nullable;
24
25 import static okio.Util.checkOffsetAndCount;
26
27 final class RealBufferedSource implements BufferedSource {
28   public final Buffer buffer = new Buffer();
29   public final Source source;
30   boolean closed;
31
32   RealBufferedSource(Source source) {
33     if (source == nullthrow new NullPointerException("source == null");
34     this.source = source;
35   }
36
37   @Override public Buffer buffer() {
38     return buffer;
39   }
40
41   @Override public Buffer getBuffer() {
42     return buffer;
43   }
44
45   @Override public long read(Buffer sink, long byteCount) throws IOException {
46     if (sink == nullthrow new IllegalArgumentException("sink == null");
47     if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
48     if (closed) throw new IllegalStateException("closed");
49
50     if (buffer.size == 0) {
51       long read = source.read(buffer, Segment.SIZE);
52       if (read == -1) return -1;
53     }
54
55     long toRead = Math.min(byteCount, buffer.size);
56     return buffer.read(sink, toRead);
57   }
58
59   @Override public boolean exhausted() throws IOException {
60     if (closed) throw new IllegalStateException("closed");
61     return buffer.exhausted() && source.read(buffer, Segment.SIZE) == -1;
62   }
63
64   @Override public void require(long byteCount) throws IOException {
65     if (!request(byteCount)) throw new EOFException();
66   }
67
68   @Override public boolean request(long byteCount) throws IOException {
69     if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
70     if (closed) throw new IllegalStateException("closed");
71     while (buffer.size < byteCount) {
72       if (source.read(buffer, Segment.SIZE) == -1) return false;
73     }
74     return true;
75   }
76
77   @Override public byte readByte() throws IOException {
78     require(1);
79     return buffer.readByte();
80   }
81
82   @Override public ByteString readByteString() throws IOException {
83     buffer.writeAll(source);
84     return buffer.readByteString();
85   }
86
87   @Override public ByteString readByteString(long byteCount) throws IOException {
88     require(byteCount);
89     return buffer.readByteString(byteCount);
90   }
91
92   @Override public int select(Options options) throws IOException {
93     if (closed) throw new IllegalStateException("closed");
94
95     while (true) {
96       int index = buffer.selectPrefix(options, true);
97       if (index == -1) return -1;
98       if (index == -2) {
99         // We need to grow the buffer. Do that, then try it all again.
100         if (source.read(buffer, Segment.SIZE) == -1L) return -1;
101       } else {
102         // We matched a full byte string: consume it and return it.
103         int selectedSize = options.byteStrings[index].size();
104         buffer.skip(selectedSize);
105         return index;
106       }
107     }
108   }
109
110   @Override public byte[] readByteArray() throws IOException {
111     buffer.writeAll(source);
112     return buffer.readByteArray();
113   }
114
115   @Override public byte[] readByteArray(long byteCount) throws IOException {
116     require(byteCount);
117     return buffer.readByteArray(byteCount);
118   }
119
120   @Override public int read(byte[] sink) throws IOException {
121     return read(sink, 0, sink.length);
122   }
123
124   @Override public void readFully(byte[] sink) throws IOException {
125     try {
126       require(sink.length);
127     } catch (EOFException e) {
128       // The underlying source is exhausted. Copy the bytes we got before rethrowing.
129       int offset = 0;
130       while (buffer.size > 0) {
131         int read = buffer.read(sink, offset, (int) buffer.size);
132         if (read == -1) throw new AssertionError();
133         offset += read;
134       }
135       throw e;
136     }
137     buffer.readFully(sink);
138   }
139
140   @Override public int read(byte[] sink, int offset, int byteCount) throws IOException {
141     checkOffsetAndCount(sink.length, offset, byteCount);
142
143     if (buffer.size == 0) {
144       long read = source.read(buffer, Segment.SIZE);
145       if (read == -1) return -1;
146     }
147
148     int toRead = (int) Math.min(byteCount, buffer.size);
149     return buffer.read(sink, offset, toRead);
150   }
151
152   @Override public int read(ByteBuffer sink) throws IOException {
153     if (buffer.size == 0) {
154       long read = source.read(buffer, Segment.SIZE);
155       if (read == -1) return -1;
156     }
157
158     return buffer.read(sink);
159   }
160
161   @Override public void readFully(Buffer sink, long byteCount) throws IOException {
162     try {
163       require(byteCount);
164     } catch (EOFException e) {
165       // The underlying source is exhausted. Copy the bytes we got before rethrowing.
166       sink.writeAll(buffer);
167       throw e;
168     }
169     buffer.readFully(sink, byteCount);
170   }
171
172   @Override public long readAll(Sink sink) throws IOException {
173     if (sink == nullthrow new IllegalArgumentException("sink == null");
174
175     long totalBytesWritten = 0;
176     while (source.read(buffer, Segment.SIZE) != -1) {
177       long emitByteCount = buffer.completeSegmentByteCount();
178       if (emitByteCount > 0) {
179         totalBytesWritten += emitByteCount;
180         sink.write(buffer, emitByteCount);
181       }
182     }
183     if (buffer.size() > 0) {
184       totalBytesWritten += buffer.size();
185       sink.write(buffer, buffer.size());
186     }
187     return totalBytesWritten;
188   }
189
190   @Override public String readUtf8() throws IOException {
191     buffer.writeAll(source);
192     return buffer.readUtf8();
193   }
194
195   @Override public String readUtf8(long byteCount) throws IOException {
196     require(byteCount);
197     return buffer.readUtf8(byteCount);
198   }
199
200   @Override public String readString(Charset charset) throws IOException {
201     if (charset == nullthrow new IllegalArgumentException("charset == null");
202
203     buffer.writeAll(source);
204     return buffer.readString(charset);
205   }
206
207   @Override public String readString(long byteCount, Charset charset) throws IOException {
208     require(byteCount);
209     if (charset == nullthrow new IllegalArgumentException("charset == null");
210     return buffer.readString(byteCount, charset);
211   }
212
213   @Override public @Nullable String readUtf8Line() throws IOException {
214     long newline = indexOf((byte) '\n');
215
216     if (newline == -1) {
217       return buffer.size != 0 ? readUtf8(buffer.size) : null;
218     }
219
220     return buffer.readUtf8Line(newline);
221   }
222
223   @Override public String readUtf8LineStrict() throws IOException {
224     return readUtf8LineStrict(Long.MAX_VALUE);
225   }
226
227   @Override public String readUtf8LineStrict(long limit) throws IOException {
228     if (limit < 0) throw new IllegalArgumentException("limit < 0: " + limit);
229     long scanLength = limit == Long.MAX_VALUE ? Long.MAX_VALUE : limit + 1;
230     long newline = indexOf((byte) '\n', 0, scanLength);
231     if (newline != -1) return buffer.readUtf8Line(newline);
232     if (scanLength < Long.MAX_VALUE
233         && request(scanLength) && buffer.getByte(scanLength - 1) == '\r'
234         && request(scanLength + 1) && buffer.getByte(scanLength) == '\n') {
235       return buffer.readUtf8Line(scanLength); // The line was 'limit' UTF-8 bytes followed by \r\n.
236     }
237     Buffer data = new Buffer();
238     buffer.copyTo(data, 0, Math.min(32, buffer.size()));
239     throw new EOFException("\\n not found: limit=" + Math.min(buffer.size(), limit)
240         + " content=" + data.readByteString().hex() + '…');
241   }
242
243   @Override public int readUtf8CodePoint() throws IOException {
244     require(1);
245
246     byte b0 = buffer.getByte(0);
247     if ((b0 & 0xe0) == 0xc0) {
248       require(2);
249     } else if ((b0 & 0xf0) == 0xe0) {
250       require(3);
251     } else if ((b0 & 0xf8) == 0xf0) {
252       require(4);
253     }
254
255     return buffer.readUtf8CodePoint();
256   }
257
258   @Override public short readShort() throws IOException {
259     require(2);
260     return buffer.readShort();
261   }
262
263   @Override public short readShortLe() throws IOException {
264     require(2);
265     return buffer.readShortLe();
266   }
267
268   @Override public int readInt() throws IOException {
269     require(4);
270     return buffer.readInt();
271   }
272
273   @Override public int readIntLe() throws IOException {
274     require(4);
275     return buffer.readIntLe();
276   }
277
278   @Override public long readLong() throws IOException {
279     require(8);
280     return buffer.readLong();
281   }
282
283   @Override public long readLongLe() throws IOException {
284     require(8);
285     return buffer.readLongLe();
286   }
287
288   @Override public long readDecimalLong() throws IOException {
289     require(1);
290
291     for (int pos = 0; request(pos + 1); pos++) {
292       byte b = buffer.getByte(pos);
293       if ((b < '0' || b > '9') && (pos != 0 || b != '-')) {
294         // Non-digit, or non-leading negative sign.
295         if (pos == 0) {
296           throw new NumberFormatException(String.format(
297               "Expected leading [0-9] or '-' character but was %#x", b));
298         }
299         break;
300       }
301     }
302
303     return buffer.readDecimalLong();
304   }
305
306   @Override public long readHexadecimalUnsignedLong() throws IOException {
307     require(1);
308
309     for (int pos = 0; request(pos + 1); pos++) {
310       byte b = buffer.getByte(pos);
311       if ((b < '0' || b > '9') && (b < 'a' || b > 'f') && (b < 'A' || b > 'F')) {
312         // Non-digit, or non-leading negative sign.
313         if (pos == 0) {
314           throw new NumberFormatException(String.format(
315               "Expected leading [0-9a-fA-F] character but was %#x", b));
316         }
317         break;
318       }
319     }
320
321     return buffer.readHexadecimalUnsignedLong();
322   }
323
324   @Override public void skip(long byteCount) throws IOException {
325     if (closed) throw new IllegalStateException("closed");
326     while (byteCount > 0) {
327       if (buffer.size == 0 && source.read(buffer, Segment.SIZE) == -1) {
328         throw new EOFException();
329       }
330       long toSkip = Math.min(byteCount, buffer.size());
331       buffer.skip(toSkip);
332       byteCount -= toSkip;
333     }
334   }
335
336   @Override public long indexOf(byte b) throws IOException {
337     return indexOf(b, 0, Long.MAX_VALUE);
338   }
339
340   @Override public long indexOf(byte b, long fromIndex) throws IOException {
341     return indexOf(b, fromIndex, Long.MAX_VALUE);
342   }
343
344   @Override public long indexOf(byte b, long fromIndex, long toIndex) throws IOException {
345     if (closed) throw new IllegalStateException("closed");
346     if (fromIndex < 0 || toIndex < fromIndex) {
347       throw new IllegalArgumentException(
348           String.format("fromIndex=%s toIndex=%s", fromIndex, toIndex));
349     }
350
351     while (fromIndex < toIndex) {
352       long result = buffer.indexOf(b, fromIndex, toIndex);
353       if (result != -1L) return result;
354
355       // The byte wasn't in the buffer. Give up if we've already reached our target size or if the
356       // underlying stream is exhausted.
357       long lastBufferSize = buffer.size;
358       if (lastBufferSize >= toIndex || source.read(buffer, Segment.SIZE) == -1) return -1L;
359
360       // Continue the search from where we left off.
361       fromIndex = Math.max(fromIndex, lastBufferSize);
362     }
363     return -1L;
364   }
365
366   @Override public long indexOf(ByteString bytes) throws IOException {
367     return indexOf(bytes, 0);
368   }
369
370   @Override public long indexOf(ByteString bytes, long fromIndex) throws IOException {
371     if (closed) throw new IllegalStateException("closed");
372
373     while (true) {
374       long result = buffer.indexOf(bytes, fromIndex);
375       if (result != -1) return result;
376
377       long lastBufferSize = buffer.size;
378       if (source.read(buffer, Segment.SIZE) == -1) return -1L;
379
380       // Keep searching, picking up from where we left off.
381       fromIndex = Math.max(fromIndex, lastBufferSize - bytes.size() + 1);
382     }
383   }
384
385   @Override public long indexOfElement(ByteString targetBytes) throws IOException {
386     return indexOfElement(targetBytes, 0);
387   }
388
389   @Override public long indexOfElement(ByteString targetBytes, long fromIndex) throws IOException {
390     if (closed) throw new IllegalStateException("closed");
391
392     while (true) {
393       long result = buffer.indexOfElement(targetBytes, fromIndex);
394       if (result != -1) return result;
395
396       long lastBufferSize = buffer.size;
397       if (source.read(buffer, Segment.SIZE) == -1) return -1L;
398
399       // Keep searching, picking up from where we left off.
400       fromIndex = Math.max(fromIndex, lastBufferSize);
401     }
402   }
403
404   @Override public boolean rangeEquals(long offset, ByteString bytes) throws IOException {
405     return rangeEquals(offset, bytes, 0, bytes.size());
406   }
407
408   @Override
409   public boolean rangeEquals(long offset, ByteString bytes, int bytesOffset, int byteCount)
410       throws IOException {
411     if (closed) throw new IllegalStateException("closed");
412
413     if (offset < 0
414         || bytesOffset < 0
415         || byteCount < 0
416         || bytes.size() - bytesOffset < byteCount) {
417       return false;
418     }
419     for (int i = 0; i < byteCount; i++) {
420       long bufferOffset = offset + i;
421       if (!request(bufferOffset + 1)) return false;
422       if (buffer.getByte(bufferOffset) != bytes.getByte(bytesOffset + i)) return false;
423     }
424     return true;
425   }
426
427   @Override public BufferedSource peek() {
428     return Okio.buffer(new PeekSource(this));
429   }
430
431   @Override public InputStream inputStream() {
432     return new InputStream() {
433       @Override public int read() throws IOException {
434         if (closed) throw new IOException("closed");
435         if (buffer.size == 0) {
436           long count = source.read(buffer, Segment.SIZE);
437           if (count == -1) return -1;
438         }
439         return buffer.readByte() & 0xff;
440       }
441
442       @Override public int read(byte[] data, int offset, int byteCount) throws IOException {
443         if (closed) throw new IOException("closed");
444         checkOffsetAndCount(data.length, offset, byteCount);
445
446         if (buffer.size == 0) {
447           long count = source.read(buffer, Segment.SIZE);
448           if (count == -1) return -1;
449         }
450
451         return buffer.read(data, offset, byteCount);
452       }
453
454       @Override public int available() throws IOException {
455         if (closed) throw new IOException("closed");
456         return (int) Math.min(buffer.size, Integer.MAX_VALUE);
457       }
458
459       @Override public void close() throws IOException {
460         RealBufferedSource.this.close();
461       }
462
463       @Override public String toString() {
464         return RealBufferedSource.this + ".inputStream()";
465       }
466     };
467   }
468
469   @Override public boolean isOpen() {
470     return !closed;
471   }
472
473   @Override public void close() throws IOException {
474     if (closed) return;
475     closed = true;
476     source.close();
477     buffer.clear();
478   }
479
480   @Override public Timeout timeout() {
481     return source.timeout();
482   }
483
484   @Override public String toString() {
485     return "buffer(" + source + ")";
486   }
487 }
488