001: // FileServlet - servlet similar to a standard httpd
002: //
003: // Copyright (C)1996,1998 by Jef Poskanzer <jef@acme.com>. All rights reserved.
004: //
005: // Redistribution and use in source and binary forms, with or without
006: // modification, are permitted provided that the following conditions
007: // are met:
008: // 1. Redistributions of source code must retain the above copyright
009: // notice, this list of conditions and the following disclaimer.
010: // 2. Redistributions in binary form must reproduce the above copyright
011: // notice, this list of conditions and the following disclaimer in the
012: // documentation and/or other materials provided with the distribution.
013: //
014: // THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
015: // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
016: // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
017: // ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
018: // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
019: // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
020: // OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
021: // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
022: // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
023: // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
024: // SUCH DAMAGE.
025: //
026: // Visit the ACME Labs Java page for up-to-date versions of this and other
027: // fine Java utilities: http://www.acme.com/java/
028: //
029: // All enhancments Copyright (C)1998,2000 by Dmitriy Rogatkin
030: // http://tjws.sourceforge.net
031:
032: package Acme.Serve;
033:
034: import java.io.*;
035: import java.util.*;
036: import java.text.*;
037: import javax.servlet.*;
038: import javax.servlet.http.*;
039:
040: /// Servlet similar to a standard httpd.
041: // <P>
042: // Implements the "GET" and "HEAD" methods for files and directories.
043: // Handles index.html, index.htm, default.htm, default.html.
044: // Redirects directory URLs that lack a trailing /.
045: // Handles If-Modified-Since.
046: // <P>
047: // <A HREF="/resources/classes/Acme/Serve/FileServlet.java">Fetch the software.</A><BR>
048: // <A HREF="/resources/classes/Acme.tar.Z">Fetch the entire Acme package.</A>
049: // <P>
050: // @see Acme.Serve.Serve
051:
052: // All enhancments Copyright (C)1998-2000 by Dmitriy Rogatkin
053: // this version is compatible with the latest JSDK 2.2
054: // http://tjws.sourceforge.net
055:
056: public class FileServlet extends HttpServlet {
057:
058: // We keep a single throttle table for all instances of the servlet.
059: // Normally there is only one instance; the exception is subclasses.
060: static Acme.WildcardDictionary throttleTab = null;
061: static final String[] DEFAULTINDEXPAGES = { "index.html",
062: "index.htm", "default.htm", "default.html" };
063: static final DecimalFormat lengthftm = new DecimalFormat("#");
064:
065: private static final boolean logenabled =
066: // true;
067: false;
068:
069: /// Constructor.
070: public FileServlet() {
071: }
072:
073: /// Constructor with throttling.
074: // @param throttles filename containing throttle settings
075: // @see ThrottledOutputStream
076: public FileServlet(String throttles) throws IOException {
077: this ();
078: readThrottles(throttles);
079: }
080:
081: private void readThrottles(String throttles) throws IOException {
082: Acme.WildcardDictionary newThrottleTab = ThrottledOutputStream
083: .parseThrottleFile(throttles);
084: if (throttleTab == null)
085: throttleTab = newThrottleTab;
086: else {
087: // Merge the new one into the old one.
088: Enumeration keys = newThrottleTab.keys();
089: Enumeration elements = newThrottleTab.elements();
090: while (keys.hasMoreElements()) {
091: Object key = keys.nextElement();
092: Object element = elements.nextElement();
093: throttleTab.put(key, element);
094: }
095: }
096: }
097:
098: /// Returns a string containing information about the author, version, and
099: // copyright of the servlet.
100: public String getServletInfo() {
101: return "servlet similar to a standard httpd";
102: }
103:
104: /// Services a single request from the client.
105: // @param req the servlet request
106: // @param req the servlet response
107: // @exception ServletException when an exception has occurred
108: public void service(HttpServletRequest req, HttpServletResponse res)
109: throws ServletException, IOException {
110: boolean headOnly;
111: if (req.getMethod().equalsIgnoreCase("get"))
112: headOnly = false;
113: else if (!req.getMethod().equalsIgnoreCase("head"))
114: headOnly = true;
115: else {
116: res.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED);
117: return;
118: }
119:
120: String path = req.getPathInfo();
121: if (path != null && path.length() > 2) {
122: path = path.replace('\\', '/');
123: if (path.indexOf("/../") > 0 || path.endsWith("/..")) {
124: res.sendError(HttpServletResponse.SC_FORBIDDEN);
125: return;
126: }
127: }
128:
129: dispatchPathname(req, res, headOnly, path);
130: }
131:
132: private void dispatchPathname(HttpServletRequest req,
133: HttpServletResponse res, boolean headOnly, String path)
134: throws IOException {
135: String filename = req.getPathTranslated() != null ? req
136: .getPathTranslated().replace('/', File.separatorChar)
137: : "";
138: if (filename.length() > 0
139: && filename.charAt(filename.length() - 1) == File.separatorChar)
140: filename = filename.substring(0, filename.length() - 1);
141: File file = new File(filename);
142: log("showing " + filename + " for path " + path);
143: if (file.exists()) {
144: if (!file.isDirectory())
145: serveFile(req, res, headOnly, path, file);
146: else {
147: log("showing dir " + file);
148: if (path.charAt(path.length() - 1) != '/')
149: redirectDirectory(req, res, path, file);
150: else
151: showIdexFile(req, res, headOnly, path, filename);
152: }
153: } else {
154: for (int i = 0; i < DEFAULTINDEXPAGES.length; i++) {
155: if (filename.endsWith(File.separator
156: + DEFAULTINDEXPAGES[i])) {
157: showIdexFile(req, res, headOnly, path, file
158: .getParent());
159: return;
160: }
161: }
162: res.sendError(HttpServletResponse.SC_NOT_FOUND);
163: }
164: }
165:
166: private void showIdexFile(HttpServletRequest req,
167: HttpServletResponse res, boolean headOnly, String path,
168: String parent) throws IOException {
169: log("showing index in directory " + parent);
170: for (int i = 0; i < DEFAULTINDEXPAGES.length; i++) {
171: File indexFile = new File(parent, DEFAULTINDEXPAGES[i]);
172: if (indexFile.exists()) {
173: serveFile(req, res, headOnly, path, indexFile);
174: return;
175: }
176: }
177: // index not found
178: serveDirectory(req, res, headOnly, path, new File(parent));
179: }
180:
181: private void serveFile(HttpServletRequest req,
182: HttpServletResponse res, boolean headOnly, String path,
183: File file) throws IOException {
184: log("getting " + file);
185: if (!file.canRead()) {
186: res.sendError(HttpServletResponse.SC_FORBIDDEN);
187: return;
188: }
189:
190: // Handle If-Modified-Since.
191: res.setStatus(HttpServletResponse.SC_OK);
192: long lastMod = file.lastModified();
193: String ifModSinceStr = req.getHeader("If-Modified-Since");
194: long ifModSince = -1;
195: if (ifModSinceStr != null) {
196: int semi = ifModSinceStr.indexOf(';');
197: if (semi != -1)
198: ifModSinceStr = ifModSinceStr.substring(0, semi);
199: try {
200: ifModSince = DateFormat.getDateInstance().parse(
201: ifModSinceStr).getTime();
202: } catch (Exception ignore) {
203: }
204: }
205: if (ifModSince != -1 && ifModSince == lastMod) {
206: res.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
207: headOnly = true;
208: }
209:
210: res.setContentType(getServletContext().getMimeType(
211: file.getName()));
212: res.setContentLength((int) file.length());
213: res.setDateHeader("Last-modified", lastMod);
214: OutputStream out = res.getOutputStream();
215: if (!headOnly) {
216: // Check throttle.
217: if (throttleTab != null) {
218: ThrottleItem throttleItem = (ThrottleItem) throttleTab
219: .get(path);
220: if (throttleItem != null) {
221: // !!! Need to account for multiple simultaneous fetches.
222: out = new ThrottledOutputStream(out, throttleItem
223: .getMaxBps());
224: }
225: }
226:
227: InputStream in = new FileInputStream(file);
228: copyStream(in, out);
229: in.close();
230: }
231: out.close();
232: }
233:
234: /// Copy a file from in to out.
235: // Sub-classes can override this in order to do filtering of some sort.
236: public void copyStream(InputStream in, OutputStream out)
237: throws IOException {
238: Acme.Utils.copyStream(in, out);
239: }
240:
241: private void serveDirectory(HttpServletRequest req,
242: HttpServletResponse res, boolean headOnly, String path,
243: File file) throws IOException {
244: log("indexing " + file);
245: if (!file.canRead()) {
246: res.sendError(HttpServletResponse.SC_FORBIDDEN);
247: return;
248: }
249: res.setStatus(HttpServletResponse.SC_OK);
250: res.setContentType("text/html");
251: OutputStream out = res.getOutputStream();
252: if (!headOnly) {
253: PrintStream p = new PrintStream(new BufferedOutputStream(
254: out));
255: p.println("<HTML><HEAD>");
256: p.println("<TITLE>Index of " + path + "</TITLE>");
257: p.println("</HEAD><BODY BGCOLOR=\"#F1D0F2\">");
258: p.println("<H2>Index of " + path + "</H2>");
259: p.println("<PRE>");
260: p.println("mode bytes last-changed name");
261: p.println("<HR>");
262: String[] names = file.list();
263: Acme.Utils.sortStrings(names);
264: for (int i = 0; i < names.length; ++i) {
265: File aFile = new File(file, names[i]);
266: String aFileType;
267: if (aFile.isDirectory())
268: aFileType = "d";
269: else if (aFile.isFile())
270: aFileType = "-";
271: else
272: aFileType = "?";
273: String aFileRead = (aFile.canRead() ? "r" : "-");
274: String aFileWrite = (aFile.canWrite() ? "w" : "-");
275: String aFileExe = "-";
276: String aFileSize = lengthftm.format(aFile.length());
277: while (aFileSize.length() < 12)
278: aFileSize = " " + aFileSize;
279: String aFileDate = Acme.Utils.lsDateStr(new Date(aFile
280: .lastModified()));
281: while (aFileDate.length() < 14)
282: aFileDate += " ";
283: String aFileDirsuf = (aFile.isDirectory() ? "/" : "");
284: String aFileSuf = (aFile.isDirectory() ? "/" : "");
285: p.println(aFileType + aFileRead + aFileWrite + aFileExe
286: + " " + aFileSize + " " + aFileDate + " "
287: + "<A HREF=\"" + names[i] + aFileDirsuf + "\">"
288: + names[i] + aFileSuf + "</A>");
289: }
290: p.println("</PRE>");
291: p.println("<HR>");
292: Serve.Identification.writeAddress(p);
293: p.println("</BODY></HTML>");
294: p.flush();
295: }
296: out.close();
297: }
298:
299: private void redirectDirectory(HttpServletRequest req,
300: HttpServletResponse res, String path, File file)
301: throws IOException {
302: log("redirecting " + path);
303: res.sendRedirect(path + "/");
304: }
305:
306: public void log(String msg) {
307: if (logenabled)
308: super.log(msg);
309: }
310: }
|