KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > openlaszlo > utils > LZHttpUtils


1 /******************************************************************************
2  * LZHttpUtils.java
3  * ****************************************************************************/

4
5 /* J_LZ_COPYRIGHT_BEGIN *******************************************************
6 * Copyright 2001-2004 Laszlo Systems, Inc. All Rights Reserved. *
7 * Use is subject to license terms. *
8 * J_LZ_COPYRIGHT_END *********************************************************/

9
10 package org.openlaszlo.utils;
11
12 import java.io.File;
13 import java.io.UnsupportedEncodingException;
14 import java.net.URL;
15 import javax.servlet.ServletContext;
16 import javax.servlet.http.HttpUtils;
17 import javax.servlet.http.HttpServletRequest;
18 import javax.servlet.http.HttpServletResponse;
19 import org.apache.commons.httpclient.methods.*;
20 import org.apache.commons.httpclient.HttpMethodBase;
21 import org.apache.commons.httpclient.Header;
22 import org.apache.commons.httpclient.URI;
23 import org.apache.commons.httpclient.URIException;
24
25 import java.text.SimpleDateFormat;
26 import java.util.TimeZone;
27 import java.util.Date;
28 import java.util.Enumeration;
29 import java.security.*;
30
31 import org.apache.log4j.*;
32
33 import org.openlaszlo.utils.ChainedException;
34
35 /**
36  * Utility class for http servlets
37  */

38 public class LZHttpUtils {
39
40     private static Logger mLogger = Logger.getLogger(LZHttpUtils.class);
41
42     public static final String CONTENT_ENCODING = "Content-Encoding";
43     public static final String CONTENT_LENGTH = "Content-Length";
44     public static final String CONTENT_TYPE = "Content-Type";
45     public static final String IF_MODIFIED_SINCE = "If-Modified-Since";
46     public static final String LAST_MODIFIED = "Last-Modified";
47     public static final String IF_NONE_MATCH = "If-None-Match";
48     public static final String TRANSFER_ENCODING = "Transfer-Encoding";
49     public static final String HOST = "Host";
50     public static final String CONNECTION = "Connection";
51     public static final String AUTHORIZATION = "Authorization";
52     public static final String COOKIE = "Cookie";
53     public static final String CACHE_CONTROL = "Cache-Control";
54     public static final String USER_AGENT = "User-Agent";
55     public static final String ACCEPT_ENCODING = "Accept-Encoding";
56     public static final String RANGE = "Range";
57     public static final String ACCEPT_RANGES = "Accept-Ranges";
58     public static final String IF_RANGE = "If-Range";
59
60     public static final String NO_STORE = "no-store";
61     public static final String NO_CACHE = "no-cache";
62
63
64     /**
65      * @return the URL for the request // with the query string?
66      * @param req the request
67      */

68     public static URL getRequestURL(HttpServletRequest req) {
69         StringBuffer surl = HttpUtils.getRequestURL(req);
70         String sturl = surl.toString(); // I love java!
71
if (sturl.indexOf("https") == 0) {
72             try {
73                 System.setProperty("java.protocol.handler.pkgs",
74                                    "com.sun.net.ssl.internal.www.protocol");
75                 Class provClass = Class.forName("com.sun.net.ssl.internal.ssl.Provider");
76                 Provider provider = (Provider)provClass.newInstance();
77                 Security.addProvider(provider);
78             } catch (InstantiationException e) {
79                 throw new ChainedException(e);
80             } catch (IllegalAccessException e) {
81                 throw new ChainedException(e);
82             } catch (ClassNotFoundException e) {
83                 throw new ChainedException(e);
84             }
85         }
86         // surl.append("?");
87
// surl.append(req.getQueryString());
88
try {
89             return new URL(surl.toString());
90         } catch (Exception e) {
91             throw new ChainedException(e);
92         }
93     }
94
95     /**
96      * For formatting HTTP dates
97      */

98
99     /**
100      * Return a formatter for HTTP date headers
101      */

102     private static SimpleDateFormat getGMTFormatter() {
103         SimpleDateFormat dateFormatter =
104             new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z",
105                     java.util.Locale.US);
106         TimeZone tz = TimeZone.getTimeZone("GMT");
107         dateFormatter.setTimeZone(tz);
108         return dateFormatter;
109     }
110
111     /**
112      * Convert a long utc value to an HTTP Date String (TZ must be GMT)
113      */

