}
if (ks.equals("/")) {
if (httprequest.isParameterSet("key")) {
String k = httprequest.getParam("key");
FreenetURI newURI;
try {
newURI = new FreenetURI(k);
} catch (MalformedURLException e) {
Logger.normal(this, "Invalid key: "+e+" for "+k, e);
sendErrorPage(ctx, 404, l10n("notFoundTitle"), NodeL10n.getBase().getString("FProxyToadlet.invalidKeyWithReason", new String[] { "reason" }, new String[] { e.toString() }));
return;
}
if(logMINOR) Logger.minor(this, "Redirecting to FreenetURI: "+newURI);
String requestedMimeType = httprequest.getParam("type");
String location = getLink(newURI, requestedMimeType, maxSize, httprequest.getParam("force", null), httprequest.isParameterSet("forcedownload"), maxRetries, overrideSize);
writeTemporaryRedirect(ctx, null, location);
return;
}
/*
* Redirect to the welcome page because no key was specified.
*/
try {
throw new RedirectException(new URI(null, null, null, -1, welcome.getPath(), uri.getQuery(), uri.getFragment()));
} catch (URISyntaxException e) {
/*
* This shouldn't happen because all the inputs to the URI constructor come from getters
* of existing URIs.
*/
Logger.error(FProxyToadlet.class, "Unexpected syntax error in URI: " + e);
writeTemporaryRedirect(ctx, "Internal error. Please check logs and report.", WelcomeToadlet.PATH);
return;
}
}else if(ks.equals("/favicon.ico")){
try {
throw new RedirectException(StaticToadlet.ROOT_URL+"favicon.ico");
} catch (URISyntaxException e) {
throw new Error(e);
}
} else if(ks.startsWith("/feed/") || ks.equals("/feed")) {
//TODO Better way to find the host. Find if https is used?
String host = ctx.getHeaders().get("host");
String atom = ctx.getAlertManager().getAtom("http://" + host);
byte[] buf = atom.getBytes("UTF-8");
ctx.sendReplyHeadersFProxy(200, "OK", null, "application/atom+xml", buf.length);
ctx.writeData(buf, 0, buf.length);
return;
}else if(ks.equals("/robots.txt") && ctx.doRobots()){
this.writeTextReply(ctx, 200, "Ok", "User-agent: *\nDisallow: /");
return;
}else if(ks.startsWith("/darknet/") || ks.equals("/darknet")) { //TODO (pre-build 1045 url format) remove when obsolete
writePermanentRedirect(ctx, "obsoleted", "/friends/");
return;
}else if(ks.startsWith("/opennet/") || ks.equals("/opennet")) { //TODO (pre-build 1045 url format) remove when obsolete
writePermanentRedirect(ctx, "obsoleted", "/strangers/");
return;
} else if(ks.startsWith("/queue/")) {
writePermanentRedirect(ctx, "obsoleted", "/downloads/");
return;
} else if(ks.startsWith("/config/")) {
writePermanentRedirect(ctx, "obsoleted", "/config/node");
return;
}
if(ks.startsWith("/"))
ks = ks.substring(1);
//first check of httprange before get
// only valid number format is checked here
String rangeStr = ctx.getHeaders().get("range");
if (rangeStr != null) {
try {
parseRange(rangeStr);
} catch (HTTPRangeException e) {
Logger.normal(this, "Invalid Range Header: "+rangeStr, e);
ctx.sendReplyHeaders(416, "Requested Range Not Satisfiable", null, null, 0);
return;
}
}
FreenetURI key;
try {
key = new FreenetURI(ks);
} catch (MalformedURLException e) {
PageNode page = ctx.getPageMaker().getPageNode(l10n("invalidKeyTitle"), ctx);
HTMLNode pageNode = page.outer;
HTMLNode contentNode = page.content;
HTMLNode errorInfobox = contentNode.addChild("div", "class", "infobox infobox-error");
errorInfobox.addChild("div", "class", "infobox-header", NodeL10n.getBase().getString("FProxyToadlet.invalidKeyWithReason", new String[] { "reason" }, new String[] { e.toString() }));
HTMLNode errorContent = errorInfobox.addChild("div", "class", "infobox-content");
errorContent.addChild("#", l10n("expectedKeyButGot"));
errorContent.addChild("code", ks);
errorContent.addChild("br");
errorContent.addChild(ctx.getPageMaker().createBackLink(ctx, l10n("goBack")));
errorContent.addChild("br");
addHomepageLink(errorContent);
this.writeHTMLReply(ctx, 400, l10n("invalidKeyTitle"), pageNode.generate());
return;
}
FetchContext fctx = getFetchContext(maxSize);
// max-size=-1 => use default
maxSize = fctx.maxOutputLength;
//We should run the ContentFilter by default
String forceString = httprequest.getParam("force");
long now = System.currentTimeMillis();
boolean force = false;
if(forceString != null) {
if(forceString.equals(getForceValue(key, now)) ||
forceString.equals(getForceValue(key, now-FORCE_GRAIN_INTERVAL)))
force = true;
}
if(restricted)
maxRetries = -2;
if(maxRetries >= -1) {
fctx.maxNonSplitfileRetries = maxRetries;
fctx.maxSplitfileBlockRetries = maxRetries;
}
if (!force && !httprequest.isParameterSet("forcedownload")) fctx.filterData = true;
else if(logMINOR) Logger.minor(this, "Content filter disabled via request parameter");
//Load the fetch context with the callbacks needed for web-pushing, if enabled
if(container.enableInlinePrefetch()) {
fctx.prefetchHook = new FoundURICallback() {
List<FreenetURI> uris = new ArrayList<FreenetURI>();
@Override
public void foundURI(FreenetURI uri) {
// Ignore
}
@Override
public void foundURI(FreenetURI uri, boolean inline) {
if(!inline) return;
if(logMINOR) Logger.minor(this, "Prefetching "+uri);
synchronized(this) {
if(uris.size() < MAX_PREFETCH)
// FIXME Maybe we should do this randomly, but since it's a DoS protection (in an obscure feature), if so we should do it in constant space!
uris.add(uri);
}
}
@Override
public void onText(String text, String type, URI baseURI) {
// Ignore
}
@Override
public void onFinishedPage() {
core.node.executor.execute(new Runnable() {
@Override
public void run() {
for(FreenetURI uri : uris) {
client.prefetch(uri, SECONDS.toMillis(60), 512*1024, prefetchAllowedTypes);
}
}
});
}
};
}
if(container.isFProxyWebPushingEnabled()) fctx.tagReplacer = new PushingTagReplacerCallback(core.getFProxy().fetchTracker, defaultMaxSize, ctx);
String requestedMimeType = httprequest.getParam("type", null);
fctx.overrideMIME = requestedMimeType;
String override = (requestedMimeType == null) ? "" : "?type="+URLEncoder.encode(requestedMimeType,true);
String maybeCharset = httprequest.isParameterSet("maybecharset") ? httprequest.getParam("maybecharset", null) : null;
fctx.charset = maybeCharset;
if(override.equals("") && maybeCharset != null)
override = "?maybecharset="+URLEncoder.encode(maybeCharset, true);
// No point passing ?force= across a redirect, since the key will change.
// However, there is every point in passing ?forcedownload.
if(httprequest.isParameterSet("forcedownload")) {
if(override.length() == 0) override = "?forcedownload";
else override = override+"&forcedownload";
}
Bucket data = null;
String mimeType = null;
String referer = sanitizeReferer(ctx);
FetchException fe = null;
FProxyFetchResult fr = null;
FProxyFetchWaiter fetch = null;
try {
fetch = fetchTracker.makeFetcher(key, maxSize, fctx, ctx.getReFilterPolicy());
} catch (FetchException e) {
fe = e;
}
if(fetch != null)
while(true) {
fr = fetch.getResult(!canSendProgress);
if(fr.hasData()) {
if(fr.getFetchCount() > 1 && !fr.hasWaited() && fr.getFetchCount() > 1 && key.isUSK() && context.uskManager.lookupKnownGood(USK.create(key)) > key.getSuggestedEdition()) {
Logger.normal(this, "Loading later edition...");
fetch.progress.requestImmediateCancel();
fr = null;
fetch = null;
try {
fetch = fetchTracker.makeFetcher(key, maxSize, fctx, ctx.getReFilterPolicy());
} catch (FetchException e) {
fe = e;
}
if(fetch == null) break;
continue;
}
if(logMINOR) Logger.minor(this, "Found data");
data = new NoFreeBucket(fr.data);
mimeType = fr.mimeType;
fetch.close(); // Not waiting any more, but still locked the results until sent
break;
} else if(fr.failed != null) {
if(logMINOR) Logger.minor(this, "Request failed");
fe = fr.failed;
fetch.close(); // Not waiting any more, but still locked the results until sent
break;
} else if(canSendProgress) {
if(logMINOR) Logger.minor(this, "Still in progress");
// Still in progress
boolean isJsEnabled=ctx.getContainer().isFProxyJavascriptEnabled() && ua != null && !ua.contains("AppleWebKit/");
boolean isWebPushingEnabled = false;
PageNode page = ctx.getPageMaker().getPageNode(l10n("fetchingPageTitle"), ctx);
HTMLNode pageNode = page.outer;
String location = getLink(key, requestedMimeType, maxSize, httprequest.getParam("force", null), httprequest.isParameterSet("forcedownload"), maxRetries, overrideSize);
HTMLNode headNode=page.headNode;
if(isJsEnabled){
//If the user has enabled javascript, we add a <noscript> http refresh(if he has disabled it in the browser)
headNode.addChild("noscript").addChild("meta", "http-equiv", "Refresh").addAttribute("content", "2;URL=" + location);
// If pushing is disabled, but js is enabled, then we add the original progresspage.js
if ((isWebPushingEnabled = ctx.getContainer().isFProxyWebPushingEnabled()) == false) {
HTMLNode scriptNode = headNode.addChild("script", "//abc");
scriptNode.addAttribute("type", "text/javascript");
scriptNode.addAttribute("src", "/static/js/progresspage.js");
}
}else{
//If he disabled it, we just put the http refresh meta, without the noscript
headNode.addChild("meta", "http-equiv", "Refresh").addAttribute("content", "2;URL=" + location);
}
HTMLNode contentNode = page.content;
HTMLNode infobox = contentNode.addChild("div", "class", "infobox infobox-information");
infobox.addChild("div", "class", "infobox-header", l10n("fetchingPageBox"));
HTMLNode infoboxContent = infobox.addChild("div", "class", "infobox-content");
infoboxContent.addAttribute("id", "infoContent");
infoboxContent.addChild(new ProgressInfoElement(fetchTracker, key, fctx, maxSize, ctx.isAdvancedModeEnabled(), ctx, isWebPushingEnabled));
HTMLNode table = infoboxContent.addChild("table", "border", "0");
HTMLNode progressCell = table.addChild("tr").addChild("td", "class", "request-progress");
if(fr.totalBlocks <= 0)
progressCell.addChild("#", NodeL10n.getBase().getString("QueueToadlet.unknown"));
else {
progressCell.addChild(new ProgressBarElement(fetchTracker,key,fctx,maxSize,ctx, isWebPushingEnabled));
}
infobox = contentNode.addChild("div", "class", "infobox infobox-information");
infobox.addChild("div", "class", "infobox-header", l10n("fetchingPageOptions"));
infoboxContent = infobox.addChild("div", "class", "infobox-content");
HTMLNode optionList = infoboxContent.addChild("ul");
optionList.addChild("li").addChild("p", l10n("progressOptionZero"));
addDownloadOptions(ctx, optionList, key, mimeType, false, false, core);
optionList.addChild("li").addChild(ctx.getPageMaker().createBackLink(ctx, l10n("goBackToPrev")));
optionList.addChild("li").addChild("a", new String[] { "href", "title" },
new String[] { "/", NodeL10n.getBase().getString("Toadlet.homepage") }, l10n("abortToHomepage"));
MultiValueTable<String, String> retHeaders = new MultiValueTable<String, String>();
//retHeaders.put("Refresh", "2; url="+location);
writeHTMLReply(ctx, 200, "OK", retHeaders, pageNode.generate());
fr.close();
fetch.close();
return;
} else if(fr != null)
fr.close();
}
try {
if(logMINOR)
Logger.minor(this, "FProxy fetching "+key+" ("+maxSize+ ')');
if(data == null && fe == null) {
boolean needsFetch=true;
//If we don't have the data, then check if an FProxyFetchInProgress has. It can happen when one FetchInProgress downloaded an image
//asynchronously, then loads it. This way a FetchInprogress will have the full image, and no need to block.
FProxyFetchInProgress progress=fetchTracker.getFetchInProgress(key, maxSize, fctx);
if(progress!=null){
FProxyFetchWaiter waiter=null;
FProxyFetchResult result=null;
try{
waiter=progress.getWaiter();
result=waiter.getResult(false);
if(result.failed==null && result.data!=null){
mimeType=result.mimeType;
data=result.data;
data=ctx.getBucketFactory().makeBucket(result.data.size());
BucketTools.copy(result.data, data);
needsFetch=false;
}
}finally{
if(waiter!=null){
progress.close(waiter);
}
if(result!=null){
progress.close(result);
}
}
}
if(needsFetch){
//If we don't have the data, then we need to fetch it and block until it is available
FetchResult result = fetch(key, maxSize, new RequestClient() {
@Override
public boolean persistent() {
return false;
}
@Override
public boolean realTimeFlag() {
return true;
} }, fctx);
// Now, is it safe?
data = result.asBucket();
mimeType = result.getMimeType();
}
} else if(fe != null) throw fe;
handleDownload(ctx, data, ctx.getBucketFactory(), mimeType, requestedMimeType, forceString, httprequest.isParameterSet("forcedownload"), "/", key, "&max-size="+maxSizeDownload, referer, true, ctx, core, fr != null, maybeCharset);
} catch (FetchException e) {
//Handle exceptions thrown from the ContentFilter
String msg = e.getMessage();
if(logMINOR) {
Logger.minor(this, "Failed to fetch "+uri+" : "+e);
}
if(e.newURI != null) {
if(accept != null && (accept.startsWith("text/css") || accept.startsWith("image/")) && recursion++ < MAX_RECURSION) {
// If it's an image or a CSS fetch, auto-follow the redirect, up to a limit.
String link = getLink(e.newURI, requestedMimeType, maxSize, httprequest.getParam("force", null), httprequest.isParameterSet("forcedownload"), maxRetries, overrideSize);
try {
uri = new URI(link);
innerHandleMethodGET(uri, httprequest, ctx, recursion);
return;
} catch (URISyntaxException e1) {
Logger.error(this, "Caught "+e1+" parsing new link "+link, e1);
}
}
Toadlet.writePermanentRedirect(ctx, msg,
getLink(e.newURI, requestedMimeType, maxSize, httprequest.getParam("force", null), httprequest.isParameterSet("forcedownload"), maxRetries, overrideSize));
} else if(e.mode == FetchExceptionMode.TOO_BIG) {
PageNode page = ctx.getPageMaker().getPageNode(l10n("fileInformationTitle"), ctx);
HTMLNode pageNode = page.outer;
HTMLNode contentNode = page.content;
HTMLNode infobox = contentNode.addChild("div", "class", "infobox infobox-information");
infobox.addChild("div", "class", "infobox-header", l10n("largeFile"));
HTMLNode infoboxContent = infobox.addChild("div", "class", "infobox-content");
HTMLNode fileInformationList = infoboxContent.addChild("ul");
HTMLNode option = fileInformationList.addChild("li");
option.addChild("#", (l10n("filenameLabel") + ' '));
option.addChild("a", "href", '/' + key.toString(), getFilename(key, e.getExpectedMimeType()));
String mime = writeSizeAndMIME(fileInformationList, e);
infobox = contentNode.addChild("div", "class", "infobox infobox-information");
infobox.addChild("div", "class", "infobox-header", l10n("explanationTitle"));
infoboxContent = infobox.addChild("div", "class", "infobox-content");
infoboxContent.addChild("#", l10n("largeFileExplanationAndOptions"));
HTMLNode optionList = infoboxContent.addChild("ul");
//HTMLNode optionTable = infoboxContent.addChild("table", "border", "0");
if(!restricted) {
option = optionList.addChild("li");
HTMLNode optionForm = option.addChild("form", new String[] { "action", "method" }, new String[] {'/' + key.toString(), "get" });
optionForm.addChild("input", new String[] { "type", "name", "value" }, new String[] { "hidden", "max-size", String.valueOf(e.expectedSize == -1 ? Long.MAX_VALUE : e.expectedSize*2) });
if (requestedMimeType != null)
optionForm.addChild("input", new String[] { "type", "name", "value" }, new String[] { "hidden", "type", requestedMimeType });
if(maxRetries >= -1)
optionForm.addChild("input", new String[] { "type", "name", "value" }, new String[] { "hidden", "max-retries", Integer.toString(maxRetries) });
optionForm.addChild("input", new String[] { "type", "name", "value" }, new String[] { "submit", "fetch", l10n("fetchLargeFileAnywayAndDisplayButton") });
optionForm.addChild("#", " - " + l10n("fetchLargeFileAnywayAndDisplay"));
addDownloadOptions(ctx, optionList, key, mime, false, false, core);
}
//optionTable.addChild("tr").addChild("td", "colspan", "2").addChild("a", new String[] { "href", "title" }, new String[] { "/", NodeL10n.getBase().getString("Toadlet.homepage") }, l10n("abortToHomepage"));
optionList.addChild("li").addChild("a", new String[] { "href", "title" }, new String[] { "/", NodeL10n.getBase().getString("Toadlet.homepage") }, l10n("abortToHomepage"));
//option = optionTable.addChild("tr").addChild("td", "colspan", "2");
optionList.addChild("li").addChild(ctx.getPageMaker().createBackLink(ctx, l10n("goBackToPrev")));
writeHTMLReply(ctx, 200, "OK", pageNode.generate());
} else {
PageNode page = ctx.getPageMaker().getPageNode(e.getShortMessage(), ctx);
HTMLNode pageNode = page.outer;
HTMLNode contentNode = page.content;
HTMLNode infobox = contentNode.addChild("div", "class", "infobox infobox-error");
infobox.addChild("div", "class", "infobox-header", l10n("errorWithReason", "error", e.getShortMessage()));
HTMLNode infoboxContent = infobox.addChild("div", "class", "infobox-content");
HTMLNode fileInformationList = infoboxContent.addChild("ul");
HTMLNode option = fileInformationList.addChild("li");
option.addChild("#", (l10n("filenameLabel") + ' '));
option.addChild("a", "href", '/' + key.toString(), getFilename(key, e.getExpectedMimeType()));
String mime = writeSizeAndMIME(fileInformationList, e);
infobox = contentNode.addChild("div", "class", "infobox infobox-error");
infobox.addChild("div", "class", "infobox-header", l10n("explanationTitle"));
UnsafeContentTypeException filterException = null;
if(e.getCause() != null && e.getCause() instanceof UnsafeContentTypeException) {
filterException = (UnsafeContentTypeException)e.getCause();
}
infoboxContent = infobox.addChild("div", "class", "infobox-content");
if(filterException == null)
infoboxContent.addChild("p", l10n("unableToRetrieve"));
else
infoboxContent.addChild("p", l10n("unableToSafelyDisplay"));
if(e.isFatal() && filterException == null)
infoboxContent.addChild("p", l10n("errorIsFatal"));
infoboxContent.addChild("p", msg);
if(filterException != null) {
if(filterException.details() != null) {
HTMLNode detailList = infoboxContent.addChild("ul");
for(String detail : filterException.details()) {
detailList.addChild("li", detail);
}
}
}
if(e.errorCodes != null) {
infoboxContent.addChild("p").addChild("pre").addChild("#", e.errorCodes.toVerboseString());
}
infobox = contentNode.addChild("div", "class", "infobox infobox-error");
infobox.addChild("div", "class", "infobox-header", l10n("options"));
infoboxContent = infobox.addChild("div", "class", "infobox-content");
HTMLNode optionList = infoboxContent.addChild("ul");
PluginInfoWrapper keyUtil;
if((e.mode == FetchExceptionMode.NOT_IN_ARCHIVE || e.mode == FetchExceptionMode.NOT_ENOUGH_PATH_COMPONENTS)) {
// first look for the newest version
if ((keyUtil = core.node.pluginManager.getPluginInfo("plugins.KeyUtils.KeyUtilsPlugin")) != null) {
option = optionList.addChild("li");
if (keyUtil.getPluginLongVersion() < 5010)
NodeL10n.getBase().addL10nSubstitution(option, "FProxyToadlet.openWithKeyExplorer", new String[] { "link" }, new HTMLNode[] { HTMLNode.link("/KeyUtils/?automf=true&key=" + key.toString()) });
else {
NodeL10n.getBase().addL10nSubstitution(option, "FProxyToadlet.openWithKeyExplorer", new String[] { "link" }, new HTMLNode[] { HTMLNode.link("/KeyUtils/?key=" + key.toString()) });
option = optionList.addChild("li");
NodeL10n.getBase().addL10nSubstitution(option, "FProxyToadlet.openWithSiteExplorer", new String[] { "link" }, new HTMLNode[] { HTMLNode.link("/KeyUtils/Site?key=" + key.toString()) });
}
} else if ((keyUtil = core.node.pluginManager.getPluginInfo("plugins.KeyExplorer.KeyExplorer")) != null) {
option = optionList.addChild("li");
if (keyUtil.getPluginLongVersion() > 4999)
NodeL10n.getBase().addL10nSubstitution(option, "FProxyToadlet.openWithKeyExplorer", new String[] { "link" }, new HTMLNode[] { HTMLNode.link("/KeyExplorer/?automf=true&key=" + key.toString())});
else
NodeL10n.getBase().addL10nSubstitution(option, "FProxyToadlet.openWithKeyExplorer", new String[] { "link" }, new HTMLNode[] { HTMLNode.link("/plugins/plugins.KeyExplorer.KeyExplorer/?key=" + key.toString())});
}
}
if(filterException != null) {
if((mime.equals("application/x-freenet-index")) && (core.node.pluginManager.isPluginLoaded("plugins.ThawIndexBrowser.ThawIndexBrowser"))) {
option = optionList.addChild("li");
NodeL10n.getBase().addL10nSubstitution(option, "FProxyToadlet.openAsThawIndex", new String[] { "link" }, new HTMLNode[] { HTMLNode.link("/plugins/plugins.ThawIndexBrowser.ThawIndexBrowser/?key=" + key.toString()).addChild("b") });
}
option = optionList.addChild("li");
// FIXME: is this safe? See bug #131
MediaType textMediaType = new MediaType("text/plain");
textMediaType.setParameter("charset", (e.getExpectedMimeType() != null) ? MediaType.getCharsetRobust(e.getExpectedMimeType()) : null);