001// Copyright (C) 1998-2001 by Jason Hunter <jhunter_AT_acm_DOT_org>. 002// All rights reserved. Use of this class is limited. 003// Please see the LICENSE for more information. 004 005package com.oreilly.servlet; 006 007import java.io.*; 008import java.lang.reflect.*; 009import java.net.*; 010import java.util.*; 011import javax.servlet.*; 012import javax.servlet.http.*; 013import javax.servlet.jsp.*; 014 015/** 016 * A collection of static utility methods useful to servlets. 017 * Some methods require Servlet API 2.2. 018 * 019 * @author <b>Jason Hunter</b>, Copyright © 1998-2000 020 * @version 1.5, 2001/02/11, added getResource() ".." check 021 * @version 1.4, 2000/09/27, finalized getResource() behavior 022 * @version 1.3, 2000/08/15, improved getStackTraceAsString() to take Throwable 023 * @version 1.2, 2000/03/10, added getResource() method 024 * @version 1.1, 2000/02/13, added returnURL() methods 025 * @version 1.0, 1098/09/18 026 */ 027public class ServletUtils { 028 029 /** 030 * Sends the contents of the specified file to the output stream 031 * 032 * @param filename the file to send 033 * @param out the output stream to write the file 034 * @exception FileNotFoundException if the file does not exist 035 * @exception IOException if an I/O error occurs 036 */ 037 public static void returnFile(String filename, OutputStream out) 038 throws FileNotFoundException, IOException { 039 // A FileInputStream is for bytes 040 FileInputStream fis = null; 041 try { 042 fis = new FileInputStream(filename); 043 byte[] buf = new byte[4 * 1024]; // 4K buffer 044 int bytesRead; 045 while ((bytesRead = fis.read(buf)) != -1) { 046 out.write(buf, 0, bytesRead); 047 } 048 } 049 finally { 050 if (fis != null) fis.close(); 051 } 052 } 053 054 /** 055 * Sends the contents of the specified URL to the output stream 056 * 057 * @param URL whose contents are to be sent 058 * @param out the output stream to write the contents 059 * @exception IOException if an I/O error occurs 060 */ 061 public static void returnURL(URL url, OutputStream out) throws IOException { 062 InputStream in = url.openStream(); 063 byte[] buf = new byte[4 * 1024]; // 4K buffer 064 int bytesRead; 065 while ((bytesRead = in.read(buf)) != -1) { 066 out.write(buf, 0, bytesRead); 067 } 068 } 069 070 /** 071 * Sends the contents of the specified URL to the Writer (commonly either a 072 * PrintWriter or JspWriter) 073 * 074 * @param URL whose contents are to be sent 075 * @param out the Writer to write the contents 076 * @exception IOException if an I/O error occurs 077 */ 078 public static void returnURL(URL url, Writer out) throws IOException { 079 // Determine the URL's content encoding 080 URLConnection con = url.openConnection(); 081 con.connect(); 082 String encoding = con.getContentEncoding(); 083 084 // Construct a Reader appropriate for that encoding 085 BufferedReader in = null; 086 if (encoding == null) { 087 in = new BufferedReader( 088 new InputStreamReader(url.openStream())); 089 } 090 else { 091 in = new BufferedReader( 092 new InputStreamReader(url.openStream(), encoding)); 093 } 094 char[] buf = new char[4 * 1024]; // 4Kchar buffer 095 int charsRead; 096 while ((charsRead = in.read(buf)) != -1) { 097 out.write(buf, 0, charsRead); 098 } 099 } 100 101 /** 102 * Gets an exception's stack trace as a String 103 * 104 * @param e the exception 105 * @return the stack trace of the exception 106 */ 107 public static String getStackTraceAsString(Throwable t) { 108 ByteArrayOutputStream bytes = new ByteArrayOutputStream(); 109 PrintWriter writer = new PrintWriter(bytes, true); 110 t.printStackTrace(writer); 111 return bytes.toString(); 112 } 113 114 /** 115 * Gets a reference to the named servlet, attempting to load it 116 * through an HTTP request if necessary. Returns null if there's a problem. 117 * This method behaves similarly to <tt>ServletContext.getServlet()</tt> 118 * except, while that method may return null if the 119 * named servlet wasn't already loaded, this method tries to load 120 * the servlet using a dummy HTTP request. Only loads HTTP servlets. 121 * 122 * @param name the name of the servlet 123 * @param req the servlet request 124 * @param context the servlet context 125 * @return the named servlet, or null if there was a problem 126 */ 127 public static Servlet getServlet(String name, 128 ServletRequest req, 129 ServletContext context) { 130 try { 131 // Try getting the servlet the old fashioned way 132 Servlet servlet = context.getServlet(name); 133 if (servlet != null) return servlet; 134 135 // If getServlet() returned null, we have to load it ourselves. 136 // Do this by making an HTTP GET request to the servlet. 137 // Use a raw socket connection so we can set a timeout. 138 Socket socket = new Socket(req.getServerName(), req.getServerPort()); 139 socket.setSoTimeout(4000); // wait up to 4 secs for a response 140 PrintWriter out = new PrintWriter(socket.getOutputStream(), true); 141 out.println("GET /servlet/" + name + " HTTP/1.0"); // the request 142 out.println(); 143 try { 144 socket.getInputStream().read(); // Even one byte means its loaded 145 } 146 catch (InterruptedIOException e) { /* timeout: ignore, hope for best */ } 147 out.close(); 148 149 // Try getting the servlet again. 150 return context.getServlet(name); 151 } 152 catch (Exception e) { 153 // If there's any problem, return null. 154 return null; 155 } 156 } 157 158 /** 159 * Splits a String into pieces according to a delimiter. 160 * 161 * @param str the string to split 162 * @param delim the delimiter 163 * @return an array of strings containing the pieces 164 */ 165 public static String[] split(String str, String delim) { 166 // Use a Vector to hold the splittee strings 167 Vector v = new Vector(); 168 169 // Use a StringTokenizer to do the splitting 170 StringTokenizer tokenizer = new StringTokenizer(str, delim); 171 while (tokenizer.hasMoreTokens()) { 172 v.addElement(tokenizer.nextToken()); 173 } 174 175 String[] ret = new String[v.size()]; 176 for (int i = 0; i < ret.length; i++) { 177 ret[i] = (String) v.elementAt(i); 178 } 179 180 return ret; 181 } 182 183 /** 184 * Gets a reference to the given resource within the given context, 185 * making sure not to serve the contents of WEB-INF, META-INF, or to 186 * display .jsp file source. 187 * Throws an IOException if the resource can't be read. 188 * 189 * @param context the context containing the resource 190 * @param resource the resource to be read 191 * @return a URL reference to the resource 192 * @exception IOException if there's any problem accessing the resource 193 */ 194 public static URL getResource(ServletContext context, String resource) 195 throws IOException { 196 // Short-circuit if resource is null 197 if (resource == null) { 198 throw new FileNotFoundException( 199 "Requested resource was null (passed in null)"); 200 } 201 202 if (resource.endsWith("/") || 203 resource.endsWith("\\") || 204 resource.endsWith(".")) { 205 throw new MalformedURLException("Path may not end with a slash or dot"); 206 } 207 208 if (resource.indexOf("..") != -1) { 209 throw new MalformedURLException("Path may not contain double dots"); 210 } 211 212 String upperResource = resource.toUpperCase(); 213 if (upperResource.startsWith("/WEB-INF") || 214 upperResource.startsWith("/META-INF")) { 215 throw new MalformedURLException( 216 "Path may not begin with /WEB-INF or /META-INF"); 217 } 218 219 if (upperResource.endsWith(".JSP")) { 220 throw new MalformedURLException( 221 "Path may not end with .jsp"); 222 } 223 224 // Convert the resource to a URL 225 URL url = context.getResource(resource); 226 if (url == null) { 227 throw new FileNotFoundException( 228 "Requested resource was null (" + resource + ")"); 229 } 230 231 return url; 232 } 233}