From 68f5c4fb45eb99ef3a8cf6ebd99568f3abf05824 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Fri, 21 Aug 2015 21:55:55 -0400 Subject: [PATCH] Better URL validation, thanks AOSP --- .../lightning/activity/BrowserActivity.java | 38 +---- .../acr/browser/lightning/utils/UrlUtils.java | 149 ++++++++++++++++++ 2 files changed, 153 insertions(+), 34 deletions(-) create mode 100644 app/src/main/java/acr/browser/lightning/utils/UrlUtils.java diff --git a/app/src/main/java/acr/browser/lightning/activity/BrowserActivity.java b/app/src/main/java/acr/browser/lightning/activity/BrowserActivity.java index 3e5fcf7..982e86c 100644 --- a/app/src/main/java/acr/browser/lightning/activity/BrowserActivity.java +++ b/app/src/main/java/acr/browser/lightning/activity/BrowserActivity.java @@ -126,6 +126,7 @@ import acr.browser.lightning.preference.PreferenceManager; import acr.browser.lightning.receiver.NetworkReceiver; import acr.browser.lightning.utils.ProxyUtils; import acr.browser.lightning.utils.ThemeUtils; +import acr.browser.lightning.utils.UrlUtils; import acr.browser.lightning.utils.Utils; import acr.browser.lightning.utils.WebUtils; import acr.browser.lightning.view.AnimatedProgressBar; @@ -1519,42 +1520,11 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements if (query.isEmpty()) { return; } - String SEARCH = mSearchText; + String searchUrl = mSearchText + UrlUtils.QUERY_PLACE_HOLDER; query = query.trim(); mCurrentView.stopLoading(); - - if (query.startsWith("www.")) { - query = Constants.HTTP + query; - } else if (query.startsWith("ftp.")) { - query = "ftp://" + query; - } - - boolean containsPeriod = query.contains("."); - boolean isIPAddress = (TextUtils.isDigitsOnly(query.replace(".", "")) - && (query.replace(".", "").length() >= 4) && query.contains(".")); - boolean aboutScheme = query.startsWith("about:"); - boolean validURL = (query.startsWith("ftp://") || query.startsWith(Constants.HTTP) - || query.startsWith(Constants.FILE) || query.startsWith(Constants.HTTPS)) - || isIPAddress; - if (isIPAddress - && (!query.startsWith(Constants.HTTP) || !query.startsWith(Constants.HTTPS))) { - query = Constants.HTTP + query; - } - - validURL |= Patterns.WEB_URL.matcher(query).matches(); - boolean isSearch = !validURL; - - if (isSearch) { - try { - query = URLEncoder.encode(query, "UTF-8"); - } catch (UnsupportedEncodingException e) { - e.printStackTrace(); - } - mCurrentView.loadUrl(SEARCH + query); - } else if (!validURL) { - mCurrentView.loadUrl(Constants.HTTP + query); - } else { - mCurrentView.loadUrl(query); + if (mCurrentView != null) { + mCurrentView.loadUrl(UrlUtils.smartUrlFilter(query, true, searchUrl)); } } diff --git a/app/src/main/java/acr/browser/lightning/utils/UrlUtils.java b/app/src/main/java/acr/browser/lightning/utils/UrlUtils.java new file mode 100644 index 0000000..824d8a1 --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/utils/UrlUtils.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2010 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 acr.browser.lightning.utils; + +import android.net.Uri; +import android.util.Patterns; +import android.webkit.URLUtil; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Utility methods for Url manipulation + */ +public class UrlUtils { + static final Pattern ACCEPTED_URI_SCHEMA = Pattern.compile( + "(?i)" + // switch on case insensitive matching + "(" + // begin group for schema + "(?:http|https|file):\\/\\/" + + "|(?:inline|data|about|javascript):" + + "|(?:.*:.*@)" + + ")" + + "(.*)"); + // Google search + public final static String QUERY_PLACE_HOLDER = "%s"; + // Regular expression to strip http:// and optionally + // the trailing slash + private static final Pattern STRIP_URL_PATTERN = + Pattern.compile("^http://(.*?)/?$"); + + private UrlUtils() { /* cannot be instantiated */ } + + /** + * Strips the provided url of preceding "http://" and any trailing "/". Does not + * strip "https://". If the provided string cannot be stripped, the original string + * is returned. + *

+ * TODO: Put this in TextUtils to be used by other packages doing something similar. + * + * @param url a url to strip, like "http://www.google.com/" + * @return a stripped url like "www.google.com", or the original string if it could + * not be stripped + */ + public static String stripUrl(String url) { + if (url == null) return null; + Matcher m = STRIP_URL_PATTERN.matcher(url); + if (m.matches()) { + return m.group(1); + } else { + return url; + } + } + + /** + * Attempts to determine whether user input is a URL or search + * terms. Anything with a space is passed to search if canBeSearch is true. + *

+ * Converts to lowercase any mistakenly uppercased schema (i.e., + * "Http://" converts to "http://" + * + * @param canBeSearch If true, will return a search url if it isn't a valid + * URL. If false, invalid URLs will return null + * @return Original or modified URL + */ + public static String smartUrlFilter(String url, boolean canBeSearch, String searchUrl) { + String inUrl = url.trim(); + boolean hasSpace = inUrl.indexOf(' ') != -1; + Matcher matcher = ACCEPTED_URI_SCHEMA.matcher(inUrl); + if (matcher.matches()) { + // force scheme to lowercase + String scheme = matcher.group(1); + String lcScheme = scheme.toLowerCase(); + if (!lcScheme.equals(scheme)) { + inUrl = lcScheme + matcher.group(2); + } + if (hasSpace && Patterns.WEB_URL.matcher(inUrl).matches()) { + inUrl = inUrl.replace(" ", "%20"); + } + return inUrl; + } + if (!hasSpace) { + if (Patterns.WEB_URL.matcher(inUrl).matches()) { + return URLUtil.guessUrl(inUrl); + } + } + if (canBeSearch) { + return URLUtil.composeSearchUrl(inUrl, + searchUrl, QUERY_PLACE_HOLDER); + } + return null; + } + + /* package */ + static String fixUrl(String inUrl) { + // FIXME: Converting the url to lower case + // duplicates functionality in smartUrlFilter(). + // However, changing all current callers of fixUrl to + // call smartUrlFilter in addition may have unwanted + // consequences, and is deferred for now. + int colon = inUrl.indexOf(':'); + boolean allLower = true; + for (int index = 0; index < colon; index++) { + char ch = inUrl.charAt(index); + if (!Character.isLetter(ch)) { + break; + } + allLower &= Character.isLowerCase(ch); + if (index == colon - 1 && !allLower) { + inUrl = inUrl.substring(0, colon).toLowerCase() + + inUrl.substring(colon); + } + } + if (inUrl.startsWith("http://") || inUrl.startsWith("https://")) + return inUrl; + if (inUrl.startsWith("http:") || + inUrl.startsWith("https:")) { + if (inUrl.startsWith("http:/") || inUrl.startsWith("https:/")) { + inUrl = inUrl.replaceFirst("/", "//"); + } else inUrl = inUrl.replaceFirst(":", "://"); + } + return inUrl; + } + + // Returns the filtered URL. Cannot return null, but can return an empty string + /* package */ + static String filteredUrl(String inUrl) { + if (inUrl == null) { + return ""; + } + if (inUrl.startsWith("content:") + || inUrl.startsWith("browser:")) { + return ""; + } + return inUrl; + } +} \ No newline at end of file