package pygmy.handlers;
import java.io.*;
import java.util.*;
import java.util.logging.Logger;
import java.util.logging.Level;
import java.net.HttpURLConnection;
import java.net.URI;
import java.text.NumberFormat;
import pygmy.core.*;
/**
* <p>
* This sends back a directory listing in html text. The look and feel is
* customizable through a cascading style sheet to use. The directory listing
* is ordered by {@link DirectoryHandler.ComparableComparator}. The referenced
* classes in the CSS are:
* </p>
*
* <p>
* <ul>
* <li>.navigationbar</li>
* <li>.box</li>
* <li>tr.tableheader</li>
* <li>tr.fileentry</li>
* <li>tr.altfileentry</li>
* <li>a.whitelink</li>
* <li>directory</li>
* <li>topHeader</li>
* <li>nameColumn</li>
* <li>typeColumn</li>
* <li>sizeColumn</li>
* </ul>
* </p>
*
* <p>
* <table class="inner">
* <tr class="header"><td>Parameter Name</td><td>Explanation</td><td>Default Value</td><td>Required</td></tr>
* <tr class="row"><td>url-prefix</td><td>The prefix to filter request urls.</td><td>None</td><td>Yes</td></tr>
* <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>
* <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>
* </table>
* </p>
*/
public class DirectoryHandler extends AbstractHandler implements Handler {
private static final Logger log = Logger.getLogger( DirectoryHandler.class.getName() );
private File root;
public static final ConfigOption ROOT_OPTION = new ConfigOption( "root", true, "Directory path to share." );
public static final ConfigOption CSS_OPTION = new ConfigOption( "css", false, "A URL of the stylesheet for theme of the directory listing." );
public boolean initialize(String handlerName, Server server) {
super.initialize( handlerName, server );
root = new File( ROOT_OPTION.getProperty( server, handlerName ) );
return true;
}
protected boolean handleBody( HttpRequest request, HttpResponse response ) throws IOException {
log.finest( "DirectoryHandler called." );
try {
String absoluteDir = root.getAbsolutePath();
File directory = Http.translatePath( absoluteDir, request.getUrl().substring( getUrlPrefix().length() ) );
if( !directory.isDirectory() ) {
log.info( "Not a directory.");
return false;
}
if( !Http.isSecure( absoluteDir, directory ) ) {
log.warning( "Access denied to " + directory.getAbsolutePath() );
return false;
}
log.warning( "Processing directory listing for " + directory.getAbsolutePath() );
StringBuffer templateHeader = new StringBuffer();
String decodedUrl = java.net.URLDecoder.decode( request.getUrl(), "UTF-8" );
addFolderNavigation( templateHeader, decodedUrl );
addTableHeaders( templateHeader );
addFilesAndFolders( request, directory.listFiles(), templateHeader);
addTableFooter( templateHeader );
response.setMimeType( "text/html" );
PrintWriter out = response.getPrintWriter();
out.write( addHtmlHeader( request ) );
out.write("<body>\n");
out.write( templateHeader.toString() );
out.write( "</body>\n" );
out.write( "</html>" );
return true;
} catch( Exception e ) {
log.log( Level.SEVERE, "Request failed due to Exception.", e );
response.sendError( HttpURLConnection.HTTP_INTERNAL_ERROR, "Exception during processing directory" );
return true;
}
}
private void addTableFooter(StringBuffer templateHeader) {
templateHeader.append("</table>\n");
templateHeader.append("</div>\n");
}
private String addHtmlHeader( HttpRequest request ) throws IOException {
StringBuffer templateHeader = new StringBuffer();
templateHeader.append( "<html>\n");
templateHeader.append( "<head>\n");
String css = CSS_OPTION.getProperty( server, handlerName );
if( css != null ) {
templateHeader.append( "<link rel=\"stylesheet\" type=\"text/css\" href=\"" );
templateHeader.append( request.createUrl( css ) );
templateHeader.append( "\">\n");
} else {
addStyleDefintion(templateHeader);
}
templateHeader.append( "</head>\n");
return templateHeader.toString();
}
private void addStyleDefintion(StringBuffer templateHeader) {
templateHeader.append("<style>\n");
templateHeader.append("body,td,ul,li,a,div,p,pre,span {color: #333333; font-family: Verdana, Arial, Helvetica; font-size: 10pt;}\n");
templateHeader.append("th {color:#333333; font-family: Verdana, Arial, Helvetica; font-size:10pt; text-align:center; }\n");
templateHeader.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");
templateHeader.append(".box { background-color:#d1dde9; border:1px #369 solid; border-top:0; padding:4px 4px 4px 4px; background-color:#77AADD}\n");
templateHeader.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");
templateHeader.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");
templateHeader.append("tr.tableheader { background-color: #ffffe4; }\n");
templateHeader.append("tr.fileentry { background-color: #EEEEEE; }\n");
templateHeader.append("tr.altfileentry { background-color: #FFFFFF; }\n");
templateHeader.append("td.nameColumn { text-align: left; }\n");
templateHeader.append("td.typeColumn { text-align: center; }\n");
templateHeader.append("td.sizeColumn { text-align: right; }\n");
templateHeader.append("a {color: #0000A0;font-family: Verdana, Arial, Helvetica;text-decoration:none;}\n");
templateHeader.append("a:active {color: #FFFFFF; text-decoration : none;}\n");
templateHeader.append("a:link {color: #336699; text-decoration : none;}\n");
templateHeader.append("a:visited {color: #336699; text-decoration : none;}\n");
templateHeader.append("a:hover {color: #000000; text-decoration : none;}\n");
templateHeader.append("a.whitelink {color: #FFFFFF; text-decoration: none;}\n");
templateHeader.append("a.whitelink:visited {color: #FFFFFF;}\n");
templateHeader.append("a.whitelink:hover {color: #AAAAAA; }\n");
templateHeader.append("</style>");
}
private void addFolderNavigation( StringBuffer templateHeader, String decodedUrl ) {
templateHeader.append( "<div class=\"navigationbar\">\n");
templateHeader.append( "<span class=\"topHeader\">\n");
StringTokenizer token = new StringTokenizer( decodedUrl, "/" );
StringBuffer buf = new StringBuffer( decodedUrl.length() );
templateHeader.append(" <a href=\"/\" class=\"whitelink\">[home]</a>\n");
while( token.hasMoreElements() ) {
String path = token.nextToken();
buf.append( "/" );
buf.append( path );
templateHeader.append( "/" );
templateHeader.append( "<a href=\"" );
templateHeader.append( buf.toString() );
templateHeader.append( "\" class=\"whitelink\">");
templateHeader.append( path );
templateHeader.append( "</a>" );
}
templateHeader.append( "</span>\n</div>\n");
}
private void addTableHeaders(StringBuffer templateHeader) {
templateHeader.append("<div class=\"box\">\n");
templateHeader.append("<table width=\"100%\" cellspacing=\"0\" cellpadding=\"2\">\n" );
templateHeader.append("<tr class=\"tableheader\">\n");
templateHeader.append("<th>Name</td>");
templateHeader.append("<th>Type</td>");
templateHeader.append("<th>Size</td>");
templateHeader.append("\n</tr>\n");
}
private void addFilesAndFolders( HttpRequest request, File[] files, StringBuffer templateHeader ) throws IOException {
URI rootUri = this.root.toURI();
ComparableComparator comp = new ComparableComparator();
TreeMap dirMap = new TreeMap( comp );
TreeMap fileMap = new TreeMap( comp );
StringBuffer fileBuffer = new StringBuffer();
for( int i = 0; i < files.length; i++ ) {
fileBuffer.delete( 0, fileBuffer.length() );
if( files[i].isDirectory() ) {
String name = files[i].getName();
fileBuffer.append( "<td class=\"nameColumn\"> <span class=\"directory\">" );
fileBuffer.append( "<small><a href=\"");
fileBuffer.append( getHttpHyperlink( request, rootUri, files[i]) );
fileBuffer.append( "\"> " );
fileBuffer.append( name );
fileBuffer.append( "</a></small></span></td>\n" );
fileBuffer.append( "<td class=\"typeColumn\"> <small>Folder</small></td>\n" );
fileBuffer.append( "<td class=\"sizeColumn\"> </td>\n" );
fileBuffer.append( "</tr>\n");
dirMap.put( name, fileBuffer.toString() );
} else {
String absolutePath = files[i].getAbsolutePath();
String mimeType = getMimeType( absolutePath );
if( mimeType != null ) {
String name = files[i].getName();
fileBuffer.append( "<td class=\"nameColumn\"><small> <a href=\"");
fileBuffer.append( getHttpHyperlink( request, rootUri, files[i] ) );
fileBuffer.append( "\">" );
fileBuffer.append( name );
fileBuffer.append( "</a></small></td>\n" );
fileBuffer.append( "<td class=\"typeColumn\"><small> ");
fileBuffer.append( mimeType );
fileBuffer.append( "</small></td>\n" );
fileBuffer.append( "<td class=\"sizeColumn\"> <small>" );
fileBuffer.append( NumberFormat.getIntegerInstance().format( files[i].length() ) );
fileBuffer.append( "</small> </td>\n");
fileMap.put( name, fileBuffer.toString() );
}
}
}
int count = 0;
count = writeOutMap(templateHeader, dirMap, count );
count = writeOutMap(templateHeader, fileMap, count );
}
private int writeOutMap(StringBuffer templateHeader, TreeMap dirMap, int count) {
String[] styles = { "fileentry", "altfileentry" };
for( Iterator i = dirMap.keySet().iterator(); i.hasNext(); ) {
templateHeader.append( "<tr class=\"");
templateHeader.append( styles[ count % 2] );
templateHeader.append( "\">\n");
templateHeader.append( (String)dirMap.get( i.next() ) );
templateHeader.append( "</tr>\n");
count++;
}
return count;
}
private String getHttpHyperlink( HttpRequest request, URI directory, File file ) throws IOException {
String prefixRelative = directory.relativize( file.toURI() ).getPath();
String urlPrefix = getUrlPrefix();
if( urlPrefix.endsWith("/") ) {
return urlPrefix + prefixRelative;
} else if( prefixRelative.startsWith("/") ) {
return urlPrefix + prefixRelative;
} else {
return request.createUrl( urlPrefix + "/" + prefixRelative );
}
}
/**
* This orders directories by calling compareTo() method.
*/
public static class ComparableComparator implements Comparator {
public int compare(Object o1, Object o2) {
Comparable c1 = (Comparable)o1;
Comparable c2 = (Comparable)o2;
return c1.compareTo( c2 );
}
}
}