114     public static String getDateString(long d) {
115         SimpleDateFormat dateFormatter = getGMTFormatter();
116         return dateFormatter.format(new Date(d));
117     }
118
119     /**
120      * Convert an HTTP Date String to a long
121      * @return the long or -1 if the string doesn't parse correctly.
122      */

123     public static long getDate(String s) {
124         if (s == null || "".equals(s)) {
125             return -1;
126         }
127         try {
128             SimpleDateFormat dateFormatter = getGMTFormatter();
129             return dateFormatter.parse(s).getTime();
130         } catch (java.text.ParseException e) {
131             mLogger.warn("bad date string", e);
132             return -1;
133         }
134     }
135
136     /** From RFC2616, 14.10:
137      *
138      * HTTP/1.1 proxies MUST parse the Connection header field before a message
139      * is forwarded and, for each connection-token in this field, remove any
140      * header field(s) from the message with the same name as the
141      * connection-token. Connection options are signaled by the presence of a
142      * connection-token in the Connection header field, not by any corresponding
143      * additional header field(s), since the additional header field may not be
144      * sent if there are no parameters associated with that connection
145      * option. */

146     static public boolean allowForward(String header, Enumeration connEnum)
147     {
148         if (header.toLowerCase().startsWith("content-"))
149             return false;
150
151         if (header.equalsIgnoreCase(CONNECTION))
152             return false;
153
154         if (header.equalsIgnoreCase(HOST))
155             return false;
156
157         if (header.equalsIgnoreCase(TRANSFER_ENCODING))
158             return false;
159
160         if (header.equalsIgnoreCase(IF_MODIFIED_SINCE))
161             return false;
162
163         if (header.equalsIgnoreCase(LAST_MODIFIED))
164             return false;
165
166         if (header.equalsIgnoreCase(IF_NONE_MATCH))
167             return false;
168
169         if (header.equalsIgnoreCase(ACCEPT_ENCODING))
170             return false;
171
172         // Someday we may allow these only when the proxy is non-transcoding
173
if (header.equalsIgnoreCase(RANGE))
174             return false;
175
176         // Someday we may allow these only when the proxy is non-transcoding
177
if (header.equalsIgnoreCase(ACCEPT_RANGES))
178             return false;
179
180         // Someday we may allow these only when the proxy is non-transcoding
181
if (header.equalsIgnoreCase(IF_RANGE))
182             return false;
183
184         // Don't forward any headers that have the same name as a connection
185
// token.
186
if (connEnum != null) {
187             while (connEnum.hasMoreElements()) {
188                 String token = (String)connEnum.nextElement();
189                 if (header.equalsIgnoreCase(token))
190                     return false;
191             }
192         }
193
194         return true;
195     }
196
197     /** Add request headers into method.
198      *
199      * @param req http servlet request object
200      * @param method method to insert request headers into
201      */

202     static public void proxyRequestHeaders(HttpServletRequest req,
203                                            HttpMethodBase method)
204     {
205         mLogger.debug("proxyRequestHeaders");
206         
207         // Copy all headers, if the servlet container allows, otherwise just
208
// copy the cookie header and log a message.
209
Enumeration enum = req.getHeaderNames();
210         if (enum!=null) {
211
212             // Connection header tokens not to forward
213
Enumeration connEnum = req.getHeaders(CONNECTION);
214
215             while (enum.hasMoreElements()) {
216                 String key = (String)enum.nextElement();
217                 if (allowForward(key, connEnum)) {
218                     String val = (String)req.getHeader(key);
219                     method.addRequestHeader(key,val);
220                     mLogger.debug(" " + key + "=" + val);
221                 }
222             }
223
224         } else {
225             mLogger.warn("Can't get header names to proxy request headers");
226
227             Enumeration cookieEnum = req.getHeaders(COOKIE);
228             if (cookieEnum != null) {
229                 while (cookieEnum.hasMoreElements()) {
230                     String val = (String)cookieEnum.nextElement();
231                     method.addRequestHeader(COOKIE, val);
232                     mLogger.debug(" Cookie=" + val);
233                 }
234             }
235
236             Enumeration authEnum = req.getHeaders(AUTHORIZATION);
237             if (authEnum != null) {
238                 while (authEnum.hasMoreElements()) {
239                     String val = (String)authEnum.nextElement();
240                     method.addRequestHeader(AUTHORIZATION, val);
241                     mLogger.debug(" Authorization=" + val);
242                 }
243             }
244         }
245     }
246
247     /** Pull response headers from method and put into
248      * servlet response object.
249      *
250      * @param req http servlet response object to proxy to
251      * @param method method to proxy from
252      * @param isSecure true if get method is secure
253      */

