PageRenderTime 98ms CodeModel.GetById 10ms RepoModel.GetById 0ms app.codeStats 0ms

/incubator/jdk-connector/src/main/java/org/glassfish/jersey/jdk/connector/TransferEncodingParser.java

https://gitlab.com/jaragan/jersey
Java | 319 lines | 202 code | 50 blank | 67 comment | 42 complexity | 85d5e0f011a90a877312fa450285fc86 MD5 | raw file
  1. /*
  2. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  3. *
  4. * Copyright (c) 2015 Oracle and/or its affiliates. All rights reserved.
  5. *
  6. * The contents of this file are subject to the terms of either the GNU
  7. * General Public License Version 2 only ("GPL") or the Common Development
  8. * and Distribution License("CDDL") (collectively, the "License"). You
  9. * may not use this file except in compliance with the License. You can
  10. * obtain a copy of the License at
  11. * http://glassfish.java.net/public/CDDL+GPL_1_1.html
  12. * or packager/legal/LICENSE.txt. See the License for the specific
  13. * language governing permissions and limitations under the License.
  14. *
  15. * When distributing the software, include this License Header Notice in each
  16. * file and include the License file at packager/legal/LICENSE.txt.
  17. *
  18. * GPL Classpath Exception:
  19. * Oracle designates this particular file as subject to the "Classpath"
  20. * exception as provided by Oracle in the GPL Version 2 section of the License
  21. * file that accompanied this code.
  22. *
  23. * Modifications:
  24. * If applicable, add the following below the License Header, with the fields
  25. * enclosed by brackets [] replaced by your own identifying information:
  26. * "Portions Copyright [year] [name of copyright owner]"
  27. *
  28. * Contributor(s):
  29. * If you wish your version of this file to be governed by only the CDDL or
  30. * only the GPL Version 2, indicate your decision by adding "[Contributor]
  31. * elects to include this software in this distribution under the [CDDL or GPL
  32. * Version 2] license." If you don't indicate a single choice of license, a
  33. * recipient has the option to distribute your version of this file under
  34. * either the CDDL, the GPL Version 2 or to extend the choice of license to
  35. * its licensees as provided above. However, if you add GPL Version 2 code
  36. * and therefore, elected the GPL Version 2 license, then the option applies
  37. * only if the new code is made subject to such option by the copyright
  38. * holder.
  39. */
  40. package org.glassfish.jersey.jdk.connector;
  41. import java.nio.ByteBuffer;
  42. import static org.glassfish.jersey.jdk.connector.HttpParserUtils.isSpaceOrTab;
  43. import static org.glassfish.jersey.jdk.connector.HttpParserUtils.skipSpaces;
  44. /**
  45. * @author Alexey Stashok
  46. * @author Petr Janouch (petr.janouch at oracle.com)
  47. */
  48. abstract class TransferEncodingParser {
  49. abstract boolean parse(ByteBuffer input) throws ParseException;
  50. static TransferEncodingParser createFixedLengthParser(AsynchronousBodyInputStream responseBody, int expectedLength) {
  51. return new FixedLengthEncodingParser(responseBody, expectedLength);
  52. }
  53. static TransferEncodingParser createChunkParser(AsynchronousBodyInputStream responseBody,
  54. HttpParser httpParser, int maxHeadersSize) {
  55. return new ChunkedEncodingParser(responseBody, httpParser, maxHeadersSize);
  56. }
  57. private static class FixedLengthEncodingParser extends TransferEncodingParser {
  58. private final int expectedLength;
  59. private final AsynchronousBodyInputStream responseBody;
  60. private volatile int consumedLength = 0;
  61. FixedLengthEncodingParser(AsynchronousBodyInputStream responseBody, int expectedLength) {
  62. this.expectedLength = expectedLength;
  63. this.responseBody = responseBody;
  64. }
  65. @Override
  66. boolean parse(ByteBuffer input) throws ParseException {
  67. if (input.remaining() + consumedLength > expectedLength) {
  68. throw new ParseException(LocalizationMessages.HTTP_BODY_SIZE_OVERFLOW());
  69. }
  70. byte[] data = new byte[input.remaining()];
  71. input.get(data);
  72. ByteBuffer parsed = ByteBuffer.wrap(data);
  73. responseBody.notifyDataAvailable(parsed);
  74. consumedLength += data.length;
  75. return consumedLength == expectedLength;
  76. }
  77. }
  78. private static class ChunkedEncodingParser extends TransferEncodingParser {
  79. private static final int MAX_HTTP_CHUNK_SIZE_LENGTH = 16;
  80. private static final long CHUNK_SIZE_OVERFLOW = Long.MAX_VALUE >> 4;
  81. private static final int CHUNK_LENGTH_PARSED_STATE = 3;
  82. private static final int[] DEC = {
  83. -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
  84. -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
  85. -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
  86. 00, 01, 02, 03, 04, 05, 06, 07, 8, 9, -1, -1, -1, -1, -1, -1,
  87. -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
  88. -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
  89. -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
  90. -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
  91. -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
  92. -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
  93. -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
  94. -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
  95. -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
  96. -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
  97. -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
  98. -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
  99. };
  100. private final HttpParserUtils.ContentParsingState contentParsingState = new HttpParserUtils
  101. .ContentParsingState();
  102. private final HttpParserUtils.HeaderParsingState headerParsingState;
  103. private final AsynchronousBodyInputStream responseBody;
  104. private final HttpParser httpParser;
  105. private final int maxHeadersSize;
  106. ChunkedEncodingParser(AsynchronousBodyInputStream responseBody, HttpParser httpParser, int maxHeadersSize) {
  107. this.responseBody = responseBody;
  108. this.httpParser = httpParser;
  109. this.headerParsingState = httpParser.getHeaderParsingState();
  110. this.maxHeadersSize = maxHeadersSize;
  111. }
  112. // Taken with small modifications from Grizzly ChunkedTransferEncoding.parsePacket
  113. @Override
  114. boolean parse(ByteBuffer input) throws ParseException {
  115. while (input.hasRemaining()) {
  116. boolean isLastChunk = contentParsingState.isLastChunk;
  117. // Check if HTTP chunk length was parsed
  118. if (!isLastChunk && contentParsingState.chunkRemainder <= 0) {
  119. if (!parseTrailerCRLF(input)) {
  120. return false;
  121. }
  122. if (!parseHttpChunkLength(input)) {
  123. // if not a HEAD request and we don't have enough data to
  124. // parse chunk length - shutdownNow execution
  125. return false;
  126. }
  127. } else {
  128. // HTTP content starts from position 0 in the input Buffer (HTTP chunk header is not part of the input Buffer)
  129. //contentParsingState.chunkContentStart = 0;
  130. contentParsingState.chunkContentStart = input.position();
  131. }
  132. // Get the position in the input Buffer, where actual HTTP content starts
  133. int chunkContentStart = contentParsingState.chunkContentStart;
  134. if (contentParsingState.chunkLength == 0) {
  135. // if it's the last HTTP chunk
  136. if (!isLastChunk) {
  137. // set it's the last chunk
  138. contentParsingState.isLastChunk = true;
  139. isLastChunk = true;
  140. // start trailer parsing
  141. initTrailerParsing();
  142. }
  143. // Check if trailer is present
  144. if (!parseLastChunkTrailer(input)) {
  145. // if yes - and there is not enough input data - shutdownNow the
  146. // filterchain processing
  147. return false;
  148. }
  149. // move the content start position after trailer parsing
  150. chunkContentStart = headerParsingState.offset;
  151. }
  152. if (isLastChunk) {
  153. input.position(chunkContentStart);
  154. return true;
  155. }
  156. // Get the number of bytes remaining in the current chunk
  157. final long thisPacketRemaining = contentParsingState.chunkRemainder;
  158. // Get the number of content bytes available in the current input Buffer
  159. final int contentAvailable = input.limit() - chunkContentStart;
  160. input.position(chunkContentStart);
  161. ByteBuffer data;
  162. if (contentAvailable > thisPacketRemaining) {
  163. // If input Buffer has part of the next message - slice it
  164. data = Utils.split(input, (int) (chunkContentStart + thisPacketRemaining));
  165. } else {
  166. data = Utils.split(input, chunkContentStart + input.remaining());
  167. }
  168. contentParsingState.chunkRemainder -= data.remaining();
  169. responseBody.notifyDataAvailable(data);
  170. }
  171. return false;
  172. }
  173. // Taken with small modifications from Grizzly ChunkedTransferEncoding.parseHttpChunkLength
  174. private boolean parseHttpChunkLength(final ByteBuffer input) throws ParseException {
  175. while (true) {
  176. switch (headerParsingState.state) {
  177. case 0: {// Initialize chunk parsing
  178. final int pos = input.position();
  179. headerParsingState.start = pos;
  180. headerParsingState.offset = pos;
  181. headerParsingState.packetLimit = pos + MAX_HTTP_CHUNK_SIZE_LENGTH;
  182. headerParsingState.state = 1;
  183. break;
  184. }
  185. case 1: { // Skip heading spaces (it's not allowed by the spec, but some servers put it there)
  186. final int nonSpaceIdx = skipSpaces(input,
  187. headerParsingState.offset, headerParsingState.packetLimit);
  188. if (nonSpaceIdx == -1) {
  189. headerParsingState.offset = input.limit();
  190. headerParsingState.state = 1;
  191. headerParsingState.checkOverflow(LocalizationMessages.HTTP_CHUNK_ENCODING_PREFIX_OVERFLOW());
  192. return false;
  193. }
  194. headerParsingState.offset = nonSpaceIdx;
  195. headerParsingState.state = 2;
  196. break;
  197. }
  198. case 2: { // Scan chunk size
  199. int offset = headerParsingState.offset;
  200. int limit = Math.min(headerParsingState.packetLimit, input.limit());
  201. long value = headerParsingState.parsingNumericValue;
  202. while (offset < limit) {
  203. final byte b = input.get(offset);
  204. if (isSpaceOrTab(b) || /*trailing spaces are not allowed by the spec, but some server put it there*/
  205. b == HttpParserUtils.CR || b == HttpParserUtils.SEMI_COLON) {
  206. headerParsingState.checkpoint = offset;
  207. } else if (b == HttpParserUtils.LF) {
  208. contentParsingState.chunkContentStart = offset + 1;
  209. contentParsingState.chunkLength = value;
  210. contentParsingState.chunkRemainder = value;
  211. headerParsingState.state = CHUNK_LENGTH_PARSED_STATE;
  212. return true;
  213. } else if (headerParsingState.checkpoint == -1) {
  214. if (DEC[b & 0xFF] != -1 && checkOverflow(value)) {
  215. value = (value << 4) + (DEC[b & 0xFF]);
  216. } else {
  217. throw new ParseException(
  218. LocalizationMessages.HTTP_INVALID_CHUNK_SIZE_HEX_VALUE(b));
  219. }
  220. } else {
  221. throw new ParseException(LocalizationMessages.HTTP_UNEXPECTED_CHUNK_HEADER());
  222. }
  223. offset++;
  224. }
  225. headerParsingState.parsingNumericValue = value;
  226. headerParsingState.offset = offset;
  227. headerParsingState.checkOverflow(LocalizationMessages.HTTP_CHUNK_ENCODING_PREFIX_OVERFLOW());
  228. return false;
  229. }
  230. }
  231. }
  232. }
  233. // Taken with small modifications from Grizzly ChunkedTransferEncoding.parseTrailerCRLF
  234. private boolean parseTrailerCRLF(ByteBuffer input) {
  235. if (headerParsingState.state == CHUNK_LENGTH_PARSED_STATE) {
  236. while (input.hasRemaining()) {
  237. if (input.get() == HttpParserUtils.LF) {
  238. headerParsingState.recycle();
  239. return input.hasRemaining();
  240. }
  241. }
  242. return false;
  243. }
  244. return true;
  245. }
  246. /**
  247. * @return <tt>false</tt> if next left bit-shift by 4 bits will cause overflow,
  248. * or <tt>true</tt> otherwise
  249. */
  250. private boolean checkOverflow(final long chunkLength) {
  251. return chunkLength <= CHUNK_SIZE_OVERFLOW;
  252. }
  253. private void initTrailerParsing() {
  254. headerParsingState.subState = 0;
  255. final int start = contentParsingState.chunkContentStart;
  256. headerParsingState.start = start;
  257. headerParsingState.offset = start;
  258. headerParsingState.packetLimit = start + maxHeadersSize;
  259. }
  260. // Taken with small modifications from Grizzly ChunkedTransferEncoding.parseLastChunkTrailer
  261. private boolean parseLastChunkTrailer(final ByteBuffer input) throws ParseException {
  262. boolean result = httpParser.parseHeadersFromBuffer(input, true);
  263. if (!result) {
  264. headerParsingState.checkOverflow(LocalizationMessages.HTTP_TRAILER_HEADER_OVERFLOW());
  265. }
  266. return result;
  267. }
  268. }
  269. }