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