254     static public void proxyResponseHeaders(GetMethod meth,
255                                             HttpServletResponse res,
256                                             boolean isSecure)
257     {
258         mLogger.debug("proxyResponseHeaders");
259         
260         Header[] hedz = meth.getResponseHeaders();
261
262         for (int i = 0; i < hedz.length; i++) {
263             String name = hedz[i].getName();
264             String value = hedz[i].getValue();
265             // Content length passed back to swf app will be different
266
if (allowForward(name, null)) {
267                 // Don't send no-cache headers request is SSL; IE 6 has
268
// problems.
269
if (isSecure) {
270                     if (name.equals("Pragma") && value.equals("no-cache"))
271                         continue;
272                     if (name.equals("Cache-Control") && value.equals("no-cache"))
273                         continue;
274                 }
275
276                 mLogger.debug(" " + name + "=" + value);
277
278                 try {
279                     if (name.equals("Date") || name.equals("Server")) {
280                         res.setHeader(name, value);
281                     } else {
282                         res.addHeader(name, value);
283                     }
284                 } catch (Exception e) {
285                     mLogger.error("Exception when proxying a response header: " + e.getMessage());
286                 }
287             }
288         }
289     }
290
291     /**
292      * Fetch cookie value for a particular cookie name.
293      *
294      * @param req servlet request.
295      * @param name name of cookie key to fetch.
296      */

297     static public String getCookie(HttpServletRequest req, String name)
298     {
299         javax.servlet.http.Cookie[] cookies = req.getCookies();
300         if (cookies != null) {
301             for (int i=0; i < cookies.length; i++) {
302                 javax.servlet.http.Cookie cookie = cookies[i];
303                 if (cookie.getName().equals(name)) {
304                     return cookie.getValue();
305                 }
306             }
307         }
308         return null;
309     }
310
311     /**
312      * Replace real path forward slash characters to back-slash for Windoze.
313      * This is to get around a WebSphere problem (see bug 988). Note that if the
314      * web application content is being served directly from a .war file, this
315      * method will return null. See ServletContext.getRealPath() for more
316      * details.
317      *
318      * @param ctx servlet context
319      * @param path virtual webapp path to resolve into a real path
320      * @return the real path, or null if the translation cannot be performed
321      */

322     static public String getRealPath(ServletContext ctxt, String path)
323     {
324         String realPath = ctxt.getRealPath(path);
325         if ( realPath != null && File.separatorChar == '\\' )
326             realPath = realPath.replace('/', '\\');
327         try {
328             return new File(realPath).getCanonicalPath();
329         } catch (java.io.IOException e) {
330             throw new org.openlaszlo.utils.ChainedException(e);
331         }
332     }
333
334     /**
335      * Replace real path forward slash characters to back-slash for Windoze.
336      *
337      * @param ctx servlet context
338      * @param generating request
339      * @return the real path, or null if the translation cannot be performed
340      */

