1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.jboss.netty.handler.codec.http.multipart;
17
18 import org.jboss.netty.buffer.ChannelBuffer;
19 import org.jboss.netty.buffer.ChannelBuffers;
20 import org.jboss.netty.handler.codec.http.HttpChunk;
21 import org.jboss.netty.handler.codec.http.HttpConstants;
22 import org.jboss.netty.handler.codec.http.HttpRequest;
23 import org.jboss.netty.handler.codec.http.multipart.HttpPostBodyUtil.SeekAheadNoBackArrayException;
24 import org.jboss.netty.handler.codec.http.multipart.HttpPostBodyUtil.SeekAheadOptimize;
25 import org.jboss.netty.handler.codec.http.multipart.HttpPostRequestDecoder.EndOfDataDecoderException;
26 import org.jboss.netty.handler.codec.http.multipart.HttpPostRequestDecoder.ErrorDataDecoderException;
27 import org.jboss.netty.handler.codec.http.multipart.HttpPostRequestDecoder.MultiPartStatus;
28 import org.jboss.netty.handler.codec.http.multipart.HttpPostRequestDecoder.NotEnoughDataDecoderException;
29 import org.jboss.netty.util.internal.CaseIgnoringComparator;
30
31 import java.io.IOException;
32 import java.io.UnsupportedEncodingException;
33 import java.net.URLDecoder;
34 import java.nio.charset.Charset;
35 import java.util.ArrayList;
36 import java.util.List;
37 import java.util.Map;
38 import java.util.TreeMap;
39
40
41
42
43 public class HttpPostStandardRequestDecoder implements InterfaceHttpPostRequestDecoder {
44
45
46
47 private final HttpDataFactory factory;
48
49
50
51
52 private final HttpRequest request;
53
54
55
56
57 private final Charset charset;
58
59
60
61
62 private boolean isLastChunk;
63
64
65
66
67 private final List<InterfaceHttpData> bodyListHttpData = new ArrayList<InterfaceHttpData>();
68
69
70
71
72 private final Map<String, List<InterfaceHttpData>> bodyMapHttpData = new TreeMap<String, List<InterfaceHttpData>>(
73 CaseIgnoringComparator.INSTANCE);
74
75
76
77
78 private ChannelBuffer undecodedChunk;
79
80
81
82
83 private int bodyListHttpDataRank;
84
85
86
87
88 private MultiPartStatus currentStatus = MultiPartStatus.NOTSTARTED;
89
90
91
92
93 private Attribute currentAttribute;
94
95
96
97
98
99
100
101 public HttpPostStandardRequestDecoder(HttpRequest request) throws ErrorDataDecoderException {
102 this(new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE),
103 request, HttpConstants.DEFAULT_CHARSET);
104 }
105
106
107
108
109
110
111
112
113 public HttpPostStandardRequestDecoder(HttpDataFactory factory, HttpRequest request)
114 throws ErrorDataDecoderException {
115 this(factory, request, HttpConstants.DEFAULT_CHARSET);
116 }
117
118
119
120
121
122
123
124
125
126 public HttpPostStandardRequestDecoder(HttpDataFactory factory, HttpRequest request,
127 Charset charset) throws ErrorDataDecoderException {
128 if (factory == null) {
129 throw new NullPointerException("factory");
130 }
131 if (request == null) {
132 throw new NullPointerException("request");
133 }
134 if (charset == null) {
135 throw new NullPointerException("charset");
136 }
137 this.request = request;
138 this.charset = charset;
139 this.factory = factory;
140 if (!this.request.isChunked()) {
141 undecodedChunk = this.request.getContent();
142 isLastChunk = true;
143 parseBody();
144 }
145 }
146
147 public boolean isMultipart() {
148 return false;
149 }
150
151 public List<InterfaceHttpData> getBodyHttpDatas()
152 throws NotEnoughDataDecoderException {
153 if (!isLastChunk) {
154 throw new NotEnoughDataDecoderException();
155 }
156 return bodyListHttpData;
157 }
158
159 public List<InterfaceHttpData> getBodyHttpDatas(String name)
160 throws NotEnoughDataDecoderException {
161 if (!isLastChunk) {
162 throw new NotEnoughDataDecoderException();
163 }
164 return bodyMapHttpData.get(name);
165 }
166
167 public InterfaceHttpData getBodyHttpData(String name)
168 throws NotEnoughDataDecoderException {
169 if (!isLastChunk) {
170 throw new NotEnoughDataDecoderException();
171 }
172 List<InterfaceHttpData> list = bodyMapHttpData.get(name);
173 if (list != null) {
174 return list.get(0);
175 }
176 return null;
177 }
178
179 public void offer(HttpChunk chunk) throws ErrorDataDecoderException {
180 ChannelBuffer chunked = chunk.getContent();
181 if (undecodedChunk == null) {
182 undecodedChunk = chunked;
183 } else {
184
185
186 undecodedChunk = ChannelBuffers.wrappedBuffer(
187 undecodedChunk, chunked);
188 }
189 if (chunk.isLast()) {
190 isLastChunk = true;
191 }
192 parseBody();
193 }
194
195 public boolean hasNext() throws EndOfDataDecoderException {
196 if (currentStatus == MultiPartStatus.EPILOGUE) {
197
198 if (bodyListHttpDataRank >= bodyListHttpData.size()) {
199 throw new EndOfDataDecoderException();
200 }
201 }
202 return !bodyListHttpData.isEmpty() && bodyListHttpDataRank < bodyListHttpData.size();
203 }
204
205 public InterfaceHttpData next() throws EndOfDataDecoderException {
206 if (hasNext()) {
207 return bodyListHttpData.get(bodyListHttpDataRank++);
208 }
209 return null;
210 }
211
212
213
214
215
216
217 private void parseBody() throws ErrorDataDecoderException {
218 if (currentStatus == MultiPartStatus.PREEPILOGUE ||
219 currentStatus == MultiPartStatus.EPILOGUE) {
220 if (isLastChunk) {
221 currentStatus = MultiPartStatus.EPILOGUE;
222 }
223 return;
224 }
225 parseBodyAttributes();
226 }
227
228
229
230
231 private void addHttpData(InterfaceHttpData data) {
232 if (data == null) {
233 return;
234 }
235 List<InterfaceHttpData> datas = bodyMapHttpData.get(data.getName());
236 if (datas == null) {
237 datas = new ArrayList<InterfaceHttpData>(1);
238 bodyMapHttpData.put(data.getName(), datas);
239 }
240 datas.add(data);
241 bodyListHttpData.add(data);
242 }
243
244
245
246
247
248
249
250
251 private void parseBodyAttributesStandard() throws ErrorDataDecoderException {
252 int firstpos = undecodedChunk.readerIndex();
253 int currentpos = firstpos;
254 int equalpos;
255 int ampersandpos;
256 if (currentStatus == MultiPartStatus.NOTSTARTED) {
257 currentStatus = MultiPartStatus.DISPOSITION;
258 }
259 boolean contRead = true;
260 try {
261 while (undecodedChunk.readable() && contRead) {
262 char read = (char) undecodedChunk.readUnsignedByte();
263 currentpos++;
264 switch (currentStatus) {
265 case DISPOSITION:
266 if (read == '=') {
267 currentStatus = MultiPartStatus.FIELD;
268 equalpos = currentpos - 1;
269 String key = decodeAttribute(
270 undecodedChunk.toString(firstpos, equalpos - firstpos, charset),
271 charset);
272 currentAttribute = factory.createAttribute(request, key);
273 firstpos = currentpos;
274 } else if (read == '&') {
275 currentStatus = MultiPartStatus.DISPOSITION;
276 ampersandpos = currentpos - 1;
277 String key = decodeAttribute(
278 undecodedChunk.toString(firstpos, ampersandpos - firstpos, charset), charset);
279 currentAttribute = factory.createAttribute(request, key);
280 currentAttribute.setValue("");
281 addHttpData(currentAttribute);
282 currentAttribute = null;
283 firstpos = currentpos;
284 contRead = true;
285 }
286 break;
287 case FIELD:
288 if (read == '&') {
289 currentStatus = MultiPartStatus.DISPOSITION;
290 ampersandpos = currentpos - 1;
291 setFinalBuffer(undecodedChunk.slice(firstpos, ampersandpos - firstpos));
292 firstpos = currentpos;
293 contRead = true;
294 } else if (read == HttpConstants.CR) {
295 if (undecodedChunk.readable()) {
296 read = (char) undecodedChunk.readUnsignedByte();
297 currentpos++;
298 if (read == HttpConstants.LF) {
299 currentStatus = MultiPartStatus.PREEPILOGUE;
300 ampersandpos = currentpos - 2;
301 setFinalBuffer(
302 undecodedChunk.slice(firstpos, ampersandpos - firstpos));
303 firstpos = currentpos;
304 contRead = false;
305 } else {
306
307 throw new ErrorDataDecoderException("Bad end of line");
308 }
309 } else {
310 currentpos--;
311 }
312 } else if (read == HttpConstants.LF) {
313 currentStatus = MultiPartStatus.PREEPILOGUE;
314 ampersandpos = currentpos - 1;
315 setFinalBuffer(
316 undecodedChunk.slice(firstpos, ampersandpos - firstpos));
317 firstpos = currentpos;
318 contRead = false;
319 }
320 break;
321 default:
322
323 contRead = false;
324 }
325 }
326 if (isLastChunk && currentAttribute != null) {
327
328 ampersandpos = currentpos;
329 if (ampersandpos > firstpos) {
330 setFinalBuffer(
331 undecodedChunk.slice(firstpos, ampersandpos - firstpos));
332 } else if (! currentAttribute.isCompleted()) {
333 setFinalBuffer(ChannelBuffers.EMPTY_BUFFER);
334 }
335 firstpos = currentpos;
336 currentStatus = MultiPartStatus.EPILOGUE;
337 undecodedChunk.readerIndex(firstpos);
338 return;
339 }
340 if (contRead && currentAttribute != null) {
341
342 if (currentStatus == MultiPartStatus.FIELD) {
343 currentAttribute.addContent(
344 undecodedChunk.slice(firstpos, currentpos - firstpos),
345 false);
346 firstpos = currentpos;
347 }
348 undecodedChunk.readerIndex(firstpos);
349 } else {
350
351 undecodedChunk.readerIndex(firstpos);
352 }
353 } catch (ErrorDataDecoderException e) {
354
355 undecodedChunk.readerIndex(firstpos);
356 throw e;
357 } catch (IOException e) {
358
359 undecodedChunk.readerIndex(firstpos);
360 throw new ErrorDataDecoderException(e);
361 }
362 }
363
364
365
366
367
368
369
370
371 private void parseBodyAttributes() throws ErrorDataDecoderException {
372 SeekAheadOptimize sao;
373 try {
374 sao = new SeekAheadOptimize(undecodedChunk);
375 } catch (SeekAheadNoBackArrayException e1) {
376 parseBodyAttributesStandard();
377 return;
378 }
379 int firstpos = undecodedChunk.readerIndex();
380 int currentpos = firstpos;
381 int equalpos;
382 int ampersandpos;
383 if (currentStatus == MultiPartStatus.NOTSTARTED) {
384 currentStatus = MultiPartStatus.DISPOSITION;
385 }
386 boolean contRead = true;
387 try {
388 loop:
389 while (sao.pos < sao.limit) {
390 char read = (char) (sao.bytes[sao.pos ++] & 0xFF);
391 currentpos ++;
392 switch (currentStatus) {
393 case DISPOSITION:
394 if (read == '=') {
395 currentStatus = MultiPartStatus.FIELD;
396 equalpos = currentpos - 1;
397 String key = decodeAttribute(
398 undecodedChunk.toString(firstpos, equalpos - firstpos, charset),
399 charset);
400 currentAttribute = factory.createAttribute(request, key);
401 firstpos = currentpos;
402 } else if (read == '&') {
403 currentStatus = MultiPartStatus.DISPOSITION;
404 ampersandpos = currentpos - 1;
405 String key = decodeAttribute(
406 undecodedChunk.toString(firstpos, ampersandpos - firstpos, charset), charset);
407 currentAttribute = factory.createAttribute(request, key);
408 currentAttribute.setValue("");
409 addHttpData(currentAttribute);
410 currentAttribute = null;
411 firstpos = currentpos;
412 contRead = true;
413 }
414 break;
415 case FIELD:
416 if (read == '&') {
417 currentStatus = MultiPartStatus.DISPOSITION;
418 ampersandpos = currentpos - 1;
419 setFinalBuffer(undecodedChunk.slice(firstpos, ampersandpos - firstpos));
420 firstpos = currentpos;
421 contRead = true;
422 } else if (read == HttpConstants.CR) {
423 if (sao.pos < sao.limit) {
424 read = (char) (sao.bytes[sao.pos ++] & 0xFF);
425 currentpos++;
426 if (read == HttpConstants.LF) {
427 currentStatus = MultiPartStatus.PREEPILOGUE;
428 ampersandpos = currentpos - 2;
429 sao.setReadPosition(0);
430 setFinalBuffer(
431 undecodedChunk.slice(firstpos, ampersandpos - firstpos));
432 firstpos = currentpos;
433 contRead = false;
434 break loop;
435 } else {
436
437 sao.setReadPosition(0);
438 throw new ErrorDataDecoderException("Bad end of line");
439 }
440 } else {
441 if (sao.limit > 0) {
442 currentpos --;
443 }
444 }
445 } else if (read == HttpConstants.LF) {
446 currentStatus = MultiPartStatus.PREEPILOGUE;
447 ampersandpos = currentpos - 1;
448 sao.setReadPosition(0);
449 setFinalBuffer(
450 undecodedChunk.slice(firstpos, ampersandpos - firstpos));
451 firstpos = currentpos;
452 contRead = false;
453 break loop;
454 }
455 break;
456 default:
457
458 sao.setReadPosition(0);
459 contRead = false;
460 break loop;
461 }
462 }
463 if (isLastChunk && currentAttribute != null) {
464
465 ampersandpos = currentpos;
466 if (ampersandpos > firstpos) {
467 setFinalBuffer(
468 undecodedChunk.slice(firstpos, ampersandpos - firstpos));
469 } else if (! currentAttribute.isCompleted()) {
470 setFinalBuffer(ChannelBuffers.EMPTY_BUFFER);
471 }
472 firstpos = currentpos;
473 currentStatus = MultiPartStatus.EPILOGUE;
474 undecodedChunk.readerIndex(firstpos);
475 return;
476 }
477 if (contRead && currentAttribute != null) {
478
479 if (currentStatus == MultiPartStatus.FIELD) {
480 currentAttribute.addContent(
481 undecodedChunk.slice(firstpos, currentpos - firstpos),
482 false);
483 firstpos = currentpos;
484 }
485 undecodedChunk.readerIndex(firstpos);
486 } else {
487
488 undecodedChunk.readerIndex(firstpos);
489 }
490 } catch (ErrorDataDecoderException e) {
491
492 undecodedChunk.readerIndex(firstpos);
493 throw e;
494 } catch (IOException e) {
495
496 undecodedChunk.readerIndex(firstpos);
497 throw new ErrorDataDecoderException(e);
498 }
499 }
500
501 private void setFinalBuffer(ChannelBuffer buffer) throws ErrorDataDecoderException, IOException {
502 currentAttribute.addContent(buffer, true);
503 String value = decodeAttribute(
504 currentAttribute.getChannelBuffer().toString(charset),
505 charset);
506 currentAttribute.setValue(value);
507 addHttpData(currentAttribute);
508 currentAttribute = null;
509 }
510
511
512
513
514
515 private static String decodeAttribute(String s, Charset charset)
516 throws ErrorDataDecoderException {
517 if (s == null) {
518 return "";
519 }
520 try {
521 return URLDecoder.decode(s, charset.name());
522 } catch (UnsupportedEncodingException e) {
523 throw new ErrorDataDecoderException(charset.toString(), e);
524 } catch (IllegalArgumentException e) {
525 throw new ErrorDataDecoderException("Bad string: '" + s + '\'', e);
526 }
527 }
528
529 public void cleanFiles() {
530 factory.cleanRequestHttpDatas(request);
531 }
532
533 public void removeHttpDataFromClean(InterfaceHttpData data) {
534 factory.removeHttpDataFromClean(request, data);
535 }
536 }