diff options
| author | Remi NGUYEN VAN <reminv@google.com> | 2018-05-23 17:15:44 -0700 |
|---|---|---|
| committer | android-build-merger <android-build-merger@google.com> | 2018-05-23 17:15:44 -0700 |
| commit | 7bea387500da0a49b0d8892442ef5e2c93a4017e (patch) | |
| tree | 2bd07675b18da9b3b256124a58099c90da1d4b1a /core/java | |
| parent | dd194d2e20811a217134b8c681d76f44416841a6 (diff) | |
| parent | 23d3487caaad1bd20ad50251311018cb852802f4 (diff) | |
Merge "Add configurable captive portal probes" into pi-dev am: 1f8f21af60
am: 23d3487caa
Change-Id: Ia958815325d1466345e9626efc8f62fc9d08d774
Diffstat (limited to 'core/java')
4 files changed, 208 insertions, 0 deletions
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index c3b8f3959fec..c5cb1f5b7cf8 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -238,6 +238,14 @@ public class ConnectivityManager { public static final String EXTRA_CAPTIVE_PORTAL_URL = "android.net.extra.CAPTIVE_PORTAL_URL"; /** + * Key for passing a {@link android.net.captiveportal.CaptivePortalProbeSpec} to the captive + * portal login activity. + * {@hide} + */ + public static final String EXTRA_CAPTIVE_PORTAL_PROBE_SPEC = + "android.net.extra.CAPTIVE_PORTAL_PROBE_SPEC"; + + /** * Key for passing a user agent string to the captive portal login activity. * {@hide} */ diff --git a/core/java/android/net/captiveportal/CaptivePortalProbeResult.java b/core/java/android/net/captiveportal/CaptivePortalProbeResult.java index 614c0b8dd17f..1634694cc71f 100644 --- a/core/java/android/net/captiveportal/CaptivePortalProbeResult.java +++ b/core/java/android/net/captiveportal/CaptivePortalProbeResult.java @@ -16,6 +16,8 @@ package android.net.captiveportal; +import android.annotation.Nullable; + /** * Result of calling isCaptivePortal(). * @hide @@ -23,6 +25,7 @@ package android.net.captiveportal; public final class CaptivePortalProbeResult { public static final int SUCCESS_CODE = 204; public static final int FAILED_CODE = 599; + public static final int PORTAL_CODE = 302; public static final CaptivePortalProbeResult FAILED = new CaptivePortalProbeResult(FAILED_CODE); public static final CaptivePortalProbeResult SUCCESS = @@ -32,15 +35,23 @@ public final class CaptivePortalProbeResult { public final String redirectUrl; // Redirect destination returned from Internet probe. public final String detectUrl; // URL where a 204 response code indicates // captive portal has been appeased. + @Nullable + public final CaptivePortalProbeSpec probeSpec; public CaptivePortalProbeResult(int httpResponseCode) { this(httpResponseCode, null, null); } public CaptivePortalProbeResult(int httpResponseCode, String redirectUrl, String detectUrl) { + this(httpResponseCode, redirectUrl, detectUrl, null); + } + + public CaptivePortalProbeResult(int httpResponseCode, String redirectUrl, String detectUrl, + CaptivePortalProbeSpec probeSpec) { mHttpResponseCode = httpResponseCode; this.redirectUrl = redirectUrl; this.detectUrl = detectUrl; + this.probeSpec = probeSpec; } public boolean isSuccessful() { diff --git a/core/java/android/net/captiveportal/CaptivePortalProbeSpec.java b/core/java/android/net/captiveportal/CaptivePortalProbeSpec.java new file mode 100644 index 000000000000..57a926afec54 --- /dev/null +++ b/core/java/android/net/captiveportal/CaptivePortalProbeSpec.java @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.captiveportal; + +import static android.net.captiveportal.CaptivePortalProbeResult.PORTAL_CODE; +import static android.net.captiveportal.CaptivePortalProbeResult.SUCCESS_CODE; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.text.TextUtils; +import android.util.Log; + +import java.net.MalformedURLException; +import java.net.URL; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +/** @hide */ +public abstract class CaptivePortalProbeSpec { + public static final String HTTP_LOCATION_HEADER_NAME = "Location"; + + private static final String TAG = CaptivePortalProbeSpec.class.getSimpleName(); + private static final String REGEX_SEPARATOR = "@@/@@"; + private static final String SPEC_SEPARATOR = "@@,@@"; + + private final String mEncodedSpec; + private final URL mUrl; + + CaptivePortalProbeSpec(String encodedSpec, URL url) { + mEncodedSpec = encodedSpec; + mUrl = url; + } + + /** + * Parse a {@link CaptivePortalProbeSpec} from a {@link String}. + * + * <p>The valid format is a URL followed by two regular expressions, each separated by "@@/@@". + * @throws MalformedURLException The URL has invalid format for {@link URL#URL(String)}. + * @throws ParseException The string is empty, does not match the above format, or a regular + * expression is invalid for {@link Pattern#compile(String)}. + */ + @NonNull + public static CaptivePortalProbeSpec parseSpec(String spec) throws ParseException, + MalformedURLException { + if (TextUtils.isEmpty(spec)) { + throw new ParseException("Empty probe spec", 0 /* errorOffset */); + } + + String[] splits = TextUtils.split(spec, REGEX_SEPARATOR); + if (splits.length != 3) { + throw new ParseException("Probe spec does not have 3 parts", 0 /* errorOffset */); + } + + final int statusRegexPos = splits[0].length() + REGEX_SEPARATOR.length(); + final int locationRegexPos = statusRegexPos + splits[1].length() + REGEX_SEPARATOR.length(); + final Pattern statusRegex = parsePatternIfNonEmpty(splits[1], statusRegexPos); + final Pattern locationRegex = parsePatternIfNonEmpty(splits[2], locationRegexPos); + + return new RegexMatchProbeSpec(spec, new URL(splits[0]), statusRegex, locationRegex); + } + + @Nullable + private static Pattern parsePatternIfNonEmpty(String pattern, int pos) throws ParseException { + if (TextUtils.isEmpty(pattern)) { + return null; + } + try { + return Pattern.compile(pattern); + } catch (PatternSyntaxException e) { + throw new ParseException( + String.format("Invalid status pattern [%s]: %s", pattern, e), + pos /* errorOffset */); + } + } + + /** + * Parse a {@link CaptivePortalProbeSpec} from a {@link String}, or return a fallback spec + * based on the status code of the provided URL if the spec cannot be parsed. + */ + @Nullable + public static CaptivePortalProbeSpec parseSpecOrNull(@Nullable String spec) { + if (spec != null) { + try { + return parseSpec(spec); + } catch (ParseException | MalformedURLException e) { + Log.e(TAG, "Invalid probe spec: " + spec, e); + // Fall through + } + } + return null; + } + + /** + * Parse a config String to build an array of {@link CaptivePortalProbeSpec}. + * + * <p>Each spec is separated by @@,@@ and follows the format for {@link #parseSpec(String)}. + * <p>This method does not throw but ignores any entry that could not be parsed. + */ + public static CaptivePortalProbeSpec[] parseCaptivePortalProbeSpecs(String settingsVal) { + List<CaptivePortalProbeSpec> specs = new ArrayList<>(); + if (settingsVal != null) { + for (String spec : TextUtils.split(settingsVal, SPEC_SEPARATOR)) { + try { + specs.add(parseSpec(spec)); + } catch (ParseException | MalformedURLException e) { + Log.e(TAG, "Invalid probe spec: " + spec, e); + } + } + } + + if (specs.isEmpty()) { + Log.e(TAG, String.format("could not create any validation spec from %s", settingsVal)); + } + return specs.toArray(new CaptivePortalProbeSpec[specs.size()]); + } + + /** + * Get the probe result from HTTP status and location header. + */ + public abstract CaptivePortalProbeResult getResult(int status, @Nullable String locationHeader); + + public String getEncodedSpec() { + return mEncodedSpec; + } + + public URL getUrl() { + return mUrl; + } + + /** + * Implementation of {@link CaptivePortalProbeSpec} that is based on configurable regular + * expressions for the HTTP status code and location header (if any). Matches indicate that + * the page is not a portal. + * This probe cannot fail: it always returns SUCCESS_CODE or PORTAL_CODE + */ + private static class RegexMatchProbeSpec extends CaptivePortalProbeSpec { + @Nullable + final Pattern mStatusRegex; + @Nullable + final Pattern mLocationHeaderRegex; + + RegexMatchProbeSpec( + String spec, URL url, Pattern statusRegex, Pattern locationHeaderRegex) { + super(spec, url); + mStatusRegex = statusRegex; + mLocationHeaderRegex = locationHeaderRegex; + } + + @Override + public CaptivePortalProbeResult getResult(int status, String locationHeader) { + final boolean statusMatch = safeMatch(String.valueOf(status), mStatusRegex); + final boolean locationMatch = safeMatch(locationHeader, mLocationHeaderRegex); + final int returnCode = statusMatch && locationMatch ? SUCCESS_CODE : PORTAL_CODE; + return new CaptivePortalProbeResult( + returnCode, locationHeader, getUrl().toString(), this); + } + } + + private static boolean safeMatch(@Nullable String value, @Nullable Pattern pattern) { + // No value is a match ("no location header" passes the location rule for non-redirects) + return pattern == null || TextUtils.isEmpty(value) || pattern.matcher(value).matches(); + } +} diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 163403425dc9..fe77dc41232a 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -10287,6 +10287,15 @@ public final class Settings { "captive_portal_other_fallback_urls"; /** + * A list of captive portal detection specifications used in addition to the fallback URLs. + * Each spec has the format url@@/@@statusCodeRegex@@/@@contentRegex. Specs are separated + * by "@@,@@". + * @hide + */ + public static final String CAPTIVE_PORTAL_FALLBACK_PROBE_SPECS = + "captive_portal_fallback_probe_specs"; + + /** * Whether to use HTTPS for network validation. This is enabled by default and the setting * needs to be set to 0 to disable it. This setting is a misnomer because captive portals * don't actually use HTTPS, but it's consistent with the other settings. |
