package edu.washington.cs.oneswarm.ui.gwt.server.handlers;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.MalformedURLException;
import java.net.URLClassLoader;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.gudy.azureus2.core3.internat.MessageText;
import org.gudy.azureus2.core3.util.Debug;
import org.gudy.azureus2.core3.util.SystemProperties;
import org.mortbay.jetty.HttpHeaders;
import org.mortbay.jetty.Request;
import org.mortbay.jetty.handler.AbstractHandler;
public class FileHandler extends AbstractHandler {
private static Logger logger = Logger.getLogger(FileHandler.class.getName());
private final static String INDEX_FILE_NAME = "oneswarmgwt/OneSwarmGWT.html";
private final static String EMBEDDED_FILE_NAME = "oneswarmgwt/OneSwarmEmbedded.html";
public static String mServerRootPath = "";
private static long jarBuiltTime = System.currentTimeMillis();
static {
File jarFile = new File(SystemProperties.getApplicationPath() + File.separator
+ "OneSwarmAzMods.jar");
logger.finest("using jar file mod time for last_modified: " + jarFile);
if (jarFile.exists()) {
jarBuiltTime = jarFile.lastModified();
logger.finest("using jar timestamp for last_modified: " + new Date(jarBuiltTime));
} else {
logger.warning("Unable to get time stamp of jar file (not found), using system time instead.");
}
}
private ConcurrentHashMap<String, Long> modifiedTimes = new ConcurrentHashMap<String, Long>();
private ConcurrentHashMap<String, Integer> fileSizes = new ConcurrentHashMap<String, Integer>();
/* The ClassLoader used to retrieve resources. */
private ClassLoader classLoader = null;
public FileHandler() {
// If we can't find the resource in the current ClassLoader, we might be
// the SWT target
// in debug mode. Eclipse won't have built the GWT targets, but if we
// built them
// externally, retrieve from the gwt-dist directory.
if (System.getProperty("debug.war") != null) {
try {
classLoader = new URLClassLoader(new java.net.URL[] { new File(
System.getProperty("debug.war")).toURI().toURL() }, getClass()
.getClassLoader());
// remove the leading '/' in paths.
mServerRootPath = "";
} catch (MalformedURLException e) {
e.printStackTrace();
}
} else {
classLoader = getClass().getClassLoader();
}
}
public void handle(String target, HttpServletRequest request, HttpServletResponse response,
int dispatch) throws IOException, ServletException {
int lastSlash = target.lastIndexOf('/');
// get the filename (we are ignoring directories, for now...)
String filename = "/";
if (lastSlash >= 0) {
filename = target.substring(lastSlash + 1, target.length());
}
logger.finer("req: " + target + " / fname: " + filename);
// check if default file
if (target.equals("/")) {
serve(INDEX_FILE_NAME, request, response);
} else if (filename.startsWith(EMBEDDED_FILE_NAME)) {
serve(EMBEDDED_FILE_NAME, request, response);
} else {
// ok, match, serv it
if (!serve(target, request, response)) {
Debug.out("file not found: " + target);
}
}
}
public static String getContentType(String filename) {
int lastDot = filename.lastIndexOf('.');
if (lastDot > 0) {
String suffix = filename.substring(lastDot + 1, filename.length());
// System.out.println("file suffix='" + suffix + "'");
if (suffix.equals("html")) {
return "text/html";
} else if (suffix.equals("js")) {
return "application/javascript";
} else if (suffix.equals("swf")) {
return "application/x-shockwave-flash";
} else if (suffix.equals("css")) {
return "text/css";
} else if (suffix.equals("gif")) {
return "image/gif";
} else if (suffix.equals("jpg")) {
return "image/jpeg";
} else if (suffix.equals("flv")) {
return "video/x-FLV";
} else if (suffix.equals("png")) {
return "image/png";
} else if (suffix.equalsIgnoreCase("rpc")) {
return "text/html";
} else if (suffix.equalsIgnoreCase("ico")) {
return "image/x-icon";
} else {
Debug.out("unknown file suffix: ." + suffix);
}
}
// just return text by default
return "application/unknown";
}
private boolean serve(String filename, HttpServletRequest request, HttpServletResponse response)
throws IOException {
logger.finer("got request for: " + filename);
if (filename.startsWith("/")) {
filename = filename.substring(1);
}
String fullPath = mServerRootPath + filename;
/**
* Hack to deal with 1.6 upgrade hosted / real world mode hacks
*/
if (fullPath.startsWith("images") || fullPath.equals("favicon.ico")
|| fullPath.startsWith("player/")) {
fullPath = mServerRootPath + "oneswarmgwt/" + fullPath;
}
boolean useCache = true;
/*
* don't use cache for the index file, it is small and we need to modify
* it when the language changes
*/
if (INDEX_FILE_NAME.equals(filename)) {
useCache = false;
logger.fine("got request for index file, skipping cache");
response.setHeader(HttpHeaders.PRAGMA, "no-cache");
response.setHeader(HttpHeaders.CACHE_CONTROL, "no-cache, must-revalidate");
}
if (useCache) {
long last_modified;
if (jarBuiltTime > 0) {
last_modified = jarBuiltTime;
// logger.finest("using jar timestamp for last_modified");
} else {
if (!modifiedTimes.containsKey(filename)) {
modifiedTimes.put(filename, System.currentTimeMillis());
}
last_modified = modifiedTimes.get(filename);
}
if (last_modified > 0) {
long if_modified = request.getDateHeader(HttpHeaders.IF_MODIFIED_SINCE);
if (if_modified > 0 && last_modified / 1000 == if_modified / 1000) {
response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
((Request) request).setHandled(true);
logger.finest("not modified, " + last_modified);
return true;
}
// ok, we have to serve the file, set the header
response.setDateHeader(HttpHeaders.LAST_MODIFIED, last_modified);
logger.finest("setting modified tag, " + last_modified + " (prev was: "
+ if_modified + ")");
}
// set the expire time
if (filename.endsWith("nocache.js")) {
// important, set no cache on these
logger.finest("setting no-cache on: " + filename);
response.setHeader(HttpHeaders.PRAGMA, "no-cache");
response.setHeader(HttpHeaders.CACHE_CONTROL, "no-cache, must-revalidate");
} else if (filename.endsWith(".cache.png") || filename.endsWith(".cache.html")) {
// these file are named with their md5sum, this means they will
// NEVER change: set cache to a year
logger.finest("setting extra long cache on: " + filename);
int oneYearInSeconds = 365 * 24 * 60 * 60;
response.setDateHeader(HttpHeaders.EXPIRES, System.currentTimeMillis()
+ (oneYearInSeconds * 1000));
response.setHeader(HttpHeaders.CACHE_CONTROL, "max-age=" + oneYearInSeconds
+ ", public");
} else if (filename.startsWith("oneswarmgwt/images/")
|| filename.startsWith("gwt/standard")) {
// tell the browser to cache images up to 24 hours to speed up
// load
int secondsToCache = 24 * 60 * 60;
response.setDateHeader(HttpHeaders.EXPIRES, System.currentTimeMillis()
+ (secondsToCache * 1000));
response.setHeader(HttpHeaders.CACHE_CONTROL, "max-age=" + secondsToCache
+ ", public");
logger.finest("setting 24h cache on: " + filename);
} else {
logger.finest("not setting custom cache rule on: " + filename);
}
}
InputStream inputstream = classLoader.getResourceAsStream(fullPath);
if (inputstream == null) {
// ok, file not found, return
return false;
}
int contentLength;
if (fileSizes.containsKey(filename)) {
contentLength = fileSizes.get(filename);
logger.finest("Cached size: File: " + filename + " content length: " + contentLength);
} else {
/*
* calc the content length
*/
contentLength = 0;
while (inputstream.read() != -1) {
contentLength++;
}
fileSizes.put(filename, contentLength);
inputstream = classLoader.getResourceAsStream(fullPath);
logger.finest("Calculated: File: " + filename + " content length: " + contentLength);
}
ServletOutputStream outputstream = response.getOutputStream();
String contentType = getContentType(filename);
response.setContentType(contentType);
response.setStatus(HttpServletResponse.SC_OK);
/*
* inject the language
*/
if (INDEX_FILE_NAME.equals(filename)) {
injectLocaleMetaTag(request, inputstream, outputstream);
} else {
response.setContentLength(contentLength);
copyStream(inputstream, outputstream);
}
outputstream.close();
// response.getWriter().println("<h1>Hello</h1>");
((Request) request).setHandled(true);
logger.finest("served: " + contentType + " " + filename);
return true;
}
private void copyStream(InputStream inputstream, OutputStream outputstream) throws IOException {
byte[] buffer = new byte[1024];
int len;
while ((len = inputstream.read(buffer)) > 0) {
outputstream.write(buffer, 0, len);
}
outputstream.flush();
}
private void injectLocaleMetaTag(HttpServletRequest request, InputStream in, OutputStream out)
throws IOException {
/*
* a list of injectors that replace some text in the html page with
* another text
*/
List<HTMLTagInjector> injectors = new LinkedList<HTMLTagInjector>();
injectors.add(new LocaleInjector());
injectors.add(new RightClickInjector());
BufferedReader bin = new BufferedReader(new InputStreamReader(in));
BufferedWriter bout = new BufferedWriter(new OutputStreamWriter(out));
String line;
lineloop: while ((line = bin.readLine()) != null) {
for (Iterator<HTMLTagInjector> iterator = injectors.iterator(); iterator.hasNext();) {
HTMLTagInjector injector = iterator.next();
/*
* check if it matches
*/
if (injector.matchesLine(line)) {
// write out the replacement line
bout.write(injector.getReplacementLine(request) + "\r\n");
// remove this injector
iterator.remove();
// and continue to the next line
continue lineloop;
}
}
bout.write(line + "\r\n");
}
bout.flush();
}
static interface HTMLTagInjector {
public boolean matchesLine(String line);
public String getReplacementLine(HttpServletRequest request);
}
static class LocaleInjector implements HTMLTagInjector {
public boolean matchesLine(String line) {
if (line.trim().equals("<head>")) {
return true;
} else {
return false;
}
}
public String getReplacementLine(HttpServletRequest request) {
Locale currentLocale = MessageText.getCurrentLocale();
String locale = "en_US";
if (currentLocale != null) {
locale = currentLocale.getLanguage() + "_" + currentLocale.getCountry();
}
logger.finer("injected language info, locale=" + locale);
return "<head>\r\n<meta name='gwt:property' content='locale=" + locale + "'/>";
}
}
static class RightClickInjector implements HTMLTagInjector {
public boolean matchesLine(String line) {
if (line.trim().equals("<body>")) {
return true;
} else {
return false;
}
}
public String getReplacementLine(HttpServletRequest request) {
boolean rightClickEnabled = false;
if (request != null && request.getCookies() != null) {
for (Cookie c : request.getCookies()) {
if (c.getName().equals("os-disable_right_click")) {
if ("0".equals(c.getValue())) {
rightClickEnabled = true;
}
}
}
}
if (rightClickEnabled) {
logger.finer("injected right click enabled");
return "<body oncontextmenu='return false;'>";
} else {
return "<body>";
}
}
}
}