341     static public String getRealPath(ServletContext ctxt, HttpServletRequest req)
342     {
343         String uriwithoutcontext = req.getRequestURI().substring(req.getContextPath().length());
344         if (uriwithoutcontext != null && File.separatorChar == '\\' ) {
345             uriwithoutcontext = uriwithoutcontext.replace('/', '\\');
346         }
347         return getRealPath(ctxt, "/") + uriwithoutcontext;
348     }
349
350     private static String WEBAPP = "/@WEBAPP@/";
351
352     /**
353      * If a URL contains <code>/@WEBAPP@</code>, replaces that string with
354      * context path. If context path is <code>/</code>, the function just
355      * removes the <code>/@WEBAPP@</code> string.
356      *
357      * @param contextPath current context path.
358      * @param url URL to check if <code>/@WEBAPP</code> token exists.
359      * @return if <code>/@WEBAPP@</code> exists, new modified URL else old URL.
360      */

361     public static String modifyWEBAPP(HttpServletRequest req, String url)
362     {
363         mLogger.debug("modifyWEBAPP");
364         if (url.startsWith(WEBAPP)) {
365             mLogger.debug(" Old URL: " + url);
366             String protocol = (req.isSecure()?"https":"http");
367             String host = req.getServerName();
368             int port = req.getServerPort();
369             String cp = req.getContextPath();
370             url = protocol + "://" + host + ":" + port + cp
371                 + url.substring(WEBAPP.length()-1);
372             mLogger.debug(" New URL: " + url);
373         }
374         return url;
375     }
376
377     /**
378      * Mark response with no-store cache control
379      */

380     static public void noStore(HttpServletResponse res) {
381         if (res.containsHeader(LZHttpUtils.CACHE_CONTROL)) {
382             mLogger.warn("over-riding back-end cache-control header to: no-store");
383         }
384         res.setHeader(CACHE_CONTROL, NO_STORE);
385     }
386
387
388     /**
389      * Return a URI object, escaping input only if needed
390      */

391     static public URI newURI(String s) throws URIException {
392         try {
393             return new URI(s.toCharArray());
394         } catch (URIException urie) {
395             // Try escaping
396
try {
397                 return new URI(s);
398             } catch (Exception e) {
399                 // Escaping failed, throw the original error
400
throw urie;
401             }
402         }
403     }
404
405     /**
406      * Decodes a urlencoded string using a specific charset encoding.
407      */

408     public static String urldecode(String s, String enc)
409       throws UnsupportedEncodingException {
410         boolean needToChange = false;
411         StringBuffer sb = new StringBuffer();
412         int numChars = s.length();
413         int i = 0;
414
415         if (enc.length() == 0) {
416             throw new UnsupportedEncodingException ("LzHTTPUtils.urldecode: empty string enc parameter");
417         }
418
419         while (i < numChars) {
420             char c = s.charAt(i);
421             switch (c) {
422               case '+':
423                 sb.append(' ');
424                 i++;
425                 needToChange = true;
426                 break;
427               case '%':
428                 /*
429                  * Starting with this instance of %, process all
430                  * consecutive substrings of the form %xy. Each
431                  * substring %xy will yield a byte. Convert all
432                  * consecutive bytes obtained this way to whatever
433                  * character(s) they represent in the provided
434                  * encoding.
435                  */

436
437                 try {
438
439                     // (numChars-i)/3 is an upper bound for the number
440
// of remaining bytes
441
byte[] bytes = new byte[(numChars-i)/3];
442                     int pos = 0;
443             
444                     while ( ((i+2) < numChars) &&
445                             (c=='%')) {
446                         bytes[pos++] =
447                             (byte)Integer.parseInt(s.substring(i+1,i+3),16);
448                         i+= 3;
449                         if (i < numChars)
450                             c = s.charAt(i);
451                     }
452
453                     // A trailing, incomplete byte encoding such as
454
// "%x" will cause an exception to be thrown
455

456                     if ((i < numChars) && (c=='%'))
457                         throw new IllegalArgumentException(
458                             "URLDecoder: Incomplete trailing escape (%) pattern");
459             
460                     sb.append(new String(bytes, 0, pos, enc));
461                 } catch (NumberFormatException e) {
462                     throw new IllegalArgumentException(
463                         "URLDecoder: Illegal hex characters in escape (%) pattern - "
464                         + e.getMessage());
465                 }
466                 needToChange = true;
467                 break;
468               default:
469                 sb.append(c);
470                 i++;
471                 break;
472             }
473         }
474         return (needToChange? sb.toString() : s);
475     }
476
477 }
478
Popular Tags