001: /*
002: * FileHandler.java
003: *
004: * Brazil project web application Framework,
005: * export version: 1.1
006: * Copyright (c) 1998-2001 Sun Microsystems, Inc.
007: *
008: * Sun Public License Notice
009: *
010: * The contents of this file are subject to the Sun Public License Version
011: * 1.0 (the "License"). You may not use this file except in compliance with
012: * the License. A copy of the License is included as the file "license.terms",
013: * and also available at http://www.sun.com/
014: *
015: * The Original Code is from:
016: * Brazil project web application Framework release 1.1.
017: * The Initial Developer of the Original Code is: suhler.
018: * Portions created by suhler are Copyright (C) Sun Microsystems, Inc.
019: * All Rights Reserved.
020: *
021: * Contributor(s): cstevens, suhler.
022: *
023: * Version: 1.30
024: * Created by suhler on 98/09/14
025: * Last modified by suhler on 01/01/07 22:35:43
026: */
027:
028: package sunlabs.brazil.server;
029:
030: import java.io.File;
031: import java.io.FileInputStream;
032: import java.io.IOException;
033: import java.util.Properties;
034: import java.util.StringTokenizer;
035:
036: import sunlabs.brazil.util.http.HttpUtil;
037:
038: /**
039: * Standard handler for fetching static files.
040: * This handler does URL to file conversion, file suffix to mime type
041: * lookup, delivery of <i>index</i> files where providing directory
042: * references, and redirection for missing slashes (/) at the end of
043: * directory requests. This handler only responds to GET requests.
044: * <p>
045: * The following coniguration parameters are used:
046: * <dl class=props>
047: * <dt>root <dd> property for document root (.)
048: * Since the document root is common to many handers, if non
049: * root property is found with the supplied prefix, then the
050: * root property with the empty prefix ("") is used instead.
051: * This allows many handlers to share the common property.
052: * <dt>default <dd> property for default document, given directory (index.html)
053: * <dt>prefix <dd> Unly url's that start with this are allowed.
054: * defaults to "". The prefix is removed from the
055: * url before looking it up in the file system.
056: * <dt>mime <dd> property for mime type
057: * For each file suffix .XX, the property mime.XX is used to
058: * determine the mime type. If no property exists, the document
059: * will not be delivered.
060: * </dl>
061: * <p>
062: * The FileHandler sets the following entries in the request properties
063: * as a side-effect:
064: * <dl>
065: * <dt>fileName <dd>The absolute path of the file
066: * that couldn't be found.
067: * <dt>DirectoryName <dd>If the URL specified is a directory name,
068: * its absolute path is placed here.
069: * <dt>lastModified <dd>The Time stamp of the last modified time
070: * </dl>
071: *
072: * @author Stephen Uhler
073: * @version 1.30, 01/01/07
074: */
075:
076: public class FileHandler implements Handler {
077: private String prefix;
078: String urlPrefix;
079:
080: public static final String ROOT = "root"; // property for document root
081: public static final String DEFAULT = "default"; // property for default document, given directory
082: public static final String MIME = "mime"; // property for mime type
083: public final static String ALLOW = "allow"; // allow other than GETs
084: public final static String PREFIX = "prefix"; // our prefix
085:
086: /**
087: * Initialize the file handler.
088: *
089: * @return The file handler always returns true.
090: */
091:
092: public boolean init(Server server, String prefix) {
093: this .prefix = prefix;
094: urlPrefix = server.props.getProperty(prefix + PREFIX, "");
095: return true;
096: }
097:
098: /**
099: * Find, read, and deliver via http the requested file.
100: * The server property <code>root</code> is used as the document root.
101: * The document root is recalculated for each request, so an upstream
102: * handler may change it for that request.
103: * For URL's ending with "/", the server property <code>default</code>
104: * (normally index.html) is automatically appended.
105: *
106: * If the file suffix is not found as a server property <code>
107: * mime.<i>suffix</i></code>, the file is not delivered.
108: */
109:
110: public boolean respond(Request request) throws IOException {
111: if (!request.url.startsWith(urlPrefix)) {
112: return false;
113: }
114: if ((request.props.getProperty(prefix + ALLOW) == null)
115: && (!request.method.equals("GET"))) {
116: request.log(Server.LOG_INFORMATIONAL, prefix,
117: "Skipping request, only GET's alloewed");
118: return false;
119: }
120:
121: String url = request.url.substring(urlPrefix.length());
122: Properties props = request.props;
123: String root = props.getProperty(prefix + ROOT, props
124: .getProperty(ROOT, "."));
125: String name = urlToPath(url);
126: request.log(Server.LOG_DIAGNOSTIC, prefix,
127: "Looking for file: (" + root + ")(" + name + ")");
128: File file = new File(root + name);
129: String path = file.getPath();
130:
131: if (file.isDirectory()) {
132: /*
133: * Must check if the original <code>name</code> ends with "/",
134: * not <code>File.getPath</code> because in jdk-1.2,
135: * <code>File.getPath</code> truncates the terminating "/".
136: */
137:
138: if (request.url.endsWith("/") == false) {
139: request.redirect(request.url + "/", null);
140: return true;
141: }
142: props.put("DirectoryName", path);
143:
144: String index = props.getProperty(prefix + DEFAULT,
145: "index.html");
146:
147: file = new File(file, index);
148: path = file.getPath();
149: }
150:
151: /*
152: * Put the name of the file in the request object. This
153: * may be of some use for down stream handlers.
154: */
155:
156: props.put("fileName", path);
157:
158: if (file.exists() == false) {
159: request.log(Server.LOG_INFORMATIONAL, prefix,
160: "no such file: " + path);
161: return false;
162: }
163:
164: String basename = file.getName();
165: int index = basename.lastIndexOf('.');
166: if (index < 0) {
167: request.log(Server.LOG_INFORMATIONAL, prefix,
168: "no file suffix for: " + path);
169: return false;
170: }
171:
172: String suffix = basename.substring(index);
173: String type = props.getProperty(prefix + MIME + suffix, props
174: .getProperty(MIME + suffix));
175: if (type == null) {
176: request.log(Server.LOG_INFORMATIONAL, prefix,
177: "unknown file suffix: " + suffix);
178: return false;
179: }
180: sendFile(request, file, 200, type);
181: return true;
182: }
183:
184: /**
185: * Helper function to convert an url into a pathname. <ul>
186: * <li> Collapse all %XX sequences.
187: * <li> Ignore missing initial "/".
188: * <li> Collapse all "/..", "/.", and "//" sequences.
189: * </ul>
190: * <code>URL(String)</code> collapses all "/.." (and "/.") sequences,
191: * except for a trailing "/.." (or "/."), which would lead to the
192: * possibility of escaping from the document root.
193: * <p>
194: * <code>File.getPath</code> in jdk-1.1 leaves all the "//" constructs
195: * in, but it collapses them in jdk-1.2, so we have to always take it
196: * out ourselves, just to be sure.
197: *
198: * @param url
199: * The file path from the URL (that is, minus the "http://host"
200: * part). May be <code>null</code>.
201: *
202: * @returns The path that corresponds to the URL. The returned value
203: * begins with "/". The caller can concatenate this path
204: * onto the end of some document root.
205: */
206: public static String urlToPath(String url) {
207: String name = HttpUtil.urlDecode(url);
208:
209: StringBuffer sb = new StringBuffer();
210: StringTokenizer st = new StringTokenizer(name, "/");
211: while (st.hasMoreTokens()) {
212: String part = st.nextToken();
213: if (part.equals(".")) {
214: continue;
215: } else if (part.equals("..")) {
216: for (int i = sb.length(); --i >= 0;) {
217: if (sb.charAt(i) == '/') {
218: sb.setLength(i);
219: break;
220: }
221: }
222: } else {
223: sb.append(File.separatorChar).append(part);
224: }
225: }
226: if ((sb.length() == 0) || name.endsWith("/")) {
227: sb.append(File.separatorChar);
228: }
229: return sb.toString();
230: }
231:
232: /**
233: * Send a file as a response.
234: * @param request The request object
235: * @param fileHandle The file to output
236: * @param type The mime type of the file
237: */
238:
239: static public void sendFile(Request request, File file, int code,
240: String type) throws IOException {
241: if (file.isFile() == false) {
242: request.sendError(404, null, "not a normal file");
243: return;
244: }
245: if (file.canRead() == false) {
246: request.sendError(403, null, "Permission Denied");
247: return;
248: }
249:
250: FileInputStream in = null;
251: try {
252: in = new FileInputStream(file);
253:
254: request.addHeader("Last-Modified", HttpUtil.formatTime(file
255: .lastModified()));
256: request.props.put("lastModified", "" + file.lastModified());
257:
258: int size = (int) file.length();
259: request.setStatus(code);
260: size = range(request, in, size);
261: request.sendResponse(in, size, type, -1);
262: } finally {
263: if (in != null) {
264: in.close();
265: }
266: }
267: }
268:
269: /**
270: * Compute simple byte ranges. (for gnutella support)
271: * @returns The (potential partial) size.
272: * The code may be modified as a side effect.
273: */
274:
275: private static int range(Request request, FileInputStream in,
276: int size) throws IOException {
277: String range = request.getRequestHeader("range");
278: int sep = 0;
279: if (range != null && request.getStatus() == 200
280: && range.indexOf("bytes=") == 0
281: && (sep = range.indexOf("-")) > 0
282: && range.indexOf(",") < 0) {
283: int start = -1;
284: int end = -1;
285: try {
286: start = Integer.parseInt(range.substring(6, sep));
287: } catch (NumberFormatException e) {
288: }
289: try {
290: end = Integer.parseInt(range.substring(sep + 1));
291: } catch (NumberFormatException e) {
292: }
293: if (end == -1) {
294: end = size;
295: } else if (end > size) {
296: end = size;
297: }
298: if (start == -1) {
299: start = size - end + 1;
300: end = size;
301: }
302: if (end >= start) {
303: in.skip(start);
304: size = end - start + 1;
305: request.setStatus(206);
306: }
307: }
308: return size;
309: }
310: }
|