diff options
Diffstat (limited to 'mobile/android/geckoview/src/main/java/org/mozilla/geckoview/WebRequestError.java')
-rw-r--r-- | mobile/android/geckoview/src/main/java/org/mozilla/geckoview/WebRequestError.java | 380 |
1 files changed, 380 insertions, 0 deletions
diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/WebRequestError.java b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/WebRequestError.java new file mode 100644 index 0000000000..455078feb7 --- /dev/null +++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/WebRequestError.java @@ -0,0 +1,380 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- + * vim: ts=4 sw=4 expandtab: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.geckoview; + +import android.annotation.SuppressLint; +import androidx.annotation.AnyThread; +import androidx.annotation.IntDef; +import androidx.annotation.Nullable; +import java.io.ByteArrayInputStream; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.Arrays; +import org.mozilla.gecko.annotation.WrapForJNI; +import org.mozilla.gecko.util.XPCOMError; + +/** + * WebRequestError is simply a container for error codes and categories used by {@link + * GeckoSession.NavigationDelegate#onLoadError(GeckoSession, String, WebRequestError)}. + */ +@AnyThread +public class WebRequestError extends Exception { + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + ERROR_CATEGORY_UNKNOWN, + ERROR_CATEGORY_SECURITY, + ERROR_CATEGORY_NETWORK, + ERROR_CATEGORY_CONTENT, + ERROR_CATEGORY_URI, + ERROR_CATEGORY_PROXY, + ERROR_CATEGORY_SAFEBROWSING + }) + public @interface ErrorCategory {} + + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + ERROR_UNKNOWN, + ERROR_SECURITY_SSL, + ERROR_SECURITY_BAD_CERT, + ERROR_NET_RESET, + ERROR_NET_INTERRUPT, + ERROR_NET_TIMEOUT, + ERROR_CONNECTION_REFUSED, + ERROR_UNKNOWN_PROTOCOL, + ERROR_UNKNOWN_HOST, + ERROR_UNKNOWN_SOCKET_TYPE, + ERROR_UNKNOWN_PROXY_HOST, + ERROR_MALFORMED_URI, + ERROR_REDIRECT_LOOP, + ERROR_SAFEBROWSING_PHISHING_URI, + ERROR_SAFEBROWSING_MALWARE_URI, + ERROR_SAFEBROWSING_UNWANTED_URI, + ERROR_SAFEBROWSING_HARMFUL_URI, + ERROR_CONTENT_CRASHED, + ERROR_OFFLINE, + ERROR_PORT_BLOCKED, + ERROR_PROXY_CONNECTION_REFUSED, + ERROR_FILE_NOT_FOUND, + ERROR_FILE_ACCESS_DENIED, + ERROR_INVALID_CONTENT_ENCODING, + ERROR_UNSAFE_CONTENT_TYPE, + ERROR_CORRUPTED_CONTENT, + ERROR_DATA_URI_TOO_LONG, + ERROR_HTTPS_ONLY, + ERROR_BAD_HSTS_CERT + }) + public @interface Error {} + + /** + * This is normally used for error codes that don't currently fit into any of the other + * categories. + */ + public static final int ERROR_CATEGORY_UNKNOWN = 0x1; + + /** This is used for error codes that relate to SSL certificate validation. */ + public static final int ERROR_CATEGORY_SECURITY = 0x2; + + /** This is used for error codes relating to network problems. */ + public static final int ERROR_CATEGORY_NETWORK = 0x3; + + /** This is used for error codes relating to invalid or corrupt web pages. */ + public static final int ERROR_CATEGORY_CONTENT = 0x4; + + public static final int ERROR_CATEGORY_URI = 0x5; + public static final int ERROR_CATEGORY_PROXY = 0x6; + public static final int ERROR_CATEGORY_SAFEBROWSING = 0x7; + + /** An unknown error occurred */ + public static final int ERROR_UNKNOWN = 0x11; + + // Security + /** This is used for a variety of SSL negotiation problems. */ + public static final int ERROR_SECURITY_SSL = 0x22; + + /** This is used to indicate an untrusted or otherwise invalid SSL certificate. */ + public static final int ERROR_SECURITY_BAD_CERT = 0x32; + + // Network + /** The network connection was interrupted. */ + public static final int ERROR_NET_INTERRUPT = 0x23; + + /** The network request timed out. */ + public static final int ERROR_NET_TIMEOUT = 0x33; + + /** The network request was refused by the server. */ + public static final int ERROR_CONNECTION_REFUSED = 0x43; + + /** The network request tried to use an unknown socket type. */ + public static final int ERROR_UNKNOWN_SOCKET_TYPE = 0x53; + + /** A redirect loop was detected. */ + public static final int ERROR_REDIRECT_LOOP = 0x63; + + /** This device does not have a network connection. */ + public static final int ERROR_OFFLINE = 0x73; + + /** The request tried to use a port that is blocked by either the OS or Gecko. */ + public static final int ERROR_PORT_BLOCKED = 0x83; + + /** The connection was reset. */ + public static final int ERROR_NET_RESET = 0x93; + + /** + * GeckoView could not connect to this website in HTTPS-only mode. Call + * document.reloadWithHttpsOnlyException() in the error page to temporarily disable HTTPS only + * mode for this request. + * + * <p>See also {@link GeckoSession.NavigationDelegate#onLoadError} + */ + public static final int ERROR_HTTPS_ONLY = 0xA3; + + /** + * A certificate validation error occurred when connecting to a site that does not allow error + * overrides. + */ + public static final int ERROR_BAD_HSTS_CERT = 0xB3; + + // Content + /** A content type was returned which was deemed unsafe. */ + public static final int ERROR_UNSAFE_CONTENT_TYPE = 0x24; + + /** The content returned was corrupted. */ + public static final int ERROR_CORRUPTED_CONTENT = 0x34; + + /** The content process crashed. */ + public static final int ERROR_CONTENT_CRASHED = 0x44; + + /** The content has an invalid encoding. */ + public static final int ERROR_INVALID_CONTENT_ENCODING = 0x54; + + // URI + /** The host could not be resolved. */ + public static final int ERROR_UNKNOWN_HOST = 0x25; + + /** An invalid URL was specified. */ + public static final int ERROR_MALFORMED_URI = 0x35; + + /** An unknown protocol was specified. */ + public static final int ERROR_UNKNOWN_PROTOCOL = 0x45; + + /** A file was not found (usually used for file:// URIs). */ + public static final int ERROR_FILE_NOT_FOUND = 0x55; + + /** The OS blocked access to a file. */ + public static final int ERROR_FILE_ACCESS_DENIED = 0x65; + + /** A data:// URI is too long to load at the top level. */ + public static final int ERROR_DATA_URI_TOO_LONG = 0x75; + + // Proxy + /** The proxy server refused the connection. */ + public static final int ERROR_PROXY_CONNECTION_REFUSED = 0x26; + + /** The host name of the proxy server could not be resolved. */ + public static final int ERROR_UNKNOWN_PROXY_HOST = 0x36; + + // Safebrowsing + /** The requested URI was present in the "malware" blocklist. */ + public static final int ERROR_SAFEBROWSING_MALWARE_URI = 0x27; + + /** The requested URI was present in the "unwanted" blocklist. */ + public static final int ERROR_SAFEBROWSING_UNWANTED_URI = 0x37; + + /** The requested URI was present in the "harmful" blocklist. */ + public static final int ERROR_SAFEBROWSING_HARMFUL_URI = 0x47; + + /** The requested URI was present in the "phishing" blocklist. */ + public static final int ERROR_SAFEBROWSING_PHISHING_URI = 0x57; + + /** The error code, e.g. {@link #ERROR_MALFORMED_URI}. */ + public final int code; + + /** The error category, e.g. {@link #ERROR_CATEGORY_URI}. */ + public final int category; + + /** + * The server certificate used. This can be useful if the error code is is e.g. {@link + * #ERROR_SECURITY_BAD_CERT}. + */ + public final @Nullable X509Certificate certificate; + + /** + * Construct a new WebRequestError with the specified code and category. + * + * @param code An error code, e.g. {@link #ERROR_MALFORMED_URI} + * @param category An error category, e.g. {@link #ERROR_CATEGORY_URI} + */ + public WebRequestError(final @Error int code, final @ErrorCategory int category) { + this(code, category, null); + } + + /** + * Construct a new WebRequestError with the specified code and category. + * + * @param code An error code, e.g. {@link #ERROR_MALFORMED_URI} + * @param category An error category, e.g. {@link #ERROR_CATEGORY_URI} + * @param certificate The X509Certificate server certificate used, if applicable. + */ + public WebRequestError( + final @Error int code, final @ErrorCategory int category, final X509Certificate certificate) { + super(String.format("Request failed, error=0x%x, category=0x%x", code, category)); + this.code = code; + this.category = category; + this.certificate = certificate; + } + + @Override + public boolean equals(final Object other) { + if (other == null || !(other instanceof WebRequestError)) { + return false; + } + + final WebRequestError otherError = (WebRequestError) other; + + // We don't compare the certificate here because it's almost never what you want. + return otherError.code == this.code && otherError.category == this.category; + } + + @Override + public int hashCode() { + return Arrays.hashCode(new Object[] {category, code}); + } + + @WrapForJNI + /* package */ static WebRequestError fromGeckoError( + final long geckoError, + final int geckoErrorModule, + final int geckoErrorClass, + final byte[] certificateBytes) { + // XXX: the geckoErrorModule argument is redundant + assert geckoErrorModule == XPCOMError.getErrorModule(geckoError); + final int code = convertGeckoError(geckoError, geckoErrorClass); + final int category = getErrorCategory(XPCOMError.getErrorModule(geckoError), code); + X509Certificate certificate = null; + if (certificateBytes != null) { + try { + final CertificateFactory factory = CertificateFactory.getInstance("X.509"); + certificate = + (X509Certificate) + factory.generateCertificate(new ByteArrayInputStream(certificateBytes)); + } catch (final CertificateException e) { + throw new IllegalArgumentException("Unable to parse DER certificate"); + } + } + + return new WebRequestError(code, category, certificate); + } + + @SuppressLint("WrongConstant") + @WrapForJNI + /* package */ static @ErrorCategory int getErrorCategory( + final long errorModule, final @Error int error) { + if (errorModule == XPCOMError.NS_ERROR_MODULE_SECURITY) { + return ERROR_CATEGORY_SECURITY; + } + return error & 0xF; + } + + @WrapForJNI + /* package */ static @Error int convertGeckoError( + final long geckoError, final int geckoErrorClass) { + // safebrowsing + if (geckoError == XPCOMError.NS_ERROR_PHISHING_URI) { + return ERROR_SAFEBROWSING_PHISHING_URI; + } + if (geckoError == XPCOMError.NS_ERROR_MALWARE_URI) { + return ERROR_SAFEBROWSING_MALWARE_URI; + } + if (geckoError == XPCOMError.NS_ERROR_UNWANTED_URI) { + return ERROR_SAFEBROWSING_UNWANTED_URI; + } + if (geckoError == XPCOMError.NS_ERROR_HARMFUL_URI) { + return ERROR_SAFEBROWSING_HARMFUL_URI; + } + // content + if (geckoError == XPCOMError.NS_ERROR_CONTENT_CRASHED) { + return ERROR_CONTENT_CRASHED; + } + if (geckoError == XPCOMError.NS_ERROR_INVALID_CONTENT_ENCODING) { + return ERROR_INVALID_CONTENT_ENCODING; + } + if (geckoError == XPCOMError.NS_ERROR_UNSAFE_CONTENT_TYPE) { + return ERROR_UNSAFE_CONTENT_TYPE; + } + if (geckoError == XPCOMError.NS_ERROR_CORRUPTED_CONTENT) { + return ERROR_CORRUPTED_CONTENT; + } + // network + if (geckoError == XPCOMError.NS_ERROR_NET_RESET) { + return ERROR_NET_RESET; + } + if (geckoError == XPCOMError.NS_ERROR_NET_RESET) { + return ERROR_NET_INTERRUPT; + } + if (geckoError == XPCOMError.NS_ERROR_NET_TIMEOUT) { + return ERROR_NET_TIMEOUT; + } + if (geckoError == XPCOMError.NS_ERROR_CONNECTION_REFUSED) { + return ERROR_CONNECTION_REFUSED; + } + if (geckoError == XPCOMError.NS_ERROR_UNKNOWN_SOCKET_TYPE) { + return ERROR_UNKNOWN_SOCKET_TYPE; + } + if (geckoError == XPCOMError.NS_ERROR_REDIRECT_LOOP) { + return ERROR_REDIRECT_LOOP; + } + if (geckoError == XPCOMError.NS_ERROR_HTTPS_ONLY) { + return ERROR_HTTPS_ONLY; + } + if (geckoError == XPCOMError.NS_ERROR_BAD_HSTS_CERT) { + return ERROR_BAD_HSTS_CERT; + } + if (geckoError == XPCOMError.NS_ERROR_OFFLINE) { + return ERROR_OFFLINE; + } + if (geckoError == XPCOMError.NS_ERROR_PORT_ACCESS_NOT_ALLOWED) { + return ERROR_PORT_BLOCKED; + } + // uri + if (geckoError == XPCOMError.NS_ERROR_UNKNOWN_PROTOCOL) { + return ERROR_UNKNOWN_PROTOCOL; + } + if (geckoError == XPCOMError.NS_ERROR_UNKNOWN_HOST) { + return ERROR_UNKNOWN_HOST; + } + if (geckoError == XPCOMError.NS_ERROR_MALFORMED_URI) { + return ERROR_MALFORMED_URI; + } + if (geckoError == XPCOMError.NS_ERROR_FILE_NOT_FOUND) { + return ERROR_FILE_NOT_FOUND; + } + if (geckoError == XPCOMError.NS_ERROR_FILE_ACCESS_DENIED) { + return ERROR_FILE_ACCESS_DENIED; + } + // proxy + if (geckoError == XPCOMError.NS_ERROR_UNKNOWN_PROXY_HOST) { + return ERROR_UNKNOWN_PROXY_HOST; + } + if (geckoError == XPCOMError.NS_ERROR_PROXY_CONNECTION_REFUSED) { + return ERROR_PROXY_CONNECTION_REFUSED; + } + + if (XPCOMError.getErrorModule(geckoError) == XPCOMError.NS_ERROR_MODULE_SECURITY) { + if (geckoErrorClass == 1) { + return ERROR_SECURITY_SSL; + } + if (geckoErrorClass == 2) { + return ERROR_SECURITY_BAD_CERT; + } + } + + return ERROR_UNKNOWN; + } +} |