package plugins.CENO.Common; import java.net.InetAddress; import java.net.MalformedURLException; import java.net.URL; import java.net.UnknownHostException; import java.util.HashMap; import java.util.Map; import java.util.regex.Pattern; import org.apache.commons.validator.routines.InetAddressValidator; import org.apache.commons.validator.routines.UrlValidator; import freenet.client.InsertException; import freenet.keys.FreenetURI; import freenet.support.Base64; import freenet.support.IllegalBase64Exception; public class URLtoUSKTools { public static final String PORTAL_DOC_NAME = "CENO-RSS"; public static Map<String, String> splitURL(String requestPath) throws MalformedURLException { // Extract domain and extra path String domain, extraPath; int slashIndex = requestPath.indexOf('/'); // Support for URLs with queries that follow right after <host> without a slash / int queryIndex = requestPath.indexOf('?'); if (queryIndex > -1 && queryIndex < slashIndex) { slashIndex = queryIndex; } if (slashIndex < 1 || slashIndex == requestPath.length()) { domain = requestPath; extraPath = ""; } else { domain = requestPath.substring(0, slashIndex); extraPath = requestPath.substring(slashIndex + 1, requestPath.length()); } /* Extract meta strings from FreenetURI StringBuilder allMetaStrings = new StringBuilder(); for (String metaString : requestKey.getAllMetaStrings()) { if (!metaString.isEmpty()) { allMetaStrings.append("/" + metaString); } }*/ Map<String, String> splitMap = new HashMap<String, String>(); splitMap.put("domain", domain); splitMap.put("extraPath", extraPath); return splitMap; } /** * Computes the USK for a given URL so that: * <ul> * <li> CENO can lookup if this URL has been cached before</li> * <li> CENO knows the insert URI to use when caching a bundle</li> * </ul> * * @param requestPath the URL requested by the user/bundler * @return the calculated FreenetURI that corresponds to that resource * @throws MalformedURLException */ public static FreenetURI computeUSKfromURL(String requestPath, String requestURI) throws MalformedURLException { //TODO Once insertions with manifests that point to previous inserted files under the same domain // are implemented, remove Base64 encoding and use USKs in the format: // USK@{bridge public key, decryption key, encryption settings}/{domain}/{version}/{extra Path} String computedKey = requestURI.replaceFirst("SSK", "USK") + b64EncSafe(requestPath) + "/-1/"; return new FreenetURI(computedKey); } public static FreenetURI computeInsertURI(String domain, String insertURI) throws MalformedURLException { FreenetURI insertURIconfig = new FreenetURI(insertURI); FreenetURI result = new FreenetURI("USK", b64EncSafe(domain), insertURIconfig.getRoutingKey(), insertURIconfig.getCryptoKey(), insertURIconfig.getExtra()); try { result.checkInsertURI(); } catch (InsertException e) { throw new MalformedURLException("The computed insertion key for domain " + domain + " failed validity check"); } return result; } public static boolean isFeedURL(String urlParam) { if (urlParam == null || urlParam.isEmpty()) { return false; } if (urlParam.startsWith(PORTAL_DOC_NAME)) { return true; } return false; } public static boolean isPortalURL(String urlParam) { if (urlParam == null || urlParam.isEmpty()) { return false; } if (urlParam.equals(PORTAL_DOC_NAME)) { return true; } return false; } /** * Validates a URL parameter and formats it accordingly * * @param urlParam the URL to validate and format * @return the URL in format that can be processed by the CENO plugins * @throws MalformedURLException if URL parameter is not a valid URL */ public static String validateURL(String urlParam) throws MalformedURLException { if (urlParam == null || urlParam.isEmpty()) { throw new MalformedURLException("Given URL was empty"); } if (isFeedURL(urlParam)) { return urlParam; } // Won't serve favicons if (urlParam.endsWith("favicon.ico")) { throw new MalformedURLException("Won't serve favicons"); } // Remove preceding slash urlParam = urlParam.replaceFirst("^/", ""); // Remove the preceding "?url=" urlParam = urlParam.replaceFirst("(?i)^\\?url=", ""); // Check if urlParam is a Freenet key if (Pattern.matches("(?i)^(freenet:|USK@|CHK@|SSK@).*", urlParam)) { throw new MalformedURLException("Given URL looks like a Freenet key"); } // Remove the http/https protocols urlParam = urlParam.replaceFirst("(?i)^https?://", ""); // Make sure that only http/https is allowed if (Pattern.matches(".*://.*", urlParam)) { throw new MalformedURLException("Unsupported protocol"); } // If the url ends with slashes, remove them urlParam = urlParam.replaceAll("/+$", ""); // Throws MalformedURLException if a URL object cannot be created URL netURL = new URL("http://" + urlParam); // Validate urlParm using org.apache.commons UrlValidator UrlValidator urlValidator = new UrlValidator(); if (!urlValidator.isValid(netURL.toString())) { throw new MalformedURLException("Given URL failed UrlValidator check"); } // Make sure the URL host is not localhost if (netURL.getHost().equals("localhost")) { throw new MalformedURLException("Given URL references a local resource"); } // If the host is an Inet address, make sure it does not reference to a local resource InetAddressValidator inetValidator = new InetAddressValidator(); if (inetValidator.isValid(netURL.getHost())) { InetAddress inet; try { // !IMPORTANT! // Make sure that URL host is in IPv4 or IPv6 format before calling InetAddress.getByName() // because in case the host is a domain, it will be resolved and // DNS resolution can expose the identity of the users. inet = InetAddress.getByName(netURL.getHost()); } catch (UnknownHostException e) { // Since we have already checked that the urlParam host is a valid Inet address, // this UnknownHost exception will never be thrown e.printStackTrace(); throw new MalformedURLException(); } if (inet.isSiteLocalAddress()) { throw new MalformedURLException("Given URL references a local resource"); } } return urlParam; } public static FreenetURI getPortalFeedsUSK(String requestURI) throws MalformedURLException { return computeUSKfromURL(PORTAL_DOC_NAME, requestURI); } /** * Base64 encodes the given UTF-8 String, so that is URL and File safe (RFC4648 section 5) * @param in * @return */ public static String b64EncSafe(String in) { return Base64.encodeStandardUTF8(in).replace("+", "-").replace("/", "_"); } /** * Decodes the given base64url (RFC4648 section 5) to a UTF-8 String * @param in * @return */ public static String b64DecSafe(String in) throws IllegalBase64Exception { return Base64.decodeUTF8(in.replace("-", "+").replace("_", "/")); } }