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 }