package org.httpkit.client; import org.httpkit.*; import java.nio.ByteBuffer; import java.util.Map; import java.util.TreeMap; import static org.httpkit.HttpUtils.*; import static org.httpkit.HttpVersion.HTTP_1_0; import static org.httpkit.HttpVersion.HTTP_1_1; import static org.httpkit.client.State.*; enum State { ALL_READ, READ_CHUNK_DELIMITER, READ_CHUNK_FOOTER, READ_CHUNK_SIZE, READ_CHUNKED_CONTENT, READ_FIXED_LENGTH_CONTENT, READ_HEADER, READ_INITIAL, READ_VARIABLE_LENGTH_CONTENT } public class Decoder { private final Map<String, Object> headers = new TreeMap<String, Object>(); // package visible final IRespListener listener; private final LineReader lineReader; int readRemaining = 0; State state = READ_INITIAL; private final HttpMethod method; private boolean emptyBodyExpected = false; public Decoder(IRespListener listener, HttpMethod method) { this.listener = listener; this.method = method; lineReader = new LineReader(16192); // max 16k header line } private void parseInitialLine(String sb) throws ProtocolException, AbortException { int aStart; int aEnd; int bStart; int bEnd; int cStart; int cEnd; aStart = findNonWhitespace(sb, 0); aEnd = findWhitespace(sb, aStart); bStart = findNonWhitespace(sb, aEnd); bEnd = findWhitespace(sb, bStart); cStart = findNonWhitespace(sb, bEnd); cEnd = findEndOfString(sb, cStart); if ((cStart < cEnd) // Account for buggy web servers that omit Reason-Phrase from Status-Line. // http://www.w3.org/Protocols/HTTP/1.0/draft-ietf-http-spec.html#Response || (cStart == cEnd && bStart < bEnd)) { try { int status = Integer.parseInt(sb.substring(bStart, bEnd)); // status is not 1xx, 204 or 304, then the body is unbounded. // RFC2616, section 4.4 emptyBodyExpected = status / 100 == 1 || status == 204 || status == 304; HttpStatus s = HttpStatus.valueOf(status); HttpVersion version = HTTP_1_1; if ("HTTP/1.0".equals(sb.substring(aStart, aEnd))) { version = HTTP_1_0; } listener.onInitialLineReceived(version, s); state = READ_HEADER; } catch (NumberFormatException e) { throw new ProtocolException("not http protocol? " + sb); } } else { throw new ProtocolException("not http protocol? " + sb); } } public State decode(ByteBuffer buffer) throws LineTooLargeException, ProtocolException, AbortException { String line; while (buffer.hasRemaining() && state != State.ALL_READ) { switch (state) { case READ_INITIAL: if ((line = lineReader.readLine(buffer)) != null) { parseInitialLine(line); } break; case READ_HEADER: readHeaders(buffer); break; case READ_CHUNK_SIZE: line = lineReader.readLine(buffer); if (line != null && !line.isEmpty()) { readRemaining = getChunkSize(line); if (readRemaining == 0) { state = READ_CHUNK_FOOTER; } else { state = READ_CHUNKED_CONTENT; } } break; case READ_FIXED_LENGTH_CONTENT: readBody(buffer, ALL_READ); break; case READ_CHUNKED_CONTENT: readBody(buffer, READ_CHUNK_DELIMITER); break; case READ_CHUNK_FOOTER: readEmptyLine(buffer); state = ALL_READ; break; case READ_CHUNK_DELIMITER: readEmptyLine(buffer); state = READ_CHUNK_SIZE; break; case READ_VARIABLE_LENGTH_CONTENT: readBody(buffer, null); break; } } return state; } private void readBody(ByteBuffer buffer, State nextState) throws AbortException { int toRead = Math.min(buffer.remaining(), readRemaining); byte[] bytes = new byte[toRead]; buffer.get(bytes, 0, toRead); listener.onBodyReceived(bytes, toRead); if (nextState != null) { readRemaining -= toRead; if (readRemaining == 0) { state = nextState; } } } void readEmptyLine(ByteBuffer buffer) { byte b = buffer.get(); if (b == CR && buffer.hasRemaining()) { buffer.get(); // should be LF } } private void readHeaders(ByteBuffer buffer) throws LineTooLargeException, AbortException { String line = lineReader.readLine(buffer); while (line != null && !line.isEmpty()) { HttpUtils.splitAndAddHeader(line, headers); line = lineReader.readLine(buffer); } if (line == null) return; // data is not received enough. for next run listener.onHeadersReceived(headers); if (method == HttpMethod.HEAD) { state = ALL_READ; return; } String te = HttpUtils.getStringValue(headers, TRANSFER_ENCODING); if (CHUNKED.equals(te)) { state = READ_CHUNK_SIZE; } else { String cl = HttpUtils.getStringValue(headers, CONTENT_LENGTH); if (cl != null) { readRemaining = Integer.parseInt(cl); if (readRemaining == 0) { state = ALL_READ; } else { state = READ_FIXED_LENGTH_CONTENT; } } else if (emptyBodyExpected) { state = ALL_READ; } else { state = READ_VARIABLE_LENGTH_CONTENT; // for readBody min readRemaining = Integer.MAX_VALUE; } } } }