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 &#169; 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}