001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: package org.apache.tomcat.util.http;
019:
020: import java.io.IOException;
021: import java.util.Enumeration;
022: import java.util.Hashtable;
023:
024: import org.apache.tomcat.util.buf.ByteChunk;
025: import org.apache.tomcat.util.buf.CharChunk;
026: import org.apache.tomcat.util.buf.MessageBytes;
027: import org.apache.tomcat.util.buf.UDecoder;
028: import org.apache.tomcat.util.collections.MultiMap;
029:
030: /**
031: *
032: * @author Costin Manolache
033: */
034: public final class Parameters extends MultiMap {
035:
036: private static org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory
037: .getLog(Parameters.class);
038:
039: // Transition: we'll use the same Hashtable( String->String[] )
040: // for the beginning. When we are sure all accesses happen through
041: // this class - we can switch to MultiMap
042: private Hashtable<String, String[]> paramHashStringArray = new Hashtable<String, String[]>();
043: private boolean didQueryParameters = false;
044: private boolean didMerge = false;
045:
046: MessageBytes queryMB;
047: MimeHeaders headers;
048:
049: UDecoder urlDec;
050: MessageBytes decodedQuery = MessageBytes.newInstance();
051:
052: public static final int INITIAL_SIZE = 4;
053:
054: // Garbage-less parameter merging.
055: // In a sub-request with parameters, the new parameters
056: // will be stored in child. When a getParameter happens,
057: // the 2 are merged togheter. The child will be altered
058: // to contain the merged values - the parent is allways the
059: // original request.
060: private Parameters child = null;
061: private Parameters parent = null;
062: private Parameters currentChild = null;
063:
064: String encoding = null;
065: String queryStringEncoding = null;
066:
067: /**
068: *
069: */
070: public Parameters() {
071: super (INITIAL_SIZE);
072: }
073:
074: public void setQuery(MessageBytes queryMB) {
075: this .queryMB = queryMB;
076: }
077:
078: public void setHeaders(MimeHeaders headers) {
079: this .headers = headers;
080: }
081:
082: public void setEncoding(String s) {
083: encoding = s;
084: if (debug > 0)
085: log("Set encoding to " + s);
086: }
087:
088: public void setQueryStringEncoding(String s) {
089: queryStringEncoding = s;
090: if (debug > 0)
091: log("Set query string encoding to " + s);
092: }
093:
094: public void recycle() {
095: super .recycle();
096: paramHashStringArray.clear();
097: didQueryParameters = false;
098: currentChild = null;
099: didMerge = false;
100: encoding = null;
101: decodedQuery.recycle();
102: }
103:
104: // -------------------- Sub-request support --------------------
105:
106: public Parameters getCurrentSet() {
107: if (currentChild == null)
108: return this ;
109: return currentChild;
110: }
111:
112: /** Create ( or reuse ) a child that will be used during a sub-request.
113: All future changes ( setting query string, adding parameters )
114: will affect the child ( the parent request is never changed ).
115: Both setters and getters will return the data from the deepest
116: child, merged with data from parents.
117: */
118: public void push() {
119: // We maintain a linked list, that will grow to the size of the
120: // longest include chain.
121: // The list has 2 points of interest:
122: // - request.parameters() is the original request and head,
123: // - request.parameters().currentChild() is the current set.
124: // The ->child and parent<- links are preserved ( currentChild is not
125: // the last in the list )
126:
127: // create a new element in the linked list
128: // note that we reuse the child, if any - pop will not
129: // set child to null !
130: if (currentChild == null) {
131: currentChild = new Parameters();
132: currentChild.setURLDecoder(urlDec);
133: currentChild.parent = this ;
134: return;
135: }
136: if (currentChild.child == null) {
137: currentChild.child = new Parameters();
138: currentChild.setURLDecoder(urlDec);
139: currentChild.child.parent = currentChild;
140: } // it is not null if this object already had a child
141: // i.e. a deeper include() ( we keep it )
142:
143: // the head will be the new element.
144: currentChild = currentChild.child;
145: currentChild.setEncoding(encoding);
146: }
147:
148: /** Discard the last child. This happens when we return from a
149: sub-request and the parameters are locally modified.
150: */
151: public void pop() {
152: if (currentChild == null) {
153: throw new RuntimeException("Attempt to pop without a push");
154: }
155: currentChild.recycle();
156: currentChild = currentChild.parent;
157: // don't remove the top.
158: }
159:
160: // -------------------- Data access --------------------
161: // Access to the current name/values, no side effect ( processing ).
162: // You must explicitely call handleQueryParameters and the post methods.
163:
164: // This is the original data representation ( hash of String->String[])
165:
166: public void addParameterValues(String key, String[] newValues) {
167: if (key == null)
168: return;
169: String values[];
170: if (paramHashStringArray.containsKey(key)) {
171: String oldValues[] = (String[]) paramHashStringArray
172: .get(key);
173: values = new String[oldValues.length + newValues.length];
174: for (int i = 0; i < oldValues.length; i++) {
175: values[i] = oldValues[i];
176: }
177: for (int i = 0; i < newValues.length; i++) {
178: values[i + oldValues.length] = newValues[i];
179: }
180: } else {
181: values = newValues;
182: }
183:
184: paramHashStringArray.put(key, values);
185: }
186:
187: public String[] getParameterValues(String name) {
188: handleQueryParameters();
189: // sub-request
190: if (currentChild != null) {
191: currentChild.merge();
192: return (String[]) currentChild.paramHashStringArray
193: .get(name);
194: }
195:
196: // no "facade"
197: String values[] = (String[]) paramHashStringArray.get(name);
198: return values;
199: }
200:
201: public Enumeration getParameterNames() {
202: handleQueryParameters();
203: // Slow - the original code
204: if (currentChild != null) {
205: currentChild.merge();
206: return currentChild.paramHashStringArray.keys();
207: }
208:
209: // merge in child
210: return paramHashStringArray.keys();
211: }
212:
213: /** Combine the parameters from parent with our local ones
214: */
215: private void merge() {
216: // recursive
217: if (debug > 0) {
218: log("Before merging " + this + " " + parent + " "
219: + didMerge);
220: log(paramsAsString());
221: }
222: // Local parameters first - they take precedence as in spec.
223: handleQueryParameters();
224:
225: // we already merged with the parent
226: if (didMerge)
227: return;
228:
229: // we are the top level
230: if (parent == null)
231: return;
232:
233: // Add the parent props to the child ( lower precedence )
234: parent.merge();
235: Hashtable<String, String[]> parentProps = parent.paramHashStringArray;
236: merge2(paramHashStringArray, parentProps);
237: didMerge = true;
238: if (debug > 0)
239: log("After " + paramsAsString());
240: }
241:
242: // Shortcut.
243: public String getParameter(String name) {
244: String[] values = getParameterValues(name);
245: if (values != null) {
246: if (values.length == 0)
247: return "";
248: return values[0];
249: } else {
250: return null;
251: }
252: }
253:
254: // -------------------- Processing --------------------
255: /** Process the query string into parameters
256: */
257: public void handleQueryParameters() {
258: if (didQueryParameters)
259: return;
260:
261: didQueryParameters = true;
262:
263: if (queryMB == null || queryMB.isNull())
264: return;
265:
266: if (debug > 0)
267: log("Decoding query " + decodedQuery + " "
268: + queryStringEncoding);
269:
270: try {
271: decodedQuery.duplicate(queryMB);
272: } catch (IOException e) {
273: // Can't happen, as decodedQuery can't overflow
274: e.printStackTrace();
275: }
276: processParameters(decodedQuery, queryStringEncoding);
277: }
278:
279: // --------------------
280:
281: /** Combine 2 hashtables into a new one.
282: * ( two will be added to one ).
283: * Used to combine child parameters ( RequestDispatcher's query )
284: * with parent parameters ( original query or parent dispatcher )
285: */
286: private static void merge2(Hashtable<String, String[]> one,
287: Hashtable<String, String[]> two) {
288: Enumeration e = two.keys();
289:
290: while (e.hasMoreElements()) {
291: String name = (String) e.nextElement();
292: String[] oneValue = one.get(name);
293: String[] twoValue = two.get(name);
294: String[] combinedValue;
295:
296: if (twoValue == null) {
297: continue;
298: } else {
299: if (oneValue == null) {
300: combinedValue = new String[twoValue.length];
301: System.arraycopy(twoValue, 0, combinedValue, 0,
302: twoValue.length);
303: } else {
304: combinedValue = new String[oneValue.length
305: + twoValue.length];
306: System.arraycopy(oneValue, 0, combinedValue, 0,
307: oneValue.length);
308: System.arraycopy(twoValue, 0, combinedValue,
309: oneValue.length, twoValue.length);
310: }
311: one.put(name, combinedValue);
312: }
313: }
314: }
315:
316: // incredibly inefficient data representation for parameters,
317: // until we test the new one
318: private void addParam(String key, String value) {
319: if (key == null)
320: return;
321: String values[];
322: if (paramHashStringArray.containsKey(key)) {
323: String oldValues[] = (String[]) paramHashStringArray
324: .get(key);
325: values = new String[oldValues.length + 1];
326: for (int i = 0; i < oldValues.length; i++) {
327: values[i] = oldValues[i];
328: }
329: values[oldValues.length] = value;
330: } else {
331: values = new String[1];
332: values[0] = value;
333: }
334:
335: paramHashStringArray.put(key, values);
336: }
337:
338: public void setURLDecoder(UDecoder u) {
339: urlDec = u;
340: }
341:
342: // -------------------- Parameter parsing --------------------
343:
344: // This code is not used right now - it's the optimized version
345: // of the above.
346:
347: // we are called from a single thread - we can do it the hard way
348: // if needed
349: ByteChunk tmpName = new ByteChunk();
350: ByteChunk tmpValue = new ByteChunk();
351: CharChunk tmpNameC = new CharChunk(1024);
352: CharChunk tmpValueC = new CharChunk(1024);
353:
354: public void processParameters(byte bytes[], int start, int len) {
355: processParameters(bytes, start, len, encoding);
356: }
357:
358: public void processParameters(byte bytes[], int start, int len,
359: String enc) {
360: int end = start + len;
361: int pos = start;
362:
363: if (debug > 0)
364: log("Bytes: " + new String(bytes, start, len));
365:
366: do {
367: boolean noEq = false;
368: int valStart = -1;
369: int valEnd = -1;
370:
371: int nameStart = pos;
372: int nameEnd = ByteChunk.indexOf(bytes, nameStart, end, '=');
373: // Workaround for a&b&c encoding
374: int nameEnd2 = ByteChunk
375: .indexOf(bytes, nameStart, end, '&');
376: if ((nameEnd2 != -1)
377: && (nameEnd == -1 || nameEnd > nameEnd2)) {
378: nameEnd = nameEnd2;
379: noEq = true;
380: valStart = nameEnd;
381: valEnd = nameEnd;
382: if (debug > 0)
383: log("no equal "
384: + nameStart
385: + " "
386: + nameEnd
387: + " "
388: + new String(bytes, nameStart, nameEnd
389: - nameStart));
390: }
391: if (nameEnd == -1)
392: nameEnd = end;
393:
394: if (!noEq) {
395: valStart = (nameEnd < end) ? nameEnd + 1 : end;
396: valEnd = ByteChunk.indexOf(bytes, valStart, end, '&');
397: if (valEnd == -1)
398: valEnd = (valStart < end) ? end : valStart;
399: }
400:
401: pos = valEnd + 1;
402:
403: if (nameEnd <= nameStart) {
404: log.warn("Parameters: Invalid chunk ignored.");
405: continue;
406: // invalid chunk - it's better to ignore
407: }
408: tmpName.setBytes(bytes, nameStart, nameEnd - nameStart);
409: tmpValue.setBytes(bytes, valStart, valEnd - valStart);
410:
411: try {
412: addParam(urlDecode(tmpName, enc), urlDecode(tmpValue,
413: enc));
414: } catch (IOException e) {
415: // Exception during character decoding: skip parameter
416: log.warn("Parameters: Character decoding failed. "
417: + "Parameter skipped.", e);
418: }
419:
420: tmpName.recycle();
421: tmpValue.recycle();
422:
423: } while (pos < end);
424: }
425:
426: private String urlDecode(ByteChunk bc, String enc)
427: throws IOException {
428: if (urlDec == null) {
429: urlDec = new UDecoder();
430: }
431: urlDec.convert(bc);
432: String result = null;
433: if (enc != null) {
434: bc.setEncoding(enc);
435: result = bc.toString();
436: } else {
437: CharChunk cc = tmpNameC;
438: int length = bc.getLength();
439: cc.allocate(length, -1);
440: // Default encoding: fast conversion
441: byte[] bbuf = bc.getBuffer();
442: char[] cbuf = cc.getBuffer();
443: int start = bc.getStart();
444: for (int i = 0; i < length; i++) {
445: cbuf[i] = (char) (bbuf[i + start] & 0xff);
446: }
447: cc.setChars(cbuf, 0, length);
448: result = cc.toString();
449: cc.recycle();
450: }
451: return result;
452: }
453:
454: public void processParameters(char chars[], int start, int len) {
455: int end = start + len;
456: int pos = start;
457:
458: if (debug > 0)
459: log("Chars: " + new String(chars, start, len));
460: do {
461: boolean noEq = false;
462: int nameStart = pos;
463: int valStart = -1;
464: int valEnd = -1;
465:
466: int nameEnd = CharChunk.indexOf(chars, nameStart, end, '=');
467: int nameEnd2 = CharChunk
468: .indexOf(chars, nameStart, end, '&');
469: if ((nameEnd2 != -1)
470: && (nameEnd == -1 || nameEnd > nameEnd2)) {
471: nameEnd = nameEnd2;
472: noEq = true;
473: valStart = nameEnd;
474: valEnd = nameEnd;
475: if (debug > 0)
476: log("no equal "
477: + nameStart
478: + " "
479: + nameEnd
480: + " "
481: + new String(chars, nameStart, nameEnd
482: - nameStart));
483: }
484: if (nameEnd == -1)
485: nameEnd = end;
486:
487: if (!noEq) {
488: valStart = (nameEnd < end) ? nameEnd + 1 : end;
489: valEnd = CharChunk.indexOf(chars, valStart, end, '&');
490: if (valEnd == -1)
491: valEnd = (valStart < end) ? end : valStart;
492: }
493:
494: pos = valEnd + 1;
495:
496: if (nameEnd <= nameStart) {
497: continue;
498: // invalid chunk - no name, it's better to ignore
499: // XXX log it ?
500: }
501:
502: try {
503: tmpNameC.append(chars, nameStart, nameEnd - nameStart);
504: tmpValueC.append(chars, valStart, valEnd - valStart);
505:
506: if (debug > 0)
507: log(tmpNameC + "= " + tmpValueC);
508:
509: if (urlDec == null) {
510: urlDec = new UDecoder();
511: }
512:
513: urlDec.convert(tmpNameC);
514: urlDec.convert(tmpValueC);
515:
516: if (debug > 0)
517: log(tmpNameC + "= " + tmpValueC);
518:
519: addParam(tmpNameC.toString(), tmpValueC.toString());
520: } catch (IOException ex) {
521: ex.printStackTrace();
522: }
523:
524: tmpNameC.recycle();
525: tmpValueC.recycle();
526:
527: } while (pos < end);
528: }
529:
530: public void processParameters(MessageBytes data) {
531: processParameters(data, encoding);
532: }
533:
534: public void processParameters(MessageBytes data, String encoding) {
535: if (data == null || data.isNull() || data.getLength() <= 0)
536: return;
537:
538: if (data.getType() == MessageBytes.T_BYTES) {
539: ByteChunk bc = data.getByteChunk();
540: processParameters(bc.getBytes(), bc.getOffset(), bc
541: .getLength(), encoding);
542: } else {
543: if (data.getType() != MessageBytes.T_CHARS)
544: data.toChars();
545: CharChunk cc = data.getCharChunk();
546: processParameters(cc.getChars(), cc.getOffset(), cc
547: .getLength());
548: }
549: }
550:
551: /** Debug purpose
552: */
553: public String paramsAsString() {
554: StringBuffer sb = new StringBuffer();
555: Enumeration en = paramHashStringArray.keys();
556: while (en.hasMoreElements()) {
557: String k = (String) en.nextElement();
558: sb.append(k).append("=");
559: String v[] = (String[]) paramHashStringArray.get(k);
560: for (int i = 0; i < v.length; i++)
561: sb.append(v[i]).append(",");
562: sb.append("\n");
563: }
564: return sb.toString();
565: }
566:
567: private static int debug = 0;
568:
569: private void log(String s) {
570: if (log.isDebugEnabled())
571: log.debug("Parameters: " + s);
572: }
573:
574: // -------------------- Old code, needs rewrite --------------------
575:
576: /** Used by RequestDispatcher
577: */
578: public void processParameters(String str) {
579: int end = str.length();
580: int pos = 0;
581: if (debug > 0)
582: log("String: " + str);
583:
584: do {
585: boolean noEq = false;
586: int valStart = -1;
587: int valEnd = -1;
588:
589: int nameStart = pos;
590: int nameEnd = str.indexOf('=', nameStart);
591: int nameEnd2 = str.indexOf('&', nameStart);
592: if (nameEnd2 == -1)
593: nameEnd2 = end;
594: if ((nameEnd2 != -1)
595: && (nameEnd == -1 || nameEnd > nameEnd2)) {
596: nameEnd = nameEnd2;
597: noEq = true;
598: valStart = nameEnd;
599: valEnd = nameEnd;
600: if (debug > 0)
601: log("no equal " + nameStart + " " + nameEnd + " "
602: + str.substring(nameStart, nameEnd));
603: }
604:
605: if (nameEnd == -1)
606: nameEnd = end;
607:
608: if (!noEq) {
609: valStart = nameEnd + 1;
610: valEnd = str.indexOf('&', valStart);
611: if (valEnd == -1)
612: valEnd = (valStart < end) ? end : valStart;
613: }
614:
615: pos = valEnd + 1;
616:
617: if (nameEnd <= nameStart) {
618: continue;
619: }
620: if (debug > 0)
621: log("XXX " + nameStart + " " + nameEnd + " " + valStart
622: + " " + valEnd);
623:
624: try {
625: tmpNameC.append(str, nameStart, nameEnd - nameStart);
626: tmpValueC.append(str, valStart, valEnd - valStart);
627:
628: if (debug > 0)
629: log(tmpNameC + "= " + tmpValueC);
630:
631: if (urlDec == null) {
632: urlDec = new UDecoder();
633: }
634:
635: urlDec.convert(tmpNameC);
636: urlDec.convert(tmpValueC);
637:
638: if (debug > 0)
639: log(tmpNameC + "= " + tmpValueC);
640:
641: addParam(tmpNameC.toString(), tmpValueC.toString());
642: } catch (IOException ex) {
643: ex.printStackTrace();
644: }
645:
646: tmpNameC.recycle();
647: tmpValueC.recycle();
648:
649: } while (pos < end);
650: }
651:
652: }
|