1
17 package org.apache.tomcat.util.http;
18
19 import java.io.IOException;
20 import java.nio.charset.Charset;
21 import java.nio.charset.StandardCharsets;
22 import java.util.ArrayList;
23 import java.util.Collections;
24 import java.util.Enumeration;
25 import java.util.LinkedHashMap;
26 import java.util.Map;
27
28 import org.apache.juli.logging.Log;
29 import org.apache.juli.logging.LogFactory;
30 import org.apache.tomcat.util.buf.ByteChunk;
31 import org.apache.tomcat.util.buf.MessageBytes;
32 import org.apache.tomcat.util.buf.StringUtils;
33 import org.apache.tomcat.util.buf.UDecoder;
34 import org.apache.tomcat.util.log.UserDataHelper;
35 import org.apache.tomcat.util.res.StringManager;
36
37
41 public final class Parameters {
42
43 private static final Log log = LogFactory.getLog(Parameters.class);
44
45 private static final UserDataHelper userDataLog = new UserDataHelper(log);
46
47 private static final UserDataHelper maxParamCountLog = new UserDataHelper(log);
48
49 private static final StringManager sm =
50 StringManager.getManager("org.apache.tomcat.util.http");
51
52 private final Map<String,ArrayList<String>> paramHashValues =
53 new LinkedHashMap<>();
54 private boolean didQueryParameters=false;
55
56 private MessageBytes queryMB;
57
58 private UDecoder urlDec;
59 private final MessageBytes decodedQuery = MessageBytes.newInstance();
60
61 private Charset charset = StandardCharsets.ISO_8859_1;
62 private Charset queryStringCharset = StandardCharsets.UTF_8;
63
64 private int limit = -1;
65 private int parameterCount = 0;
66
67
71 private FailReason parseFailedReason = null;
72
73 public Parameters() {
74
75 }
76
77 public void setQuery( MessageBytes queryMB ) {
78 this.queryMB=queryMB;
79 }
80
81 public void setLimit(int limit) {
82 this.limit = limit;
83 }
84
85 public Charset getCharset() {
86 return charset;
87 }
88
89 public void setCharset(Charset charset) {
90 if (charset == null) {
91 charset = DEFAULT_BODY_CHARSET;
92 }
93 this.charset = charset;
94 if(log.isDebugEnabled()) {
95 log.debug("Set encoding to " + charset.name());
96 }
97 }
98
99 public void setQueryStringCharset(Charset queryStringCharset) {
100 if (queryStringCharset == null) {
101 queryStringCharset = DEFAULT_URI_CHARSET;
102 }
103 this.queryStringCharset = queryStringCharset;
104
105 if(log.isDebugEnabled()) {
106 log.debug("Set query string encoding to " + queryStringCharset.name());
107 }
108 }
109
110
111 public boolean isParseFailed() {
112 return parseFailedReason != null;
113 }
114
115
116 public FailReason getParseFailedReason() {
117 return parseFailedReason;
118 }
119
120
121 public void setParseFailedReason(FailReason failReason) {
122 if (this.parseFailedReason == null) {
123 this.parseFailedReason = failReason;
124 }
125 }
126
127
128 public void recycle() {
129 parameterCount = 0;
130 paramHashValues.clear();
131 didQueryParameters = false;
132 charset = DEFAULT_BODY_CHARSET;
133 decodedQuery.recycle();
134 parseFailedReason = null;
135 }
136
137
138
139
140
141
142 public String[] getParameterValues(String name) {
143 handleQueryParameters();
144
145 ArrayList<String> values = paramHashValues.get(name);
146 if (values == null) {
147 return null;
148 }
149 return values.toArray(new String[values.size()]);
150 }
151
152 public Enumeration<String> getParameterNames() {
153 handleQueryParameters();
154 return Collections.enumeration(paramHashValues.keySet());
155 }
156
157 public String getParameter(String name ) {
158 handleQueryParameters();
159 ArrayList<String> values = paramHashValues.get(name);
160 if (values != null) {
161 if(values.size() == 0) {
162 return "";
163 }
164 return values.get(0);
165 } else {
166 return null;
167 }
168 }
169
170
172 public void handleQueryParameters() {
173 if (didQueryParameters) {
174 return;
175 }
176
177 didQueryParameters = true;
178
179 if (queryMB == null || queryMB.isNull()) {
180 return;
181 }
182
183 if(log.isDebugEnabled()) {
184 log.debug("Decoding query " + decodedQuery + " " + queryStringCharset.name());
185 }
186
187 try {
188 decodedQuery.duplicate(queryMB);
189 } catch (IOException e) {
190
191 e.printStackTrace();
192 }
193 processParameters(decodedQuery, queryStringCharset);
194 }
195
196
197 public void addParameter( String key, String value )
198 throws IllegalStateException {
199
200 if( key==null ) {
201 return;
202 }
203
204 parameterCount ++;
205 if (limit > -1 && parameterCount > limit) {
206
207
208 setParseFailedReason(FailReason.TOO_MANY_PARAMETERS);
209 throw new IllegalStateException(sm.getString(
210 "parameters.maxCountFail", Integer.valueOf(limit)));
211 }
212
213 ArrayList<String> values = paramHashValues.get(key);
214 if (values == null) {
215 values = new ArrayList<>(1);
216 paramHashValues.put(key, values);
217 }
218 values.add(value);
219 }
220
221 public void setURLDecoder( UDecoder u ) {
222 urlDec=u;
223 }
224
225
226
227
228 private final ByteChunk tmpName=new ByteChunk();
229 private final ByteChunk tmpValue=new ByteChunk();
230 private final ByteChunk origName=new ByteChunk();
231 private final ByteChunk origValue=new ByteChunk();
232 private static final Charset DEFAULT_BODY_CHARSET = StandardCharsets.ISO_8859_1;
233 private static final Charset DEFAULT_URI_CHARSET = StandardCharsets.UTF_8;
234
235
236 public void processParameters( byte bytes[], int start, int len ) {
237 processParameters(bytes, start, len, charset);
238 }
239
240 private void processParameters(byte bytes[], int start, int len, Charset charset) {
241
242 if(log.isDebugEnabled()) {
243 log.debug(sm.getString("parameters.bytes",
244 new String(bytes, start, len, DEFAULT_BODY_CHARSET)));
245 }
246
247 int decodeFailCount = 0;
248
249 int pos = start;
250 int end = start + len;
251
252 while(pos < end) {
253 int nameStart = pos;
254 int nameEnd = -1;
255 int valueStart = -1;
256 int valueEnd = -1;
257
258 boolean parsingName = true;
259 boolean decodeName = false;
260 boolean decodeValue = false;
261 boolean parameterComplete = false;
262
263 do {
264 switch(bytes[pos]) {
265 case '=':
266 if (parsingName) {
267
268 nameEnd = pos;
269 parsingName = false;
270 valueStart = ++pos;
271 } else {
272
273 pos++;
274 }
275 break;
276 case '&':
277 if (parsingName) {
278
279 nameEnd = pos;
280 } else {
281
282 valueEnd = pos;
283 }
284 parameterComplete = true;
285 pos++;
286 break;
287 case '%':
288 case '+':
289
290 if (parsingName) {
291 decodeName = true;
292 } else {
293 decodeValue = true;
294 }
295 pos ++;
296 break;
297 default:
298 pos ++;
299 break;
300 }
301 } while (!parameterComplete && pos < end);
302
303 if (pos == end) {
304 if (nameEnd == -1) {
305 nameEnd = pos;
306 } else if (valueStart > -1 && valueEnd == -1){
307 valueEnd = pos;
308 }
309 }
310
311 if (log.isDebugEnabled() && valueStart == -1) {
312 log.debug(sm.getString("parameters.noequal",
313 Integer.valueOf(nameStart), Integer.valueOf(nameEnd),
314 new String(bytes, nameStart, nameEnd-nameStart, DEFAULT_BODY_CHARSET)));
315 }
316
317 if (nameEnd <= nameStart ) {
318 if (valueStart == -1) {
319
320 if (log.isDebugEnabled()) {
321 log.debug(sm.getString("parameters.emptyChunk"));
322 }
323
324 continue;
325 }
326
327 UserDataHelper.Mode logMode = userDataLog.getNextMode();
328 if (logMode != null) {
329 String extract;
330 if (valueEnd > nameStart) {
331 extract = new String(bytes, nameStart, valueEnd - nameStart,
332 DEFAULT_BODY_CHARSET);
333 } else {
334 extract = "";
335 }
336 String message = sm.getString("parameters.invalidChunk",
337 Integer.valueOf(nameStart),
338 Integer.valueOf(valueEnd), extract);
339 switch (logMode) {
340 case INFO_THEN_DEBUG:
341 message += sm.getString("parameters.fallToDebug");
342
343 case INFO:
344 log.info(message);
345 break;
346 case DEBUG:
347 log.debug(message);
348 }
349 }
350 setParseFailedReason(FailReason.NO_NAME);
351 continue;
352
353 }
354
355 tmpName.setBytes(bytes, nameStart, nameEnd - nameStart);
356 if (valueStart >= 0) {
357 tmpValue.setBytes(bytes, valueStart, valueEnd - valueStart);
358 } else {
359 tmpValue.setBytes(bytes, 0, 0);
360 }
361
362
363
364
365 if (log.isDebugEnabled()) {
366 try {
367 origName.append(bytes, nameStart, nameEnd - nameStart);
368 if (valueStart >= 0) {
369 origValue.append(bytes, valueStart, valueEnd - valueStart);
370 } else {
371 origValue.append(bytes, 0, 0);
372 }
373 } catch (IOException ioe) {
374
375 log.error(sm.getString("parameters.copyFail"), ioe);
376 }
377 }
378
379 try {
380 String name;
381 String value;
382
383 if (decodeName) {
384 urlDecode(tmpName);
385 }
386 tmpName.setCharset(charset);
387 name = tmpName.toString();
388
389 if (valueStart >= 0) {
390 if (decodeValue) {
391 urlDecode(tmpValue);
392 }
393 tmpValue.setCharset(charset);
394 value = tmpValue.toString();
395 } else {
396 value = "";
397 }
398
399 try {
400 addParameter(name, value);
401 } catch (IllegalStateException ise) {
402
403
404 UserDataHelper.Mode logMode = maxParamCountLog.getNextMode();
405 if (logMode != null) {
406 String message = ise.getMessage();
407 switch (logMode) {
408 case INFO_THEN_DEBUG:
409 message += sm.getString(
410 "parameters.maxCountFail.fallToDebug");
411
412 case INFO:
413 log.info(message);
414 break;
415 case DEBUG:
416 log.debug(message);
417 }
418 }
419 break;
420 }
421 } catch (IOException e) {
422 setParseFailedReason(FailReason.URL_DECODING);
423 decodeFailCount++;
424 if (decodeFailCount == 1 || log.isDebugEnabled()) {
425 if (log.isDebugEnabled()) {
426 log.debug(sm.getString("parameters.decodeFail.debug",
427 origName.toString(), origValue.toString()), e);
428 } else if (log.isInfoEnabled()) {
429 UserDataHelper.Mode logMode = userDataLog.getNextMode();
430 if (logMode != null) {
431 String message = sm.getString(
432 "parameters.decodeFail.info",
433 tmpName.toString(), tmpValue.toString());
434 switch (logMode) {
435 case INFO_THEN_DEBUG:
436 message += sm.getString("parameters.fallToDebug");
437
438 case INFO:
439 log.info(message);
440 break;
441 case DEBUG:
442 log.debug(message);
443 }
444 }
445 }
446 }
447 }
448
449 tmpName.recycle();
450 tmpValue.recycle();
451
452 if (log.isDebugEnabled()) {
453 origName.recycle();
454 origValue.recycle();
455 }
456 }
457
458 if (decodeFailCount > 1 && !log.isDebugEnabled()) {
459 UserDataHelper.Mode logMode = userDataLog.getNextMode();
460 if (logMode != null) {
461 String message = sm.getString(
462 "parameters.multipleDecodingFail",
463 Integer.valueOf(decodeFailCount));
464 switch (logMode) {
465 case INFO_THEN_DEBUG:
466 message += sm.getString("parameters.fallToDebug");
467
468 case INFO:
469 log.info(message);
470 break;
471 case DEBUG:
472 log.debug(message);
473 }
474 }
475 }
476 }
477
478 private void urlDecode(ByteChunk bc)
479 throws IOException {
480 if( urlDec==null ) {
481 urlDec=new UDecoder();
482 }
483 urlDec.convert(bc, true);
484 }
485
486 public void processParameters(MessageBytes data, Charset charset) {
487 if( data==null || data.isNull() || data.getLength() <= 0 ) {
488 return;
489 }
490
491 if( data.getType() != MessageBytes.T_BYTES ) {
492 data.toBytes();
493 }
494 ByteChunk bc=data.getByteChunk();
495 processParameters(bc.getBytes(), bc.getOffset(), bc.getLength(), charset);
496 }
497
498
501 @Override
502 public String toString() {
503 StringBuilder sb = new StringBuilder();
504 for (Map.Entry<String, ArrayList<String>> e : paramHashValues.entrySet()) {
505 sb.append(e.getKey()).append('=');
506 StringUtils.join(e.getValue(), ',', sb);
507 sb.append('\n');
508 }
509 return sb.toString();
510 }
511
512
513 public enum FailReason {
514 CLIENT_DISCONNECT,
515 MULTIPART_CONFIG_INVALID,
516 INVALID_CONTENT_TYPE,
517 IO_ERROR,
518 NO_NAME,
519 POST_TOO_LARGE,
520 REQUEST_BODY_INCOMPLETE,
521 TOO_MANY_PARAMETERS,
522 UNKNOWN,
523 URL_DECODING
524 }
525 }
526