package plugins.CENO.Client; import java.net.MalformedURLException; import java.util.concurrent.TimeUnit; import net.minidev.json.JSONObject; import plugins.CENO.CENOErrCode; import plugins.CENO.CENOException; import plugins.CENO.Client.ULPRManager.ULPRStatus; import plugins.CENO.Common.GetSyncCallback; import plugins.CENO.Common.URLtoUSKTools; import plugins.CENO.FreenetInterface.ConnectionOverview.NodeConnections; import freenet.client.FetchException; import freenet.client.FetchException.FetchExceptionMode; import freenet.keys.FreenetURI; import freenet.pluginmanager.PluginHTTPException; import freenet.support.IllegalBase64Exception; import freenet.support.Logger; import freenet.support.api.HTTPRequest; /** * Handler for requests to the /lookup path. Responsible for determining whether * a bundle for a URL exists in the local or the distributed cache, as well as * for its retrieval. */ public class LookupHandler extends AbstractCENOClientHandler { private static Long noPeersTimer = null; @Override public String handleHTTPGet(HTTPRequest request) throws PluginHTTPException { // If "client" GET parameter is set to "HTML", then LCS will compose an // HTML response instead of the JSON Object boolean clientIsHtml = isClientHtml(request); // Check if URL parameter of the GET request is Empty String urlParam = request.getParam("url", ""); if (urlParam.isEmpty()) { return returnError(new CENOException(CENOErrCode.LCS_HANDLER_URL_INVALID), clientIsHtml); } if (!clientIsHtml) { // Base64 Decode the URL parameter try { urlParam = URLtoUSKTools.b64DecSafe(urlParam); } catch (IllegalBase64Exception e) { return returnError(new CENOException(CENOErrCode.LCS_HANDLER_URL_DECODE), clientIsHtml); } } // Validate the URL requested try { urlParam = URLtoUSKTools.validateURL(urlParam); } catch (MalformedURLException e) { return returnError(new CENOException(CENOErrCode.LCS_HANDLER_URL_INVALID), clientIsHtml); } // Calculate the retrieval address of the inserted bundle for this URL FreenetURI calculatedUSK = null; try { calculatedUSK = URLtoUSKTools.computeUSKfromURL(urlParam, CENOClient.getBridgeKey()); } catch (Exception e) { return returnError(new CENOException(CENOErrCode.LCS_HANDLER_URL_INVALID), clientIsHtml); } // Make a synchronous lookup in the local cache for the bundled version of the URL String localFetchResult = null; byte[] localFetchBytes = null; try { localFetchBytes = localCacheLookup(calculatedUSK); } catch (CENOException e) { return returnError(e, clientIsHtml); } /* * If the bundle was found and retrieved from the the local cache, return it * to the agent that made the request. * Resources of Ultra Light Passive Requests (ULPRs) in the distributed cache, once * successfully retrieved, are also available in the local cache. */ if (localFetchBytes != null) { localFetchResult = new String(localFetchBytes); if (clientIsHtml) { return localFetchResult; } else { JSONObject jsonResponse = new JSONObject(); jsonResponse.put("complete", true); jsonResponse.put("found", true); jsonResponse.put("bundle", localFetchResult); return jsonResponse.toJSONString(); } } // If the bundle was not found in the local cache: NodeConnections nodeConnections = CENOClient.nodeInterface.getConnections(); if (nodeConnections.getCurrent() == 0) { LookupHandler.noPeersTimer = (LookupHandler.noPeersTimer == null) ? System.currentTimeMillis() : LookupHandler.noPeersTimer; if (System.currentTimeMillis() - LookupHandler.noPeersTimer > TimeUnit.MINUTES.toMillis(5)) { // The node is not connected to any peers for longer than 5 mins. // Could it be a firewall/connectivity issue? return returnError(new CENOException(CENOErrCode.LCS_NODE_NOT_ENOUGH_PEERS), clientIsHtml); } } // If the Freenet node is connected to less than 3 peers, the process will be slow // and we inform the users appropriately if (nodeConnections.getCurrent() < 3) { return returnError(new CENOException(CENOErrCode.LCS_NODE_INITIALIZING), clientIsHtml); } LookupHandler.noPeersTimer = null; // The node is in state of performing ULPRs and we initiate one for the calculated SSK ULPRStatus urlULPRStatus; try { urlULPRStatus = ULPRManager.lookupULPR(urlParam); } catch (CENOException e) { return returnError(e, clientIsHtml); } if (urlULPRStatus == ULPRStatus.failed) { // Unlikely to happen return returnError(new CENOException(CENOErrCode.LCS_LOOKUP_ULPR_FAILED), clientIsHtml); } // Check whether the request timeout has expired if (RequestSender.getInstance().shouldSignalBridge(urlParam)) { if (clientIsHtml) { // HTML client cannot signal RS to make requests for bundles to the bridge, so LookupHandler // initiates such a request //boolean isX_CENO_Rewrite = (request.getHeader("X-Ceno-Rewritten") != null) ? true : false; RequestSender.getInstance().requestFromBridge(urlParam); return printStaticHTMLReplace("resources/requestedFromBridge.html", "[urlRequested]", urlParam); } else { JSONObject jsonResponse = new JSONObject(); jsonResponse.put("complete", true); jsonResponse.put("found", false); return jsonResponse.toJSONString(); } } // Request to the bridge for the requested URL has not timed out and a ULPR is in progress. if (clientIsHtml) { return printStaticHTMLReplace("resources/sentULPR.html", "[urlRequested]", urlParam); } else { JSONObject jsonResponse = new JSONObject(); jsonResponse.put("complete", false); return jsonResponse.toJSONString(); } } /** * Perform a synchronous lookup for a USK in the node's local cache. * As soon as a passive request in the distributed cache gets successfully * completed, the bundle will also be available in the local cache. * * @param calculatedUSK the Freenet key to lookup for the bundle * @return the content of the bundle if it is found, <code>null</code> * otherwise */ private byte[] localCacheLookup(FreenetURI calculatedUSK) throws CENOException { // Local cache lookups do not support "-1" as the edition of a USK if (calculatedUSK.getSuggestedEdition() < 0) { calculatedUSK = calculatedUSK.setSuggestedEdition(0); } GetSyncCallback getSyncCallback = new GetSyncCallback(CENOClient.nodeInterface.getRequestClient()); byte[] fetchResult = null; try { CENOClient.nodeInterface.localFetchURI(calculatedUSK, getSyncCallback); fetchResult = getSyncCallback.getResult(45L, TimeUnit.SECONDS); } catch (FetchException e) { if (e.getMode() == FetchExceptionMode.PERMANENT_REDIRECT) { fetchResult = localCacheLookup(e.newURI); } else if (e.isFatal()) { Logger.warning(this, "Fatal fetch exception while looking in the local cache for a USK, Exception: " + e.getMessage()); Logger.normal(this, "Fatal fetch exception while looking in the local cache for USK: " + calculatedUSK + " Exception: " + e.getMessage()); throw new CENOException(CENOErrCode.LCS_LOOKUP_LOCAL); } else { Logger.error(this, "Unhandled exception while looking in the local cache for a USK, Exception: " + e.getMessage()); Logger.normal(this, "Unhandled exception while looking in the local cache for USK: " + calculatedUSK + " Exception: " + e.getMessage()); } } return fetchResult; } @Override public String handleHTTPPost(HTTPRequest request) throws PluginHTTPException { // LCS won't handle POST requests return "LookupHandler: POST request received on /lookup path"; } }