1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *  http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  
20  package javax.servlet.http;
21  
22  import javax.servlet.ServletInputStream;
23  import java.util.Hashtable;
24  import java.util.ResourceBundle;
25  import java.util.StringTokenizer;
26  import java.io.IOException;
27  
28  /**
29   * @deprecated		As of Java(tm) Servlet API 2.3. 
30   *			These methods were only useful
31   *			with the default encoding and have been moved
32   *			to the request interfaces.
33   *
34  */
35  
36  
37  public class HttpUtils {
38  
39      private static final String LSTRING_FILE =
40  	"javax.servlet.http.LocalStrings";
41      private static ResourceBundle lStrings =
42  	ResourceBundle.getBundle(LSTRING_FILE);
43          
44      
45      
46      /**
47       * Constructs an empty <code>HttpUtils</code> object.
48       *
49       */
50  
51      public HttpUtils() {}
52      
53      
54      
55      
56  
57      /**
58       *
59       * Parses a query string passed from the client to the
60       * server and builds a <code>HashTable</code> object
61       * with key-value pairs. 
62       * The query string should be in the form of a string
63       * packaged by the GET or POST method, that is, it
64       * should have key-value pairs in the form <i>key=value</i>,
65       * with each pair separated from the next by a &amp; character.
66       *
67       * <p>A key can appear more than once in the query string
68       * with different values. However, the key appears only once in 
69       * the hashtable, with its value being
70       * an array of strings containing the multiple values sent
71       * by the query string.
72       * 
73       * <p>The keys and values in the hashtable are stored in their
74       * decoded form, so
75       * any + characters are converted to spaces, and characters
76       * sent in hexadecimal notation (like <i>%xx</i>) are
77       * converted to ASCII characters.
78       *
79       * @param s		a string containing the query to be parsed
80       *
81       * @return		a <code>HashTable</code> object built
82       * 			from the parsed key-value pairs
83       *
84       * @exception IllegalArgumentException	if the query string 
85       *						is invalid
86       *
87       */
88  
89      static public Hashtable parseQueryString(String s) {
90  
91  	String valArray[] = null;
92  	
93  	if (s == null) {
94  	    throw new IllegalArgumentException();
95  	}
96  	Hashtable ht = new Hashtable();
97  	StringBuffer sb = new StringBuffer();
98  	StringTokenizer st = new StringTokenizer(s, "&");
99  	while (st.hasMoreTokens()) {
100 	    String pair = (String)st.nextToken();
101 	    int pos = pair.indexOf('=');
102 	    if (pos == -1) {
103 		// XXX
104 		// should give more detail about the illegal argument
105 		throw new IllegalArgumentException();
106 	    }
107 	    String key = parseName(pair.substring(0, pos), sb);
108 	    String val = parseName(pair.substring(pos+1, pair.length()), sb);
109 	    if (ht.containsKey(key)) {
110 		String oldVals[] = (String []) ht.get(key);
111 		valArray = new String[oldVals.length + 1];
112 		for (int i = 0; i < oldVals.length; i++) 
113 		    valArray[i] = oldVals[i];
114 		valArray[oldVals.length] = val;
115 	    } else {
116 		valArray = new String[1];
117 		valArray[0] = val;
118 	    }
119 	    ht.put(key, valArray);
120 	}
121 	return ht;
122     }
123 
124 
125 
126 
127     /**
128      *
129      * Parses data from an HTML form that the client sends to 
130      * the server using the HTTP POST method and the 
131      * <i>application/x-www-form-urlencoded</i> MIME type.
132      *
133      * <p>The data sent by the POST method contains key-value
134      * pairs. A key can appear more than once in the POST data
135      * with different values. However, the key appears only once in 
136      * the hashtable, with its value being
137      * an array of strings containing the multiple values sent
138      * by the POST method.
139      *
140      * <p>The keys and values in the hashtable are stored in their
141      * decoded form, so
142      * any + characters are converted to spaces, and characters
143      * sent in hexadecimal notation (like <i>%xx</i>) are
144      * converted to ASCII characters.
145      *
146      *
147      *
148      * @param len	an integer specifying the length,
149      *			in characters, of the 
150      *			<code>ServletInputStream</code>
151      *			object that is also passed to this
152      *			method
153      *
154      * @param in	the <code>ServletInputStream</code>
155      *			object that contains the data sent
156      *			from the client
157      * 
158      * @return		a <code>HashTable</code> object built
159      *			from the parsed key-value pairs
160      *
161      *
162      * @exception IllegalArgumentException	if the data
163      *			sent by the POST method is invalid
164      *
165      */
166      
167 
168     static public Hashtable parsePostData(int len, 
169 					  ServletInputStream in)
170     {
171 	// XXX
172 	// should a length of 0 be an IllegalArgumentException
173 	
174 	if (len <=0)
175 	    return new Hashtable(); // cheap hack to return an empty hash
176 
177 	if (in == null) {
178 	    throw new IllegalArgumentException();
179 	}
180 	
181 	//
182 	// Make sure we read the entire POSTed body.
183 	//
184         byte[] postedBytes = new byte [len];
185         try {
186             int offset = 0;
187        
188 	    do {
189 		int inputLen = in.read (postedBytes, offset, len - offset);
190 		if (inputLen <= 0) {
191 		    String msg = lStrings.getString("err.io.short_read");
192 		    throw new IllegalArgumentException (msg);
193 		}
194 		offset += inputLen;
195 	    } while ((len - offset) > 0);
196 
197 	} catch (IOException e) {
198 	    throw new IllegalArgumentException(e.getMessage());
199 	}
200 
201         // XXX we shouldn't assume that the only kind of POST body
202         // is FORM data encoded using ASCII or ISO Latin/1 ... or
203         // that the body should always be treated as FORM data.
204         //
205 
206         try {
207             String postedBody = new String(postedBytes, 0, len, "8859_1");
208             return parseQueryString(postedBody);
209         } catch (java.io.UnsupportedEncodingException e) {
210             // XXX function should accept an encoding parameter & throw this
211             // exception.  Otherwise throw something expected.
212             throw new IllegalArgumentException(e.getMessage());
213         }
214     }
215 
216 
217 
218 
219     /*
220      * Parse a name in the query string.
221      */
222 
223     static private String parseName(String s, StringBuffer sb) {
224 	sb.setLength(0);
225 	for (int i = 0; i < s.length(); i++) {
226 	    char c = s.charAt(i); 
227 	    switch (c) {
228 	    case '+':
229 		sb.append(' ');
230 		break;
231 	    case '%':
232 		try {
233 		    sb.append((char) Integer.parseInt(s.substring(i+1, i+3), 
234 						      16));
235 		    i += 2;
236 		} catch (NumberFormatException e) {
237 		    // XXX
238 		    // need to be more specific about illegal arg
239 		    throw new IllegalArgumentException();
240 		} catch (StringIndexOutOfBoundsException e) {
241 		    String rest  = s.substring(i);
242 		    sb.append(rest);
243 		    if (rest.length()==2)
244 			i++;
245 		}
246 		
247 		break;
248 	    default:
249 		sb.append(c);
250 		break;
251 	    }
252 	}
253 	return sb.toString();
254     }
255 
256 
257 
258 
259     /**
260      *
261      * Reconstructs the URL the client used to make the request,
262      * using information in the <code>HttpServletRequest</code> object.
263      * The returned URL contains a protocol, server name, port
264      * number, and server path, but it does not include query
265      * string parameters.
266      * 
267      * <p>Because this method returns a <code>StringBuffer</code>,
268      * not a string, you can modify the URL easily, for example,
269      * to append query parameters.
270      *
271      * <p>This method is useful for creating redirect messages
272      * and for reporting errors.
273      *
274      * @param req	a <code>HttpServletRequest</code> object
275      *			containing the client's request
276      * 
277      * @return		a <code>StringBuffer</code> object containing
278      *			the reconstructed URL
279      *
280      */
281 
282     public static StringBuffer getRequestURL (HttpServletRequest req) {
283 	StringBuffer url = new StringBuffer ();
284 	String scheme = req.getScheme ();
285 	int port = req.getServerPort ();
286 	String urlPath = req.getRequestURI();
287 	
288 	//String		servletPath = req.getServletPath ();
289 	//String		pathInfo = req.getPathInfo ();
290 
291 	url.append (scheme);		// http, https
292 	url.append ("://");
293 	url.append (req.getServerName ());
294 	if ((scheme.equals ("http") && port != 80)
295 		|| (scheme.equals ("https") && port != 443)) {
296 	    url.append (':');
297 	    url.append (req.getServerPort ());
298 	}
299 	//if (servletPath != null)
300 	//    url.append (servletPath);
301 	//if (pathInfo != null)
302 	//    url.append (pathInfo);
303 	url.append(urlPath);
304 	return url;
305     }
306 }
307 
308 
309