001: package pygmy.handlers;
002:
003: import java.io.*;
004: import java.util.*;
005: import java.util.logging.Logger;
006: import java.util.logging.Level;
007: import java.net.HttpURLConnection;
008: import java.net.URI;
009: import java.text.NumberFormat;
010:
011: import pygmy.core.*;
012:
013: /**
014: * <p>
015: * This sends back a directory listing in html text. The look and feel is
016: * customizable through a cascading style sheet to use. The directory listing
017: * is ordered by {@link DirectoryHandler.ComparableComparator}. The referenced
018: * classes in the CSS are:
019: * </p>
020: *
021: * <p>
022: * <ul>
023: * <li>.navigationbar</li>
024: * <li>.box</li>
025: * <li>tr.tableheader</li>
026: * <li>tr.fileentry</li>
027: * <li>tr.altfileentry</li>
028: * <li>a.whitelink</li>
029: * <li>directory</li>
030: * <li>topHeader</li>
031: * <li>nameColumn</li>
032: * <li>typeColumn</li>
033: * <li>sizeColumn</li>
034: * </ul>
035: * </p>
036: *
037: * <p>
038: * <table class="inner">
039: * <tr class="header"><td>Parameter Name</td><td>Explanation</td><td>Default Value</td><td>Required</td></tr>
040: * <tr class="row"><td>url-prefix</td><td>The prefix to filter request urls.</td><td>None</td><td>Yes</td></tr>
041: * <tr class="altrow"><td>root</td><td>A local system path to the root of the folder to share.</td><td>None</td><td>Yes</td></tr>
042: * <tr class="row"><td>css</td><td>The name stylesheet to use for customizing the style of the display. If unspecified a default style will be inlined in the HTML.</td><td>None</td><td>No</td></tr>
043: * </table>
044: * </p>
045: */
046: public class DirectoryHandler extends AbstractHandler implements
047: Handler {
048: private static final Logger log = Logger
049: .getLogger(DirectoryHandler.class.getName());
050: private File root;
051:
052: public static final ConfigOption ROOT_OPTION = new ConfigOption(
053: "root", true, "Directory path to share.");
054: public static final ConfigOption CSS_OPTION = new ConfigOption(
055: "css", false,
056: "A URL of the stylesheet for theme of the directory listing.");
057:
058: public boolean initialize(String handlerName, Server server) {
059: super .initialize(handlerName, server);
060: root = new File(ROOT_OPTION.getProperty(server, handlerName));
061: return true;
062: }
063:
064: protected boolean handleBody(HttpRequest request,
065: HttpResponse response) throws IOException {
066: log.finest("DirectoryHandler called.");
067: try {
068: String absoluteDir = root.getAbsolutePath();
069: File directory = Http.translatePath(absoluteDir, request
070: .getUrl().substring(getUrlPrefix().length()));
071:
072: if (!directory.isDirectory()) {
073: log.info("Not a directory.");
074: return false;
075: }
076: if (!Http.isSecure(absoluteDir, directory)) {
077: log.warning("Access denied to "
078: + directory.getAbsolutePath());
079: return false;
080: }
081: log.warning("Processing directory listing for "
082: + directory.getAbsolutePath());
083: StringBuffer templateHeader = new StringBuffer();
084:
085: String decodedUrl = java.net.URLDecoder.decode(request
086: .getUrl(), "UTF-8");
087: addFolderNavigation(templateHeader, decodedUrl);
088: addTableHeaders(templateHeader);
089: addFilesAndFolders(request, directory.listFiles(),
090: templateHeader);
091: addTableFooter(templateHeader);
092:
093: response.setMimeType("text/html");
094: PrintWriter out = response.getPrintWriter();
095: out.write(addHtmlHeader(request));
096: out.write("<body>\n");
097: out.write(templateHeader.toString());
098: out.write("</body>\n");
099: out.write("</html>");
100: return true;
101: } catch (Exception e) {
102: log
103: .log(Level.SEVERE,
104: "Request failed due to Exception.", e);
105: response.sendError(HttpURLConnection.HTTP_INTERNAL_ERROR,
106: "Exception during processing directory");
107: return true;
108: }
109: }
110:
111: private void addTableFooter(StringBuffer templateHeader) {
112: templateHeader.append("</table>\n");
113: templateHeader.append("</div>\n");
114: }
115:
116: private String addHtmlHeader(HttpRequest request)
117: throws IOException {
118: StringBuffer templateHeader = new StringBuffer();
119: templateHeader.append("<html>\n");
120: templateHeader.append("<head>\n");
121: String css = CSS_OPTION.getProperty(server, handlerName);
122: if (css != null) {
123: templateHeader
124: .append("<link rel=\"stylesheet\" type=\"text/css\" href=\"");
125: templateHeader.append(request.createUrl(css));
126: templateHeader.append("\">\n");
127: } else {
128: addStyleDefintion(templateHeader);
129:
130: }
131: templateHeader.append("</head>\n");
132: return templateHeader.toString();
133: }
134:
135: private void addStyleDefintion(StringBuffer templateHeader) {
136: templateHeader.append("<style>\n");
137: templateHeader
138: .append("body,td,ul,li,a,div,p,pre,span {color: #333333; font-family: Verdana, Arial, Helvetica; font-size: 10pt;}\n");
139: templateHeader
140: .append("th {color:#333333; font-family: Verdana, Arial, Helvetica; font-size:10pt; text-align:center; }\n");
141: templateHeader
142: .append(".navigationbar {background-color:#7094b8;color:#f6f6ee;border-bottom:1px #666 solid;border-right:1px #666 solid;font:bold 11px tahoma,verdana,sans-serif;padding:3px 2px 3px 4px;margin-top:10px;}\n");
143: templateHeader
144: .append(".box { background-color:#d1dde9; border:1px #369 solid; border-top:0; padding:4px 4px 4px 4px; background-color:#77AADD}\n");
145: templateHeader
146: .append(".directory { padding-top: 2px;padding-right: 0px;padding-bottom: 0px;padding-left: 16px;background-image: url(/web/folder16.gif);background-repeat: no-repeat;background-position: left center;}\n");
147: templateHeader
148: .append(".topHeader { padding-top: 2px;padding-right: 0px;padding-bottom: 0px;padding-left: 16px;background-image: url(/web/folder16.gif);background-repeat: no-repeat;background-position: left center;}\n");
149: templateHeader
150: .append("tr.tableheader { background-color: #ffffe4; }\n");
151: templateHeader
152: .append("tr.fileentry { background-color: #EEEEEE; }\n");
153: templateHeader
154: .append("tr.altfileentry { background-color: #FFFFFF; }\n");
155: templateHeader.append("td.nameColumn { text-align: left; }\n");
156: templateHeader
157: .append("td.typeColumn { text-align: center; }\n");
158: templateHeader.append("td.sizeColumn { text-align: right; }\n");
159:
160: templateHeader
161: .append("a {color: #0000A0;font-family: Verdana, Arial, Helvetica;text-decoration:none;}\n");
162: templateHeader
163: .append("a:active {color: #FFFFFF; text-decoration : none;}\n");
164: templateHeader
165: .append("a:link {color: #336699; text-decoration : none;}\n");
166: templateHeader
167: .append("a:visited {color: #336699; text-decoration : none;}\n");
168: templateHeader
169: .append("a:hover {color: #000000; text-decoration : none;}\n");
170:
171: templateHeader
172: .append("a.whitelink {color: #FFFFFF; text-decoration: none;}\n");
173: templateHeader
174: .append("a.whitelink:visited {color: #FFFFFF;}\n");
175: templateHeader.append("a.whitelink:hover {color: #AAAAAA; }\n");
176: templateHeader.append("</style>");
177: }
178:
179: private void addFolderNavigation(StringBuffer templateHeader,
180: String decodedUrl) {
181: templateHeader.append("<div class=\"navigationbar\">\n");
182: templateHeader.append("<span class=\"topHeader\">\n");
183: StringTokenizer token = new StringTokenizer(decodedUrl, "/");
184: StringBuffer buf = new StringBuffer(decodedUrl.length());
185: templateHeader
186: .append(" <a href=\"/\" class=\"whitelink\">[home]</a>\n");
187: while (token.hasMoreElements()) {
188: String path = token.nextToken();
189: buf.append("/");
190: buf.append(path);
191: templateHeader.append("/");
192: templateHeader.append("<a href=\"");
193: templateHeader.append(buf.toString());
194: templateHeader.append("\" class=\"whitelink\">");
195: templateHeader.append(path);
196: templateHeader.append("</a>");
197: }
198: templateHeader.append("</span>\n</div>\n");
199: }
200:
201: private void addTableHeaders(StringBuffer templateHeader) {
202: templateHeader.append("<div class=\"box\">\n");
203: templateHeader
204: .append("<table width=\"100%\" cellspacing=\"0\" cellpadding=\"2\">\n");
205: templateHeader.append("<tr class=\"tableheader\">\n");
206: templateHeader.append("<th>Name</td>");
207: templateHeader.append("<th>Type</td>");
208: templateHeader.append("<th>Size</td>");
209: templateHeader.append("\n</tr>\n");
210: }
211:
212: private void addFilesAndFolders(HttpRequest request, File[] files,
213: StringBuffer templateHeader) throws IOException {
214: URI rootUri = this .root.toURI();
215: ComparableComparator comp = new ComparableComparator();
216: TreeMap dirMap = new TreeMap(comp);
217: TreeMap fileMap = new TreeMap(comp);
218: StringBuffer fileBuffer = new StringBuffer();
219: for (int i = 0; i < files.length; i++) {
220: fileBuffer.delete(0, fileBuffer.length());
221: if (files[i].isDirectory()) {
222: String name = files[i].getName();
223: fileBuffer
224: .append("<td class=\"nameColumn\"> <span class=\"directory\">");
225: fileBuffer.append("<small><a href=\"");
226: fileBuffer.append(getHttpHyperlink(request, rootUri,
227: files[i]));
228: fileBuffer.append("\"> ");
229: fileBuffer.append(name);
230: fileBuffer.append("</a></small></span></td>\n");
231: fileBuffer
232: .append("<td class=\"typeColumn\"> <small>Folder</small></td>\n");
233: fileBuffer
234: .append("<td class=\"sizeColumn\"> </td>\n");
235: fileBuffer.append("</tr>\n");
236: dirMap.put(name, fileBuffer.toString());
237: } else {
238: String absolutePath = files[i].getAbsolutePath();
239: String mimeType = getMimeType(absolutePath);
240: if (mimeType != null) {
241: String name = files[i].getName();
242: fileBuffer
243: .append("<td class=\"nameColumn\"><small> <a href=\"");
244: fileBuffer.append(getHttpHyperlink(request,
245: rootUri, files[i]));
246: fileBuffer.append("\">");
247: fileBuffer.append(name);
248: fileBuffer.append("</a></small></td>\n");
249: fileBuffer
250: .append("<td class=\"typeColumn\"><small> ");
251: fileBuffer.append(mimeType);
252: fileBuffer.append("</small></td>\n");
253: fileBuffer
254: .append("<td class=\"sizeColumn\"> <small>");
255: fileBuffer.append(NumberFormat.getIntegerInstance()
256: .format(files[i].length()));
257: fileBuffer.append("</small> </td>\n");
258: fileMap.put(name, fileBuffer.toString());
259: }
260: }
261: }
262: int count = 0;
263: count = writeOutMap(templateHeader, dirMap, count);
264: count = writeOutMap(templateHeader, fileMap, count);
265: }
266:
267: private int writeOutMap(StringBuffer templateHeader,
268: TreeMap dirMap, int count) {
269: String[] styles = { "fileentry", "altfileentry" };
270: for (Iterator i = dirMap.keySet().iterator(); i.hasNext();) {
271: templateHeader.append("<tr class=\"");
272: templateHeader.append(styles[count % 2]);
273: templateHeader.append("\">\n");
274: templateHeader.append((String) dirMap.get(i.next()));
275: templateHeader.append("</tr>\n");
276: count++;
277: }
278:
279: return count;
280: }
281:
282: private String getHttpHyperlink(HttpRequest request, URI directory,
283: File file) throws IOException {
284: String prefixRelative = directory.relativize(file.toURI())
285: .getPath();
286: String urlPrefix = getUrlPrefix();
287: if (urlPrefix.endsWith("/")) {
288: return urlPrefix + prefixRelative;
289: } else if (prefixRelative.startsWith("/")) {
290: return urlPrefix + prefixRelative;
291: } else {
292: return request.createUrl(urlPrefix + "/" + prefixRelative);
293: }
294: }
295:
296: /**
297: * This orders directories by calling compareTo() method.
298: */
299: public static class ComparableComparator implements Comparator {
300: public int compare(Object o1, Object o2) {
301: Comparable c1 = (Comparable) o1;
302: Comparable c2 = (Comparable) o2;
303:
304: return c1.compareTo(c2);
305: }
306: }
307: }
|