001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements.  See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership.  The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the
007     * "License"); you may not use this file except in compliance
008     * with the License.  You may obtain a copy of the License at
009     *
010     *  http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing,
013     * software distributed under the License is distributed on an
014     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015     * KIND, either express or implied.  See the License for the
016     * specific language governing permissions and limitations
017     * under the License.
018     */
019    
020    package javax.servlet.http;
021    
022    import javax.servlet.ServletInputStream;
023    import java.util.Hashtable;
024    import java.util.ResourceBundle;
025    import java.util.StringTokenizer;
026    import java.io.IOException;
027    
028    /**
029     * @deprecated          As of Java(tm) Servlet API 2.3. 
030     *                      These methods were only useful
031     *                      with the default encoding and have been moved
032     *                      to the request interfaces.
033     *
034    */
035    
036    
037    public class HttpUtils {
038    
039        private static final String LSTRING_FILE =
040            "javax.servlet.http.LocalStrings";
041        private static ResourceBundle lStrings =
042            ResourceBundle.getBundle(LSTRING_FILE);
043            
044        
045        
046        /**
047         * Constructs an empty <code>HttpUtils</code> object.
048         *
049         */
050    
051        public HttpUtils() {}
052        
053        
054        
055        
056    
057        /**
058         *
059         * Parses a query string passed from the client to the
060         * server and builds a <code>HashTable</code> object
061         * with key-value pairs. 
062         * The query string should be in the form of a string
063         * packaged by the GET or POST method, that is, it
064         * should have key-value pairs in the form <i>key=value</i>,
065         * with each pair separated from the next by a &amp; character.
066         *
067         * <p>A key can appear more than once in the query string
068         * with different values. However, the key appears only once in 
069         * the hashtable, with its value being
070         * an array of strings containing the multiple values sent
071         * by the query string.
072         * 
073         * <p>The keys and values in the hashtable are stored in their
074         * decoded form, so
075         * any + characters are converted to spaces, and characters
076         * sent in hexadecimal notation (like <i>%xx</i>) are
077         * converted to ASCII characters.
078         *
079         * @param s         a string containing the query to be parsed
080         *
081         * @return          a <code>HashTable</code> object built
082         *                  from the parsed key-value pairs
083         *
084         * @exception IllegalArgumentException      if the query string 
085         *                                          is invalid
086         *
087         */
088    
089        static public Hashtable parseQueryString(String s) {
090    
091            String valArray[] = null;
092            
093            if (s == null) {
094                throw new IllegalArgumentException();
095            }
096            Hashtable ht = new Hashtable();
097            StringBuffer sb = new StringBuffer();
098            StringTokenizer st = new StringTokenizer(s, "&");
099            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