package freenet.clients.http;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.net.URL;
import java.util.Date;
import freenet.client.DefaultMIMETypes;
import freenet.l10n.NodeL10n;
import freenet.support.api.Bucket;
import freenet.support.api.HTTPRequest;
import freenet.support.io.FileBucket;
/**
* Static Toadlet.
* Serve up static files
*/
public class StaticToadlet extends Toadlet {
StaticToadlet() {
super(null);
}
public static final String ROOT_URL = "/static/";
public static final String ROOT_PATH = "staticfiles/";
public static final String OVERRIDE = "override/";
public static final String OVERRIDE_URL = ROOT_URL + OVERRIDE;
public void handleMethodGET(URI uri, HTTPRequest request, ToadletContext ctx) throws ToadletContextClosedException, IOException {
String path = uri.getPath();
if (!path.startsWith(ROOT_URL)) {
// we should never get any other path anyway
return;
}
try {
path = path.substring(ROOT_URL.length());
} catch (IndexOutOfBoundsException ioobe) {
this.sendErrorPage(ctx, 404, l10n("pathNotFoundTitle"), l10n("pathNotFound"));
return;
}
// be very strict about what characters we allow in the path, since
if (!path.matches("^[A-Za-z0-9\\._\\/\\-]*$") || (path.indexOf("..") != -1)) {
this.sendErrorPage(ctx, 404, l10n("pathNotFoundTitle"), l10n("pathInvalidChars"));
return;
}
if(path.startsWith(OVERRIDE)) {
File f = this.container.getOverrideFile();
if(f == null || (!f.exists()) || (f.isDirectory()) || (!f.isFile())) {
this.sendErrorPage(ctx, 404, l10n("pathNotFoundTitle"), l10n("pathInvalidChars"));
return;
}
f = f.getAbsoluteFile();
if(f == null || (!f.exists()) || (f.isDirectory()) || (!f.isFile())) {
this.sendErrorPage(ctx, 404, l10n("pathNotFoundTitle"), l10n("pathInvalidChars"));
return;
}
File parent = f.getParentFile();
// Basic sanity check.
// Prevents user from specifying root dir.
// They can still shoot themselves in the foot, but only when developing themes/using custom themes.
// Because of the .. check above, any malicious thing cannot break out of the dir anyway.
if(parent.getParentFile() == null) {
this.sendErrorPage(ctx, 404, l10n("pathNotFoundTitle"), l10n("pathInvalidChars"));
return;
}
File from = new File(parent, path.substring(OVERRIDE.length()));
if((!from.exists()) && (!from.isFile())) {
this.sendErrorPage(ctx, 404, l10n("pathNotFoundTitle"), l10n("pathInvalidChars"));
return;
}
try {
FileBucket fb = new FileBucket(from, true, false, false, false);
ctx.sendReplyHeadersStatic(200, "OK", null, DefaultMIMETypes.guessMIMEType(path, false), fb.size(), new Date(System.currentTimeMillis() - 1000)); // Already expired, we want it to reload it.
ctx.writeData(fb);
return;
} catch (IOException e) {
// Not strictly accurate but close enough
this.sendErrorPage(ctx, 404, l10n("pathNotFoundTitle"), l10n("pathNotFound"));
return;
}
}
InputStream strm = getClass().getResourceAsStream(ROOT_PATH+path);
if (strm == null) {
this.sendErrorPage(ctx, 404, l10n("pathNotFoundTitle"), l10n("pathNotFound"));
return;
}
Bucket data = ctx.getBucketFactory().makeBucket(strm.available());
OutputStream os = data.getOutputStream();
try {
byte[] cbuf = new byte[4096];
while(true) {
int r = strm.read(cbuf);
if(r == -1) break;
os.write(cbuf, 0, r);
}
} finally {
strm.close();
os.close();
}
URL url = getClass().getResource(ROOT_PATH+path);
Date mTime = getUrlMTime(url);
ctx.sendReplyHeadersStatic(200, "OK", null, DefaultMIMETypes.guessMIMEType(path, false), data.size(), mTime);
ctx.writeData(data);
}
/**
* Try to find the modification time for a URL, or return null if not possible
* We usually load our resources from the JAR, or possibly from a file in some setups, so we check the modification time of
* the JAR for resources in a jar and the mtime for files.
*/
private Date getUrlMTime(URL url) {
if (url.getProtocol().equals("jar")) {
File f = new File(url.getPath().substring(0, url.getPath().indexOf('!')));
return new Date(f.lastModified());
} else if (url.getProtocol().equals("file")) {
File f = new File(url.getPath());
return new Date(f.lastModified());
} else {
return null;
}
}
private String l10n(String key) {
return NodeL10n.getBase().getString("StaticToadlet."+key);
}
@Override
public String path() {
return ROOT_URL;
}
/** Do we have a specific static file? Note that override files are not
* supported here as it is a static method.
* @param The path to the file, relative to the staticfiles directory.
*/
public static boolean haveFile(String path) {
URL url = StaticToadlet.class.getResource(ROOT_PATH+path);
return url != null;
}
}