/incubator/jdk-connector/src/main/java/org/glassfish/jersey/jdk/connector/TransferEncodingParser.java
Java | 319 lines | 202 code | 50 blank | 67 comment | 42 complexity | 85d5e0f011a90a877312fa450285fc86 MD5 | raw file
- /*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright (c) 2015 Oracle and/or its affiliates. All rights reserved.
- *
- * The contents of this file are subject to the terms of either the GNU
- * General Public License Version 2 only ("GPL") or the Common Development
- * and Distribution License("CDDL") (collectively, the "License"). You
- * may not use this file except in compliance with the License. You can
- * obtain a copy of the License at
- * http://glassfish.java.net/public/CDDL+GPL_1_1.html
- * or packager/legal/LICENSE.txt. See the License for the specific
- * language governing permissions and limitations under the License.
- *
- * When distributing the software, include this License Header Notice in each
- * file and include the License file at packager/legal/LICENSE.txt.
- *
- * GPL Classpath Exception:
- * Oracle designates this particular file as subject to the "Classpath"
- * exception as provided by Oracle in the GPL Version 2 section of the License
- * file that accompanied this code.
- *
- * Modifications:
- * If applicable, add the following below the License Header, with the fields
- * enclosed by brackets [] replaced by your own identifying information:
- * "Portions Copyright [year] [name of copyright owner]"
- *
- * Contributor(s):
- * If you wish your version of this file to be governed by only the CDDL or
- * only the GPL Version 2, indicate your decision by adding "[Contributor]
- * elects to include this software in this distribution under the [CDDL or GPL
- * Version 2] license." If you don't indicate a single choice of license, a
- * recipient has the option to distribute your version of this file under
- * either the CDDL, the GPL Version 2 or to extend the choice of license to
- * its licensees as provided above. However, if you add GPL Version 2 code
- * and therefore, elected the GPL Version 2 license, then the option applies
- * only if the new code is made subject to such option by the copyright
- * holder.
- */
- package org.glassfish.jersey.jdk.connector;
- import java.nio.ByteBuffer;
- import static org.glassfish.jersey.jdk.connector.HttpParserUtils.isSpaceOrTab;
- import static org.glassfish.jersey.jdk.connector.HttpParserUtils.skipSpaces;
- /**
- * @author Alexey Stashok
- * @author Petr Janouch (petr.janouch at oracle.com)
- */
- abstract class TransferEncodingParser {
- abstract boolean parse(ByteBuffer input) throws ParseException;
- static TransferEncodingParser createFixedLengthParser(AsynchronousBodyInputStream responseBody, int expectedLength) {
- return new FixedLengthEncodingParser(responseBody, expectedLength);
- }
- static TransferEncodingParser createChunkParser(AsynchronousBodyInputStream responseBody,
- HttpParser httpParser, int maxHeadersSize) {
- return new ChunkedEncodingParser(responseBody, httpParser, maxHeadersSize);
- }
- private static class FixedLengthEncodingParser extends TransferEncodingParser {
- private final int expectedLength;
- private final AsynchronousBodyInputStream responseBody;
- private volatile int consumedLength = 0;
- FixedLengthEncodingParser(AsynchronousBodyInputStream responseBody, int expectedLength) {
- this.expectedLength = expectedLength;
- this.responseBody = responseBody;
- }
- @Override
- boolean parse(ByteBuffer input) throws ParseException {
- if (input.remaining() + consumedLength > expectedLength) {
- throw new ParseException(LocalizationMessages.HTTP_BODY_SIZE_OVERFLOW());
- }
- byte[] data = new byte[input.remaining()];
- input.get(data);
- ByteBuffer parsed = ByteBuffer.wrap(data);
- responseBody.notifyDataAvailable(parsed);
- consumedLength += data.length;
- return consumedLength == expectedLength;
- }
- }
- private static class ChunkedEncodingParser extends TransferEncodingParser {
- private static final int MAX_HTTP_CHUNK_SIZE_LENGTH = 16;
- private static final long CHUNK_SIZE_OVERFLOW = Long.MAX_VALUE >> 4;
- private static final int CHUNK_LENGTH_PARSED_STATE = 3;
- private static final int[] DEC = {
- -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
- 00, 01, 02, 03, 04, 05, 06, 07, 8, 9, -1, -1, -1, -1, -1, -1,
- -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
- };
- private final HttpParserUtils.ContentParsingState contentParsingState = new HttpParserUtils
- .ContentParsingState();
- private final HttpParserUtils.HeaderParsingState headerParsingState;
- private final AsynchronousBodyInputStream responseBody;
- private final HttpParser httpParser;
- private final int maxHeadersSize;
- ChunkedEncodingParser(AsynchronousBodyInputStream responseBody, HttpParser httpParser, int maxHeadersSize) {
- this.responseBody = responseBody;
- this.httpParser = httpParser;
- this.headerParsingState = httpParser.getHeaderParsingState();
- this.maxHeadersSize = maxHeadersSize;
- }
- // Taken with small modifications from Grizzly ChunkedTransferEncoding.parsePacket
- @Override
- boolean parse(ByteBuffer input) throws ParseException {
- while (input.hasRemaining()) {
- boolean isLastChunk = contentParsingState.isLastChunk;
- // Check if HTTP chunk length was parsed
- if (!isLastChunk && contentParsingState.chunkRemainder <= 0) {
- if (!parseTrailerCRLF(input)) {
- return false;
- }
- if (!parseHttpChunkLength(input)) {
- // if not a HEAD request and we don't have enough data to
- // parse chunk length - shutdownNow execution
- return false;
- }
- } else {
- // HTTP content starts from position 0 in the input Buffer (HTTP chunk header is not part of the input Buffer)
- //contentParsingState.chunkContentStart = 0;
- contentParsingState.chunkContentStart = input.position();
- }
- // Get the position in the input Buffer, where actual HTTP content starts
- int chunkContentStart = contentParsingState.chunkContentStart;
- if (contentParsingState.chunkLength == 0) {
- // if it's the last HTTP chunk
- if (!isLastChunk) {
- // set it's the last chunk
- contentParsingState.isLastChunk = true;
- isLastChunk = true;
- // start trailer parsing
- initTrailerParsing();
- }
- // Check if trailer is present
- if (!parseLastChunkTrailer(input)) {
- // if yes - and there is not enough input data - shutdownNow the
- // filterchain processing
- return false;
- }
- // move the content start position after trailer parsing
- chunkContentStart = headerParsingState.offset;
- }
- if (isLastChunk) {
- input.position(chunkContentStart);
- return true;
- }
- // Get the number of bytes remaining in the current chunk
- final long thisPacketRemaining = contentParsingState.chunkRemainder;
- // Get the number of content bytes available in the current input Buffer
- final int contentAvailable = input.limit() - chunkContentStart;
- input.position(chunkContentStart);
- ByteBuffer data;
- if (contentAvailable > thisPacketRemaining) {
- // If input Buffer has part of the next message - slice it
- data = Utils.split(input, (int) (chunkContentStart + thisPacketRemaining));
- } else {
- data = Utils.split(input, chunkContentStart + input.remaining());
- }
- contentParsingState.chunkRemainder -= data.remaining();
- responseBody.notifyDataAvailable(data);
- }
- return false;
- }
- // Taken with small modifications from Grizzly ChunkedTransferEncoding.parseHttpChunkLength
- private boolean parseHttpChunkLength(final ByteBuffer input) throws ParseException {
- while (true) {
- switch (headerParsingState.state) {
- case 0: {// Initialize chunk parsing
- final int pos = input.position();
- headerParsingState.start = pos;
- headerParsingState.offset = pos;
- headerParsingState.packetLimit = pos + MAX_HTTP_CHUNK_SIZE_LENGTH;
- headerParsingState.state = 1;
- break;
- }
- case 1: { // Skip heading spaces (it's not allowed by the spec, but some servers put it there)
- final int nonSpaceIdx = skipSpaces(input,
- headerParsingState.offset, headerParsingState.packetLimit);
- if (nonSpaceIdx == -1) {
- headerParsingState.offset = input.limit();
- headerParsingState.state = 1;
- headerParsingState.checkOverflow(LocalizationMessages.HTTP_CHUNK_ENCODING_PREFIX_OVERFLOW());
- return false;
- }
- headerParsingState.offset = nonSpaceIdx;
- headerParsingState.state = 2;
- break;
- }
- case 2: { // Scan chunk size
- int offset = headerParsingState.offset;
- int limit = Math.min(headerParsingState.packetLimit, input.limit());
- long value = headerParsingState.parsingNumericValue;
- while (offset < limit) {
- final byte b = input.get(offset);
- if (isSpaceOrTab(b) || /*trailing spaces are not allowed by the spec, but some server put it there*/
- b == HttpParserUtils.CR || b == HttpParserUtils.SEMI_COLON) {
- headerParsingState.checkpoint = offset;
- } else if (b == HttpParserUtils.LF) {
- contentParsingState.chunkContentStart = offset + 1;
- contentParsingState.chunkLength = value;
- contentParsingState.chunkRemainder = value;
- headerParsingState.state = CHUNK_LENGTH_PARSED_STATE;
- return true;
- } else if (headerParsingState.checkpoint == -1) {
- if (DEC[b & 0xFF] != -1 && checkOverflow(value)) {
- value = (value << 4) + (DEC[b & 0xFF]);
- } else {
- throw new ParseException(
- LocalizationMessages.HTTP_INVALID_CHUNK_SIZE_HEX_VALUE(b));
- }
- } else {
- throw new ParseException(LocalizationMessages.HTTP_UNEXPECTED_CHUNK_HEADER());
- }
- offset++;
- }
- headerParsingState.parsingNumericValue = value;
- headerParsingState.offset = offset;
- headerParsingState.checkOverflow(LocalizationMessages.HTTP_CHUNK_ENCODING_PREFIX_OVERFLOW());
- return false;
- }
- }
- }
- }
- // Taken with small modifications from Grizzly ChunkedTransferEncoding.parseTrailerCRLF
- private boolean parseTrailerCRLF(ByteBuffer input) {
- if (headerParsingState.state == CHUNK_LENGTH_PARSED_STATE) {
- while (input.hasRemaining()) {
- if (input.get() == HttpParserUtils.LF) {
- headerParsingState.recycle();
- return input.hasRemaining();
- }
- }
- return false;
- }
- return true;
- }
- /**
- * @return <tt>false</tt> if next left bit-shift by 4 bits will cause overflow,
- * or <tt>true</tt> otherwise
- */
- private boolean checkOverflow(final long chunkLength) {
- return chunkLength <= CHUNK_SIZE_OVERFLOW;
- }
- private void initTrailerParsing() {
- headerParsingState.subState = 0;
- final int start = contentParsingState.chunkContentStart;
- headerParsingState.start = start;
- headerParsingState.offset = start;
- headerParsingState.packetLimit = start + maxHeadersSize;
- }
- // Taken with small modifications from Grizzly ChunkedTransferEncoding.parseLastChunkTrailer
- private boolean parseLastChunkTrailer(final ByteBuffer input) throws ParseException {
- boolean result = httpParser.parseHeadersFromBuffer(input, true);
- if (!result) {
- headerParsingState.checkOverflow(LocalizationMessages.HTTP_TRAILER_HEADER_OVERFLOW());
- }
- return result;
- }
- }
- }