View Javadoc
1   /*
2    * Copyright 2012 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://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, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package io.netty.handler.codec.http.multipart;
17  
18  import io.netty.buffer.ByteBuf;
19  import io.netty.handler.codec.http.HttpConstants;
20  
21  /**
22   * Shared Static object between HttpMessageDecoder, HttpPostRequestDecoder and HttpPostRequestEncoder
23   */
24  final class HttpPostBodyUtil {
25  
26      public static final int chunkSize = 8096;
27  
28      /**
29       * Default Content-Type in binary form
30       */
31      public static final String DEFAULT_BINARY_CONTENT_TYPE = "application/octet-stream";
32  
33      /**
34       * Default Content-Type in Text form
35       */
36      public static final String DEFAULT_TEXT_CONTENT_TYPE = "text/plain";
37  
38      /**
39       * Allowed mechanism for multipart
40       * mechanism := "7bit"
41                    / "8bit"
42                    / "binary"
43         Not allowed: "quoted-printable"
44                    / "base64"
45       */
46      public enum TransferEncodingMechanism {
47          /**
48           * Default encoding
49           */
50          BIT7("7bit"),
51          /**
52           * Short lines but not in ASCII - no encoding
53           */
54          BIT8("8bit"),
55          /**
56           * Could be long text not in ASCII - no encoding
57           */
58          BINARY("binary");
59  
60          private final String value;
61  
62          TransferEncodingMechanism(String value) {
63              this.value = value;
64          }
65  
66          public String value() {
67              return value;
68          }
69  
70          @Override
71          public String toString() {
72              return value;
73          }
74      }
75  
76      private HttpPostBodyUtil() {
77      }
78  
79      /**
80      * This class intends to decrease the CPU in seeking ahead some bytes in
81      * HttpPostRequestDecoder
82      */
83      static class SeekAheadOptimize {
84          byte[] bytes;
85          int readerIndex;
86          int pos;
87          int origPos;
88          int limit;
89          ByteBuf buffer;
90  
91          /**
92           * @param buffer buffer with a backing byte array
93           */
94          SeekAheadOptimize(ByteBuf buffer) {
95              if (!buffer.hasArray()) {
96                  throw new IllegalArgumentException("buffer hasn't backing byte array");
97              }
98              this.buffer = buffer;
99              bytes = buffer.array();
100             readerIndex = buffer.readerIndex();
101             origPos = pos = buffer.arrayOffset() + readerIndex;
102             limit = buffer.arrayOffset() + buffer.writerIndex();
103         }
104 
105         /**
106         *
107         * @param minus this value will be used as (currentPos - minus) to set
108         * the current readerIndex in the buffer.
109         */
110         void setReadPosition(int minus) {
111             pos -= minus;
112             readerIndex = getReadPosition(pos);
113             buffer.readerIndex(readerIndex);
114         }
115 
116         /**
117         *
118         * @param index raw index of the array (pos in general)
119         * @return the value equivalent of raw index to be used in readerIndex(value)
120         */
121         int getReadPosition(int index) {
122             return index - origPos + readerIndex;
123         }
124     }
125 
126     /**
127      * Find the first non whitespace
128      * @return the rank of the first non whitespace
129      */
130     static int findNonWhitespace(String sb, int offset) {
131         int result;
132         for (result = offset; result < sb.length(); result ++) {
133             if (!Character.isWhitespace(sb.charAt(result))) {
134                 break;
135             }
136         }
137         return result;
138     }
139 
140     /**
141      * Find the end of String
142      * @return the rank of the end of string
143      */
144     static int findEndOfString(String sb) {
145         int result;
146         for (result = sb.length(); result > 0; result --) {
147             if (!Character.isWhitespace(sb.charAt(result - 1))) {
148                 break;
149             }
150         }
151         return result;
152     }
153 
154     /**
155      * Try to find first LF or CRLF as Line Breaking
156      *
157      * @param buffer the buffer to search in
158      * @param index the index to start from in the buffer
159      * @return a relative position from index > 0 if LF or CRLF is found
160      *         or < 0 if not found
161      */
162     static int findLineBreak(ByteBuf buffer, int index) {
163         int toRead = buffer.readableBytes() - (index - buffer.readerIndex());
164         int posFirstChar = buffer.bytesBefore(index, toRead, HttpConstants.LF);
165         if (posFirstChar == -1) {
166             // No LF, so neither CRLF
167             return -1;
168         }
169         if (posFirstChar > 0 && buffer.getByte(index + posFirstChar - 1) == HttpConstants.CR) {
170             posFirstChar--;
171         }
172         return posFirstChar;
173     }
174 
175     /**
176      * Try to find last LF or CRLF as Line Breaking
177      *
178      * @param buffer the buffer to search in
179      * @param index the index to start from in the buffer
180      * @return a relative position from index > 0 if LF or CRLF is found
181      *         or < 0 if not found
182      */
183     static int findLastLineBreak(ByteBuf buffer, int index) {
184         int candidate = findLineBreak(buffer, index);
185         int findCRLF = 0;
186         if (candidate >= 0) {
187             if (buffer.getByte(index + candidate) == HttpConstants.CR) {
188                 findCRLF = 2;
189             } else {
190                 findCRLF = 1;
191             }
192             candidate += findCRLF;
193         }
194         int next;
195         while (candidate > 0 && (next = findLineBreak(buffer, index + candidate)) >= 0) {
196             candidate += next;
197             if (buffer.getByte(index + candidate) == HttpConstants.CR) {
198                 findCRLF = 2;
199             } else {
200                 findCRLF = 1;
201             }
202             candidate += findCRLF;
203         }
204         return candidate - findCRLF;
205     }
206 
207     /**
208      * Try to find the delimiter, with LF or CRLF in front of it (added as delimiters) if needed
209      *
210      * @param buffer the buffer to search in
211      * @param index the index to start from in the buffer
212      * @param delimiter the delimiter as byte array
213      * @param precededByLineBreak true if it must be preceded by LF or CRLF, else false
214      * @return a relative position from index > 0 if delimiter found designing the start of it
215      *         (including LF or CRLF is asked)
216      *         or a number < 0 if delimiter is not found
217      * @throws IndexOutOfBoundsException
218      *         if {@code offset + delimiter.length} is greater than {@code buffer.capacity}
219      */
220     static int findDelimiter(ByteBuf buffer, int index, byte[] delimiter, boolean precededByLineBreak) {
221         final int delimiterLength = delimiter.length;
222         final int readerIndex = buffer.readerIndex();
223         final int writerIndex = buffer.writerIndex();
224         int toRead = writerIndex - index;
225         int newOffset = index;
226         boolean delimiterNotFound = true;
227         while (delimiterNotFound && delimiterLength <= toRead) {
228             // Find first position: delimiter
229             int posDelimiter = buffer.bytesBefore(newOffset, toRead, delimiter[0]);
230             if (posDelimiter < 0) {
231                 return -1;
232             }
233             newOffset += posDelimiter;
234             toRead -= posDelimiter;
235             // Now check for delimiter
236             if (toRead >= delimiterLength) {
237                 delimiterNotFound = false;
238                 for (int i = 0; i < delimiterLength; i++) {
239                     if (buffer.getByte(newOffset + i) != delimiter[i]) {
240                         newOffset++;
241                         toRead--;
242                         delimiterNotFound = true;
243                         break;
244                     }
245                 }
246             }
247             if (!delimiterNotFound) {
248                 // Delimiter found, find if necessary: LF or CRLF
249                 if (precededByLineBreak && newOffset > readerIndex) {
250                     if (buffer.getByte(newOffset - 1) == HttpConstants.LF) {
251                         newOffset--;
252                         // Check if CR before: not mandatory to be there
253                         if (newOffset > readerIndex && buffer.getByte(newOffset - 1) == HttpConstants.CR) {
254                             newOffset--;
255                         }
256                     } else {
257                         // Delimiter with Line Break could be further: iterate after first char of delimiter
258                         newOffset++;
259                         toRead--;
260                         delimiterNotFound = true;
261                         continue;
262                     }
263                 }
264                 return newOffset - readerIndex;
265             }
266         }
267         return -1;
268     }
269 }