package org.lobobrowser.request; import java.net.MalformedURLException; import java.net.URL; import java.util.Collection; import java.util.LinkedList; import org.eclipse.jdt.annotation.NonNull; import org.lobobrowser.util.Urls; import de.malkusch.whoisServerList.publicSuffixList.PublicSuffixList; import de.malkusch.whoisServerList.publicSuffixList.PublicSuffixListFactory; public final class DomainValidation { public static boolean isValidCookieDomain(String domain, final String requestHostName) { String plainDomain; if (!domain.startsWith(".")) { // Valid domains must start with a dot according to RFC 2109, but // RFC 2965 specifies a dot is prepended in the Set-Cookie2 header. plainDomain = domain; domain = "." + domain; } else { plainDomain = domain.substring(1); } final String plainDomainTL = plainDomain.toLowerCase(); final String hostNameTL = requestHostName.toLowerCase(); if (plainDomainTL.equals(hostNameTL)) { return true; } else { if (!hostNameTL.endsWith(plainDomainTL)) { return false; } else { // plainDomainTL is a suffix of hostName TL. Now ensure the first non-common character is a '.', // and there is a residual character after that final int nonCommonLength = hostNameTL.length() - plainDomainTL.length(); final boolean residualCharacterExists = nonCommonLength >= 2; if (!residualCharacterExists) { return false; } else { final char firstNonCommonCharacter = hostNameTL.charAt(nonCommonLength - 1); if (firstNonCommonCharacter != '.') { return false; } } } } return !isPublicSuffix(plainDomain); } private static final PublicSuffixList suffixList = new PublicSuffixListFactory().build(); /** * Returns true if the given domain is a public suffix. * * @param domain * The domain to check. Expected <b>not</b> to have any leading or * trailing '.' * @return true if the given domain is a public suffix */ public static boolean isPublicSuffix(final String domain) { return suffixList.isPublicSuffix(domain); } /** * Returns a collection of domains that are acceptable for cookies originating * from the given hostname */ public static Collection<String> getPossibleDomains(final String hostName) { // TODO: reuse collection object instead of creating a new one per recursive call. final Collection<String> domains = new LinkedList<>(); domains.add(hostName); final int dotIdx = hostName.indexOf('.', 1); if (dotIdx == -1) { return domains; } final String testDomain = hostName.substring(dotIdx); if (!isValidCookieDomain(testDomain, hostName)) { return domains; } domains.addAll(getPossibleDomains(testDomain.substring(1))); return domains; } public static boolean isLikelyHostName(final String name) { final String nameTL = name.toLowerCase(); if (nameTL.startsWith("www.")) { return true; } if (endsWithGTLD(name)) { return true; } final int lastDotIdx = nameTL.lastIndexOf('.'); if (lastDotIdx == -1) { return false; } // Check for country code. return lastDotIdx == (nameTL.length() - 3); } private static boolean endsWithGTLD(final String name) { if (name.length() == 0) { return false; } else if (isPublicSuffix(name)) { return true; } else { final int sepIndex = name.indexOf('.'); if (sepIndex < 0) { return false; } else { return endsWithGTLD(name.substring(sepIndex + 1)); } } } public static @NonNull URL guessURL(URL baseURL, String spec) throws MalformedURLException { URL finalURL; try { if (baseURL != null) { final int colonIdx = spec.indexOf(':'); final String newProtocol = colonIdx == -1 ? null : spec.substring(0, colonIdx); if ((newProtocol != null) && !newProtocol.equalsIgnoreCase(baseURL.getProtocol())) { baseURL = null; } } finalURL = Urls.createURL(baseURL, spec); } catch (final MalformedURLException mfu) { spec = spec.trim(); final int idx = spec.indexOf(':'); if (idx == -1) { final int slashIdx = spec.indexOf('/'); if (slashIdx == 0) { // A file, absolute finalURL = new URL("file:" + spec); } else { if (slashIdx == -1) { // No slash, no colon, must be host. finalURL = new URL(baseURL, "http://" + spec); } else { final String possibleHost = spec.substring(0, slashIdx).toLowerCase(); finalURL = guessProtocol(baseURL, spec, possibleHost); } } } else { if (idx == 1) { // Likely a drive finalURL = new URL(baseURL, "file:" + spec); } else { final String possibleHost = spec.substring(0, idx).toLowerCase(); finalURL = guessProtocol(baseURL, spec, possibleHost); } } } if (!"".equals(finalURL.getHost()) && (finalURL.toExternalForm().indexOf(' ') != -1)) { throw new MalformedURLException("There are blanks in the URL: " + finalURL.toExternalForm()); } return finalURL; } private static URL guessProtocol(final URL baseURL, final String spec, final String possibleHost) throws MalformedURLException { if (DomainValidation.isLikelyHostName(possibleHost)) { // TODO: Use https when possible return new URL(baseURL, "http://" + spec); } else { // TODO: Should file URLs be guessed? Probably not. return new URL(baseURL, "file:" + spec); } } public static @NonNull URL guessURL(final String spec) throws MalformedURLException { return guessURL(null, spec); } }