private void readPriority(Handler handler, int streamId) throws IOException { int w1 = source.readInt(); boolean exclusive = (w1 & 0x80000000) != 0; int streamDependency = (w1 & 0x7fffffff); int weight = (source.readByte() & 0xff) + 1; handler.priority(streamId, streamDependency, weight, exclusive); }
/** * Eagerly reads {@code byteCount} bytes from the source before launching a background task to * process the data. This avoids corrupting the stream. */ private void pushDataLater( final int streamId, final BufferedSource source, final int byteCount, final boolean inFinished) throws IOException { final Buffer buffer = new Buffer(); source.require(byteCount); // Eagerly read the frame before firing client thread. source.read(buffer, byteCount); if (buffer.size() != byteCount) throw new IOException(buffer.size() + " != " + byteCount); pushExecutor.execute( new NamedRunnable("OkHttp %s Push Data[%s]", hostname, streamId) { @Override public void execute() { try { boolean cancel = pushObserver.onData(streamId, buffer, byteCount, inFinished); if (cancel) frameWriter.rstStream(streamId, ErrorCode.CANCEL); if (cancel || inFinished) { synchronized (FramedConnection.this) { currentPushRequests.remove(streamId); } } } catch (IOException ignored) { } } }); }
private void readContinuationHeader() throws IOException { int previousStreamId = streamId; length = left = readMedium(source); byte type = (byte) (source.readByte() & 0xff); flags = (byte) (source.readByte() & 0xff); if (logger.isLoggable(FINE)) logger.fine(formatHeader(true, streamId, length, type, flags)); streamId = (source.readInt() & 0x7fffffff); if (type != TYPE_CONTINUATION) throw ioException("%s != TYPE_CONTINUATION", type); if (streamId != previousStreamId) throw ioException("TYPE_CONTINUATION streamId changed"); }
private void readPushPromise(Handler handler, int length, byte flags, int streamId) throws IOException { if (streamId == 0) { throw ioException("PROTOCOL_ERROR: TYPE_PUSH_PROMISE streamId == 0"); } short padding = (flags & FLAG_PADDED) != 0 ? (short) (source.readByte() & 0xff) : 0; int promisedStreamId = source.readInt() & 0x7fffffff; length -= 4; // account for above read. length = lengthWithoutPadding(length, flags, padding); List<Header> headerBlock = readHeaderBlock(length, padding, flags, streamId); handler.pushPromise(streamId, promisedStreamId, headerBlock); }
private void readHeader() throws IOException { if (closed) throw new IOException("closed"); int b0 = source.readByte() & 0xff; opcode = b0 & B0_MASK_OPCODE; isFinalFrame = (b0 & B0_FLAG_FIN) != 0; isControlFrame = (b0 & OPCODE_FLAG_CONTROL) != 0; // Control frames must be final frames (cannot contain continuations). if (isControlFrame && !isFinalFrame) { throw new ProtocolException("Control frames must be final."); } boolean reservedFlag1 = (b0 & B0_FLAG_RSV1) != 0; boolean reservedFlag2 = (b0 & B0_FLAG_RSV2) != 0; boolean reservedFlag3 = (b0 & B0_FLAG_RSV3) != 0; if (reservedFlag1 || reservedFlag2 || reservedFlag3) { // Reserved flags are for extensions which we currently do not support. throw new ProtocolException("Reserved flags are unsupported."); } int b1 = source.readByte() & 0xff; isMasked = (b1 & B1_FLAG_MASK) != 0; if (isMasked == isClient) { // Masked payloads must be read on the server. Unmasked payloads must be read on the client. throw new ProtocolException("Client-sent frames must be masked. Server sent must not."); } // Get frame length, optionally reading from follow-up bytes if indicated by special values. frameLength = b1 & B1_MASK_LENGTH; if (frameLength == PAYLOAD_SHORT) { frameLength = source.readShort() & 0xffffL; // Value is unsigned. } else if (frameLength == PAYLOAD_LONG) { frameLength = source.readLong(); if (frameLength < 0) { throw new ProtocolException( "Frame length 0x" + Long.toHexString(frameLength) + " > 0x7FFFFFFFFFFFFFFF"); } } frameBytesRead = 0; if (isControlFrame && frameLength > PAYLOAD_BYTE_MAX) { throw new ProtocolException("Control frame must be less than " + PAYLOAD_BYTE_MAX + "B."); } if (isMasked) { // Read the masking key as bytes so that they can be used directly for unmasking. source.readFully(maskKey); } }
private static long readLong(BufferedSource source) throws IOException { try { long result = source.readDecimalLong(); String line = source.readUtf8LineStrict(); if (!line.isEmpty()) { throw new IOException("expected new line was \"" + line + "\""); } return result; } catch (NumberFormatException e) { e.printStackTrace(); throw new IOException(e.getMessage()); } }
private void readSettings(Handler handler, int length, byte flags, int streamId) throws IOException { if (streamId != 0) throw ioException("TYPE_SETTINGS streamId != 0"); if ((flags & FLAG_ACK) != 0) { if (length != 0) throw ioException("FRAME_SIZE_ERROR ack frame should be empty!"); handler.ackSettings(); return; } if (length % 6 != 0) throw ioException("TYPE_SETTINGS length %% 6 != 0: %s", length); Settings settings = new Settings(); for (int i = 0; i < length; i += 6) { short id = source.readShort(); int value = source.readInt(); switch (id) { case 1: // SETTINGS_HEADER_TABLE_SIZE break; case 2: // SETTINGS_ENABLE_PUSH if (value != 0 && value != 1) { throw ioException("PROTOCOL_ERROR SETTINGS_ENABLE_PUSH != 0 or 1"); } break; case 3: // SETTINGS_MAX_CONCURRENT_STREAMS id = 4; // Renumbered in draft 10. break; case 4: // SETTINGS_INITIAL_WINDOW_SIZE id = 7; // Renumbered in draft 10. if (value < 0) { throw ioException("PROTOCOL_ERROR SETTINGS_INITIAL_WINDOW_SIZE > 2^31 - 1"); } break; case 5: // SETTINGS_MAX_FRAME_SIZE if (value < INITIAL_MAX_FRAME_SIZE || value > 16777215) { throw ioException("PROTOCOL_ERROR SETTINGS_MAX_FRAME_SIZE: %s", value); } break; case 6: // SETTINGS_MAX_HEADER_LIST_SIZE break; // Advisory only, so ignored. default: throw ioException("PROTOCOL_ERROR invalid settings id: %s", id); } settings.set(id, 0, value); } handler.settings(false, settings); if (settings.getHeaderTableSize() >= 0) { hpackReader.headerTableSizeSetting(settings.getHeaderTableSize()); } }
private void readData(Handler handler, int length, byte flags, int streamId) throws IOException { // TODO: checkState open or half-closed (local) or raise STREAM_CLOSED boolean inFinished = (flags & FLAG_END_STREAM) != 0; boolean gzipped = (flags & FLAG_COMPRESSED) != 0; if (gzipped) { throw ioException("PROTOCOL_ERROR: FLAG_COMPRESSED without SETTINGS_COMPRESS_DATA"); } short padding = (flags & FLAG_PADDED) != 0 ? (short) (source.readByte() & 0xff) : 0; length = lengthWithoutPadding(length, flags, padding); handler.data(inFinished, streamId, source, length); source.skip(padding); }
@Override public long read(Buffer sink, long byteCount) throws IOException { while (left == 0) { source.skip(padding); padding = 0; if ((flags & FLAG_END_HEADERS) != 0) return -1; readContinuationHeader(); // TODO: test case for empty continuation header? } long read = source.read(sink, Math.min(byteCount, left)); if (read == -1) return -1; left -= read; return read; }
private void readWindowUpdate(Handler handler, int length, byte flags, int streamId) throws IOException { if (length != 4) throw ioException("TYPE_WINDOW_UPDATE length !=4: %s", length); long increment = (source.readInt() & 0x7fffffffL); if (increment == 0) throw ioException("windowSizeIncrement was 0", increment); handler.windowUpdate(streamId, increment); }
private void readControlFrame() throws IOException { Buffer buffer = null; if (frameBytesRead < frameLength) { buffer = new Buffer(); if (isClient) { source.readFully(buffer, frameLength); } else { while (frameBytesRead < frameLength) { int toRead = (int) Math.min(frameLength - frameBytesRead, maskBuffer.length); int read = source.read(maskBuffer, 0, toRead); if (read == -1) throw new EOFException(); toggleMask(maskBuffer, read, maskKey, frameBytesRead); buffer.write(maskBuffer, 0, read); frameBytesRead += read; } } } switch (opcode) { case OPCODE_CONTROL_PING: frameCallback.onPing(buffer); break; case OPCODE_CONTROL_PONG: frameCallback.onPong(buffer); break; case OPCODE_CONTROL_CLOSE: int code = 1000; String reason = ""; if (buffer != null) { long bufferSize = buffer.size(); if (bufferSize == 1) { throw new ProtocolException("Malformed close payload length of 1."); } else if (bufferSize != 0) { code = buffer.readShort(); validateCloseCode(code, false); reason = buffer.readUtf8(); } } frameCallback.onClose(code, reason); closed = true; break; default: throw new ProtocolException("Unknown control opcode: " + toHexString(opcode)); } }
/** * Peeks up to {@code byteCount} bytes from the response body and returns them as a new response * body. If fewer than {@code byteCount} bytes are in the response body, the full response body is * returned. If more than {@code byteCount} bytes are in the response body, the returned value * will be truncated to {@code byteCount} bytes. * * <p>It is an error to call this method after the body has been consumed. * * <p><strong>Warning:</strong> this method loads the requested bytes into memory. Most * applications should set a modest limit on {@code byteCount}, such as 1 MiB. */ public ResponseBody peekBody(long byteCount) throws IOException { BufferedSource source = body.source(); source.request(byteCount); Buffer copy = source.buffer().clone(); // There may be more than byteCount bytes in source.buffer(). If there is, return a prefix. Buffer result; if (copy.size() > byteCount) { result = new Buffer(); result.write(copy, byteCount); copy.clear(); } else { result = copy; } return ResponseBody.create(body.contentType(), result.size(), result); }
public static @NonNull SmashNetworkData perform(SmashRequest<?> request) throws SmashError { SmashNetworkData data = new SmashNetworkData(); try { Request.Builder okBuilder = new Request.Builder().url(request.getUrl()); okBuilder.removeHeader("User-Agent").addHeader("User-Agent", Smash.getUserAgent()); BufferedSource body = getBody(request); switch (request.getMethod()) { case GET: okBuilder = okBuilder.get(); break; case POST: okBuilder = okBuilder.post(convertBody(request, body)); break; case PUT: okBuilder = okBuilder.put(convertBody(request, body)); break; case DELETE: okBuilder = okBuilder.delete(convertBody(request, body)); break; case HEAD: okBuilder = okBuilder.head(); break; case PATCH: okBuilder = okBuilder.patch(convertBody(request, body)); break; } Request okRequest = okBuilder.build(); Response okResponse = sHttpClient.newCall(okRequest).execute(); if (body != null) { body.close(); } data.code = okResponse.code(); data.headers = okResponse.headers(); data.source = okResponse.body().source(); data.length = okResponse.body().contentLength(); } catch (IOException ioe) { throw new SmashError(ioe); } return data; }
private void readGoAway(Handler handler, int length, byte flags, int streamId) throws IOException { if (length < 8) throw ioException("TYPE_GOAWAY length < 8: %s", length); if (streamId != 0) throw ioException("TYPE_GOAWAY streamId != 0"); int lastStreamId = source.readInt(); int errorCodeInt = source.readInt(); int opaqueDataLength = length - 8; ErrorCode errorCode = ErrorCode.fromHttp2(errorCodeInt); if (errorCode == null) { throw ioException("TYPE_GOAWAY unexpected error code: %d", errorCodeInt); } ByteString debugData = EMPTY; if (opaqueDataLength > 0) { // Must read debug data in order to not corrupt the connection. debugData = source.readByteString(opaqueDataLength); } handler.goAway(lastStreamId, errorCode, debugData); }
private static RequestBody convertBody(SmashRequest request, BufferedSource body) throws SmashError { try { return RequestBody.create(MediaType.parse(request.getBodyContentType()), body.readUtf8()); } catch (IOException ioe) { throw new SmashError(ioe); } }
/** * Reads an entry from an input stream. A typical entry looks like this: * * <pre>{@code * http://google.com/foo * GET * 2 * Accept-Language: fr-CA * Accept-Charset: UTF-8 * HTTP/1.1 200 OK * 3 * Content-Type: image/png * Content-Length: 100 * Cache-Control: max-age=600 * }</pre> * * <p>A typical HTTPS file looks like this: * * <pre>{@code * https://google.com/foo * GET * 2 * Accept-Language: fr-CA * Accept-Charset: UTF-8 * HTTP/1.1 200 OK * 3 * Content-Type: image/png * Content-Length: 100 * Cache-Control: max-age=600 * * AES_256_WITH_MD5 * 2 * base64-encoded peerCertificate[0] * base64-encoded peerCertificate[1] * -1 * }</pre> * * The file is newline separated. The first two lines are the URL and the request method. Next * is the number of HTTP Vary request header lines, followed by those lines. * * <p>Next is the response status line, followed by the number of HTTP response header lines, * followed by those lines. * * <p>HTTPS responses also contain SSL session information. This begins with a blank line, and * then a line containing the cipher suite. Next is the length of the peer certificate chain. * These certificates are base64-encoded and appear each on their own line. The next line * contains the length of the local certificate chain. These certificates are also * base64-encoded and appear each on their own line. A length of -1 is used to encode a null * array. */ public Entry(Source in) throws IOException { try { BufferedSource source = Okio.buffer(in); url = source.readUtf8LineStrict(); requestMethod = source.readUtf8LineStrict(); if (readInt(source) == 1) { MediaType contentType = MediaType.parse(source.readUtf8LineStrict()); int contentLength = readInt(source); requestBody = RequestBody.create(contentType, source.readByteArray(contentLength)); } else { requestBody = null; } Headers.Builder varyHeadersBuilder = new Headers.Builder(); int varyRequestHeaderLineCount = readInt(source); for (int i = 0; i < varyRequestHeaderLineCount; i++) { OkHttpAccess.addLenient(varyHeadersBuilder, source.readUtf8LineStrict()); } varyHeaders = varyHeadersBuilder.build(); StatusLine statusLine = StatusLine.parse(source.readUtf8LineStrict()); protocol = statusLine.protocol; code = statusLine.code; message = statusLine.message; Headers.Builder responseHeadersBuilder = new Headers.Builder(); int responseHeaderLineCount = readInt(source); for (int i = 0; i < responseHeaderLineCount; i++) { OkHttpAccess.addLenient(responseHeadersBuilder, source.readUtf8LineStrict()); } responseHeaders = responseHeadersBuilder.build(); if (isHttps()) { String blank = source.readUtf8LineStrict(); if (blank.length() > 0) { throw new IOException("expected \"\" but was \"" + blank + "\""); } String cipherSuite = source.readUtf8LineStrict(); List<Certificate> peerCertificates = readCertificateList(source); List<Certificate> localCertificates = readCertificateList(source); handshake = Handshake.get(cipherSuite, peerCertificates, localCertificates); } else { handshake = null; } } finally { in.close(); } }
@Override public void readConnectionPreface() throws IOException { if (client) return; // Nothing to read; servers doesn't send a connection preface! ByteString connectionPreface = source.readByteString(CONNECTION_PREFACE.size()); if (logger.isLoggable(FINE)) logger.fine(format("<< CONNECTION %s", connectionPreface.hex())); if (!CONNECTION_PREFACE.equals(connectionPreface)) { throw ioException("Expected a connection header but was %s", connectionPreface.utf8()); } }
private void readPing(Handler handler, int length, byte flags, int streamId) throws IOException { if (length != 8) throw ioException("TYPE_PING length != 8: %s", length); if (streamId != 0) throw ioException("TYPE_PING streamId != 0"); int payload1 = source.readInt(); int payload2 = source.readInt(); boolean ack = (flags & FLAG_ACK) != 0; handler.ping(ack, payload1, payload2); }
private void readRstStream(Handler handler, int length, byte flags, int streamId) throws IOException { if (length != 4) throw ioException("TYPE_RST_STREAM length: %d != 4", length); if (streamId == 0) throw ioException("TYPE_RST_STREAM streamId == 0"); int errorCodeInt = source.readInt(); ErrorCode errorCode = ErrorCode.fromHttp2(errorCodeInt); if (errorCode == null) { throw ioException("TYPE_RST_STREAM unexpected error code: %d", errorCodeInt); } handler.rstStream(streamId, errorCode); }
private Response response(DiskLruCache.Snapshot snapshot) throws IOException { String contentType = responseHeaders.get("Content-Type"); Request cacheRequest = new Request.Builder() .url(url) .method(requestMethod, requestBody) .headers(varyHeaders) .build(); BufferedSource source = Okio.buffer(snapshot.getSource(RESPONSE_BODY)); byte[] bodyContent = source.readByteArray(); MediaType mediaType = contentType == null ? null : MediaType.parse(contentType); return new Response.Builder() .request(cacheRequest) .protocol(protocol) .code(code) .message(message) .headers(responseHeaders) .body(new OkHttpUtil.ReusableResponseBody(mediaType, bodyContent)) .handshake(handshake) .build(); }
/** Stops the SSE stream. */ public void stop() { mClosed = true; try { if (in != null) in.close(); } catch (final IOException ignore) { } try { out.close(); } catch (final IOException ignore) { } // try { if (socket != null) socket.close(); } catch (final IOException ignore) {} }
private List<Certificate> readCertificateList(BufferedSource source) throws IOException { int length = readInt(source); if (length == -1) return Collections.emptyList(); // OkHttp v1.2 used -1 to indicate null. try { CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); List<Certificate> result = new ArrayList<>(length); for (int i = 0; i < length; i++) { String line = source.readUtf8LineStrict(); Buffer bytes = new Buffer(); bytes.write(ByteString.decodeBase64(line)); result.add(certificateFactory.generateCertificate(bytes.inputStream())); } return result; } catch (CertificateException e) { throw new IOException(e.getMessage()); } }
@Override public void data(boolean inFinished, int streamId, BufferedSource source, int length) throws IOException { if (pushedStream(streamId)) { pushDataLater(streamId, source, length, inFinished); return; } FramedStream dataStream = getStream(streamId); if (dataStream == null) { writeSynResetLater(streamId, ErrorCode.INVALID_STREAM); source.skip(length); return; } dataStream.receiveData(source, length); if (inFinished) { dataStream.receiveFin(); } }
private void readHeaders(Handler handler, int length, byte flags, int streamId) throws IOException { if (streamId == 0) throw ioException("PROTOCOL_ERROR: TYPE_HEADERS streamId == 0"); boolean endStream = (flags & FLAG_END_STREAM) != 0; short padding = (flags & FLAG_PADDED) != 0 ? (short) (source.readByte() & 0xff) : 0; if ((flags & FLAG_PRIORITY) != 0) { readPriority(handler, streamId); length -= 5; // account for above read. } length = lengthWithoutPadding(length, flags, padding); List<Header> headerBlock = readHeaderBlock(length, padding, flags, streamId); handler.headers(false, endStream, streamId, -1, headerBlock, HeadersMode.HTTP_20_HEADERS); }
@Override public Response intercept(Chain chain) throws IOException { Level level = this.level; Request request = chain.request(); if (level == Level.NONE) { return chain.proceed(request); } boolean logBody = level == Level.BODY; boolean logHeaders = logBody || level == Level.HEADERS; RequestBody requestBody = request.body(); boolean hasRequestBody = requestBody != null; Connection connection = chain.connection(); Protocol protocol = connection != null ? connection.getProtocol() : Protocol.HTTP_1_1; String requestStartMessage = "--> " + request.method() + ' ' + requestPath(request.httpUrl()) + ' ' + protocol(protocol); if (!logHeaders && hasRequestBody) { requestStartMessage += " (" + requestBody.contentLength() + "-byte body)"; } logger.log(requestStartMessage); if (logHeaders) { Headers headers = request.headers(); for (int i = 0, count = headers.size(); i < count; i++) { logger.log(headers.name(i) + ": " + headers.value(i)); } String endMessage = "--> END " + request.method(); if (logBody && hasRequestBody) { Buffer buffer = new Buffer(); requestBody.writeTo(buffer); Charset charset = UTF8; MediaType contentType = requestBody.contentType(); if (contentType != null) { contentType.charset(UTF8); } logger.log(""); logger.log(buffer.readString(charset)); endMessage += " (" + requestBody.contentLength() + "-byte body)"; } logger.log(endMessage); } long startNs = System.nanoTime(); Response response = chain.proceed(request); long tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs); ResponseBody responseBody = response.body(); logger.log( "<-- " + protocol(response.protocol()) + ' ' + response.code() + ' ' + response.message() + " (" + tookMs + "ms" + (!logHeaders ? ", " + responseBody.contentLength() + "-byte body" : "") + ')'); if (logHeaders) { Headers headers = response.headers(); for (int i = 0, count = headers.size(); i < count; i++) { logger.log(headers.name(i) + ": " + headers.value(i)); } String endMessage = "<-- END HTTP"; if (logBody) { BufferedSource source = responseBody.source(); source.request(Long.MAX_VALUE); // Buffer the entire body. Buffer buffer = source.buffer(); Charset charset = UTF8; MediaType contentType = responseBody.contentType(); if (contentType != null) { charset = contentType.charset(UTF8); } if (responseBody.contentLength() != 0) { logger.log(""); logger.log(buffer.clone().readString(charset)); } endMessage += " (" + buffer.size() + "-byte body)"; } logger.log(endMessage); } return response; }
@Override public void close() throws IOException { source.close(); }
@Override public boolean onData(int streamId, BufferedSource source, int byteCount, boolean last) throws IOException { source.skip(byteCount); return false; }
private void assertResponseBody(HttpURLConnection connection, String expected) throws Exception { BufferedSource source = buffer(source(connection.getInputStream())); String actual = source.readString(US_ASCII); source.close(); assertEquals(expected, actual); }
@Override public Timeout timeout() { return source.timeout(); }
private static int readMedium(BufferedSource source) throws IOException { return (source.readByte() & 0xff) << 16 | (source.readByte() & 0xff) << 8 | (source.readByte() & 0xff); }