001: // URLDecoder.java
002: // $Id: URLDecoder.java,v 1.13 2004/10/13 20:59:48 ylafon Exp $
003: // (c) COPYRIGHT MIT and INRIA, 1996.
004: // Please first read the full copyright statement in file COPYRIGHT.html
005:
006: package org.w3c.jigsaw.forms;
007:
008: import java.io.IOException;
009: import java.io.InputStream;
010: import java.io.InputStreamReader;
011: import java.io.Reader;
012: import java.io.UnsupportedEncodingException;
013:
014: import java.util.Hashtable;
015: import java.util.Enumeration;
016:
017: /**
018: * Form data decoder.
019: * This class takes an InputStream and decodes it in compliance to the
020: * <b>application/x-www-form-urlencoded</b> MIME type.
021: */
022:
023: public class URLDecoder {
024: public final static String EMPTY_VALUE = "";
025:
026: int ch = -1;
027: Hashtable values = null;
028: byte buffer[] = new byte[1024];
029: int bsize = 0;
030: Reader in = null;
031: boolean overide = true;
032: String enc = null;
033:
034: private void append(int c) {
035: if (bsize + 1 >= buffer.length) {
036: byte nb[] = new byte[buffer.length * 2];
037: System.arraycopy(buffer, 0, nb, 0, buffer.length);
038: buffer = nb;
039: }
040: buffer[bsize++] = (byte) c;
041: }
042:
043: /**
044: * Get an enumeration of the variable names.
045: * @return An enumeration continaing one element per key.
046: */
047:
048: public Enumeration keys() {
049: return values.keys();
050: }
051:
052: /**
053: * Define a new variable in this data set.
054: * @param name The name of the variable.
055: * @param value Its value.
056: */
057:
058: protected void addVariable(String var, String val) {
059: if (overide) {
060: values.put(var, val);
061: } else {
062: Object value = values.get(var);
063: if (value == null) {
064: values.put(var, val);
065: } else if (value instanceof String[]) {
066: String olds[] = (String[]) value;
067: String vals[] = new String[olds.length + 1];
068: System.arraycopy(olds, 0, vals, 0, olds.length);
069: vals[olds.length] = val;
070: values.put(var, vals);
071: } else if (value instanceof String) {
072: String vals[] = new String[2];
073: vals[0] = (String) value;
074: vals[1] = val;
075: values.put(var, vals);
076: }
077: }
078: }
079:
080: /**
081: * Get the values of the variable, as an array.
082: * Use this method when you have turned off the <em>overide</em> flag
083: * in the constructor of this object. This will always return either an
084: * array of Strings or <strong>null</strong>.
085: * <p>I use this in the PICS label bureau, and I pretty sure this is not a
086: * good reason to have it here.
087: * @param name The name of the variable to look for.
088: * @return An String[] having one entry per variable's value, or <strong>
089: * null</strong> if none was found.
090: */
091:
092: public String[] getMultipleValues(String name) {
093: if (overide)
094: throw new RuntimeException(this .getClass().getName()
095: + "[getMultipleValues]: " + " overide not set !");
096: Object value = values.get(name);
097: if (value instanceof String[]) {
098: return (String[]) value;
099: } else {
100: String vals[] = new String[1];
101: vals[0] = (String) value;
102: values.put(name, vals);
103: return vals;
104: }
105: }
106:
107: /**
108: * Get the value of a variable.
109: * If you have allowed the decoder to accumulate multiple values for the
110: * same variable, this method casts of the value to a String may fail
111: * <em>at runtime</em>.
112: * @param name The name of the variable whose value is to be fetched.
113: * @return Its values, which is always provided as a String, or null.
114: */
115:
116: public String getValue(String name) {
117: Object value = values.get(name);
118: if ((value != null) && !(value instanceof String))
119: throw new RuntimeException(this .getClass().getName()
120: + "[getValue]:" + " use getMultipleValues in:\n\t"
121: + name + " " + value);
122: return (String) value;
123: }
124:
125: private String stringify(byte[] bytes, int offset, int length)
126: throws URLDecoderException {
127: String __s = null;
128: if (enc != null) {
129: try {
130: __s = new String(bytes, offset, length, enc);
131: } catch (UnsupportedEncodingException uex) {
132: throw new URLDecoderException("Unsupported Encoding: "
133: + enc);
134: }
135: } else {
136: __s = new String(bytes, offset, length);
137: }
138: return __s;
139: }
140:
141: /**
142: * Parse our input stream following the
143: * <b>application/x-www-form-urlencoded</b> specification.
144: * @return The raw bindings obtained from parsing the stream, as a
145: * Hashtable instance.
146: * @exception IOException When IO error occurs while reading the stream.
147: * @exception URLDecoderException If the format is invalid.
148: */
149:
150: public Hashtable parse() throws IOException, URLDecoderException {
151: String key = null;
152:
153: read_loop: ch = in.read();
154: while (true) {
155: switch (ch) {
156: case '+':
157: append(' ');
158: break;
159: case '%':
160: int hi,
161: lo;
162: if ((hi = ch = in.read()) == -1)
163: throw new URLDecoderException("Invalid escape seq.");
164: if ((lo = ch = in.read()) == -1)
165: throw new URLDecoderException("Invalid escape seq.");
166: hi = (Character.isDigit((char) hi) ? (hi - '0')
167: : 10 + (Character.toUpperCase((char) hi) - 'A'));
168: lo = (Character.isDigit((char) lo) ? (lo - '0')
169: : 10 + (Character.toUpperCase((char) lo) - 'A'));
170: append((char) (((byte) lo) | (((byte) hi) << 4)));
171: break;
172: case '&':
173: if (key == null) {
174: // We only get a simple key (with no value)
175: addVariable(stringify(buffer, 0, bsize),
176: EMPTY_VALUE);
177: bsize = 0;
178: } else {
179: addVariable(key, stringify(buffer, 0, bsize));
180: key = null;
181: bsize = 0;
182: }
183: break;
184: case ';': // HTML4.0: appendix b2.2: use of ";" in place of "&"
185: if (key == null) {
186: // We only get a simple key (with no value)
187: addVariable(stringify(buffer, 0, bsize),
188: EMPTY_VALUE);
189: bsize = 0;
190: } else {
191: addVariable(key, stringify(buffer, 0, bsize));
192: key = null;
193: bsize = 0;
194: }
195: break;
196: case '=':
197: if (key != null) {
198: append(ch);
199: } else {
200: key = stringify(buffer, 0, bsize);
201: bsize = 0;
202: }
203: break;
204: case -1:
205: // Same as '&', except that we return
206: if (key == null) {
207: // We only get a simple key (with no value)
208: addVariable(stringify(buffer, 0, bsize),
209: EMPTY_VALUE);
210: bsize = 0;
211: } else {
212: addVariable(key, stringify(buffer, 0, bsize));
213: key = null;
214: bsize = 0;
215: }
216: return values;
217: default:
218: append(ch);
219: break;
220: }
221: ch = in.read();
222: }
223: }
224:
225: /**
226: * Create an URLDecoder for the given stream.
227: * @param in The input stream to be parsed.
228: * @param list Tells how to handle multiple settings of the same variable.
229: * When <strong>false</strong>, mutiple settings to the same variable
230: * will accumulate the value into an array, returned by getValue().
231: * Otherwise, the last assignment will overide any previous assignment.
232: * @param encoding The character encoding used.
233: */
234:
235: public URLDecoder(Reader in, boolean overide, String encoding) {
236: this .values = new Hashtable(23);
237: this .in = in;
238: this .ch = -1;
239: this .overide = overide;
240: this .enc = encoding;
241: }
242:
243: /**
244: * Create an URLDecoder for the given stream.
245: * @param in The input stream to be parsed.
246: * @param list Tells how to handle multiple settings of the same variable.
247: * When <strong>false</strong>, mutiple settings to the same variable
248: * will accumulate the value into an array, returned by getValue().
249: * Otherwise, the last assignment will overide any previous assignment.
250: */
251:
252: public URLDecoder(Reader in, boolean overide) {
253: this (in, overide, null);
254: }
255:
256: /**
257: * Create an URLDecoder for the given stream.
258: * @param in The input stream to be parsed.
259: * @param list Tells how to handle multiple settings of the same variable.
260: * When <strong>false</strong>, mutiple settings to the same variable
261: * will accumulate the value into an array, returned by getValue().
262: * Otherwise, the last assignment will overide any previous assignment.
263: */
264:
265: public URLDecoder(Reader in) {
266: this (in, true, null);
267: }
268:
269: /**
270: * Create an URLDecoder for the given stream.
271: * @param in The input stream to be parsed.
272: * @param list Tells how to handle multiple settings of the same variable.
273: * When <strong>false</strong>, mutiple settings to the same variable
274: * will accumulate the value into an array, returned by getValue().
275: * Otherwise, the last assignment will overide any previous assignment.
276: * @param encoding The character encoding used.
277: */
278:
279: public URLDecoder(InputStream in, boolean overide, String encoding) {
280: this .values = new Hashtable(23);
281: this .in = new InputStreamReader(in);
282: this .ch = -1;
283: this .overide = overide;
284: this .enc = encoding;
285: }
286:
287: /**
288: * Create an URLDecoder for the given stream.
289: * @param in The input stream to be parsed.
290: * @param list Tells how to handle multiple settings of the same variable.
291: * When <strong>false</strong>, mutiple settings to the same variable
292: * will accumulate the value into an array, returned by getValue().
293: * Otherwise, the last assignment will overide any previous assignment.
294: */
295:
296: public URLDecoder(InputStream in, boolean overide) {
297: this (in, overide, null);
298: }
299:
300: /**
301: * Create an URLDecoder for the given stream.
302: * Default constructor, which will keep track only of the last setting
303: * of the same variable (if ever it gets assigned multiply).
304: * @param in The input stream to be parsed.
305: */
306:
307: public URLDecoder(InputStream in) {
308: this (in, true, null);
309: }
310:
311: }
|