diff options
Diffstat (limited to 'chromium/url')
31 files changed, 1842 insertions, 252 deletions
diff --git a/chromium/url/BUILD.gn b/chromium/url/BUILD.gn index 71cc92c41cf..197d31ab0fb 100644 --- a/chromium/url/BUILD.gn +++ b/chromium/url/BUILD.gn @@ -8,6 +8,7 @@ import("//testing/test.gni") import("features.gni") if (is_android) { + import("//build/config/android/config.gni") import("//build/config/android/rules.gni") } @@ -97,12 +98,25 @@ component("url") { } if (is_android) { - static_library("origin_android") { + source_set("gurl_android") { sources = [ - "android/origin_android.cc", + "android/gurl_android.cc", + "android/gurl_android.h", + "android/parsed_android.cc", + "android/parsed_android.h", ] deps = [ + ":gurl_jni_headers", + ":url", + "//base:base", + ] + } + + static_library("origin_android") { + sources = [ "android/origin_android.cc" ] + + deps = [ ":url", ":url_jni_headers", "//base", @@ -111,14 +125,25 @@ if (is_android) { } android_library("url_java") { - java_files = [ "android/java/src/org/chromium/url/IDNStringUtil.java" ] + sources = [ "android/java/src/org/chromium/url/IDNStringUtil.java" ] + deps = [ "//base:base_java" ] + } + + android_library("gurl_java") { + sources = [ + "android/java/src/org/chromium/url/GURL.java", + "android/java/src/org/chromium/url/Parsed.java", + "android/java/src/org/chromium/url/URI.java", + ] deps = [ "//base:base_java", + "//base:jni_java", ] + annotation_processor_deps = [ "//base/android/jni_generator:jni_processor" ] } android_library("origin_java") { - java_files = [ "android/java/src/org/chromium/url/Origin.java" ] + sources = [ "android/java/src/org/chromium/url/Origin.java" ] deps = [ "//base:base_java", "//url/mojom:url_mojom_origin_java", @@ -131,6 +156,13 @@ if (is_android) { "android/java/src/org/chromium/url/Origin.java", ] } + + generate_jni("gurl_jni_headers") { + sources = [ + "android/java/src/org/chromium/url/GURL.java", + "android/java/src/org/chromium/url/Parsed.java", + ] + } } test("url_unittests") { @@ -187,6 +219,15 @@ test("url_unittests") { "//url/mojom:test_url_mojom_gurl", ] } + if (is_android) { + sources += [ "android/gurl_android_unittest.cc" ] + deps += [ + ":gurl_android", + ":gurl_java", + ":gurl_javatests", + ":native_j_unittests_jni_headers", + ] + } } test("url_perftests") { @@ -204,9 +245,7 @@ test("url_perftests") { } fuzzer_test("gurl_fuzzer") { - sources = [ - "gurl_fuzzer.cc", - ] + sources = [ "gurl_fuzzer.cc" ] deps = [ ":url", "//base", @@ -214,3 +253,33 @@ fuzzer_test("gurl_fuzzer") { ] dict = "gurl_fuzzer.dict" } + +if (is_android) { + android_library("gurl_javatests") { + testonly = true + sources = [ + "android/native_java_unittests/src/org/chromium/url/GURLJavaTest.java", + ] + deps = [ + ":gurl_java", + ":gurl_jni_headers", + "//base:base_java", + "//base:base_java_test_support", + "//base:jni_java", + "//third_party/android_support_test_runner:rules_java", + "//third_party/android_support_test_runner:runner_java", + "//third_party/junit", + "//third_party/mockito:mockito_java", + ] + annotation_processor_deps = [ "//base/android/jni_generator:jni_processor" ] + } + + # See https://bugs.chromium.org/p/chromium/issues/detail?id=908819 for why we + # can't put 'java' in the name here. + generate_jni("native_j_unittests_jni_headers") { + testonly = true + sources = [ + "android/native_java_unittests/src/org/chromium/url/GURLJavaTest.java", + ] + } +} diff --git a/chromium/url/android/gurl_android.cc b/chromium/url/android/gurl_android.cc new file mode 100644 index 00000000000..00cfe10207c --- /dev/null +++ b/chromium/url/android/gurl_android.cc @@ -0,0 +1,102 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "url/android/gurl_android.h" + +#include <jni.h> + +#include <cstdint> +#include <string> +#include <vector> + +#include "base/android/jni_android.h" +#include "base/android/jni_string.h" +#include "base/bind.h" +#include "base/callback.h" +#include "base/memory/ptr_util.h" +#include "url/android/parsed_android.h" +#include "url/gurl_jni_headers/GURL_jni.h" +#include "url/third_party/mozilla/url_parse.h" + +using base::android::AttachCurrentThread; +using base::android::JavaParamRef; +using base::android::JavaRef; +using base::android::ScopedJavaLocalRef; + +namespace url { + +namespace { + +static GURL FromJString(JNIEnv* env, const JavaRef<jstring>& uri) { + if (!uri) + return GURL(); + return GURL(base::android::ConvertJavaStringToUTF16(env, uri)); +} + +static std::unique_ptr<GURL> FromJavaGURL(JNIEnv* env, + const JavaRef<jstring>& j_spec, + bool is_valid, + jlong parsed_ptr) { + Parsed* parsed = reinterpret_cast<Parsed*>(parsed_ptr); + const std::string& spec = ConvertJavaStringToUTF8(env, j_spec); + std::unique_ptr<GURL> gurl = + std::make_unique<GURL>(spec.data(), parsed->Length(), *parsed, is_valid); + delete parsed; + return gurl; +} + +static void InitFromGURL(JNIEnv* env, + const GURL& gurl, + const JavaRef<jobject>& target) { + Java_GURL_init( + env, target, + base::android::ConvertUTF8ToJavaString(env, gurl.possibly_invalid_spec()), + gurl.is_valid(), + ParsedAndroid::InitFromParsed(env, + gurl.parsed_for_possibly_invalid_spec())); +} + +} // namespace + +// static +std::unique_ptr<GURL> GURLAndroid::ToNativeGURL( + JNIEnv* env, + const base::android::JavaRef<jobject>& j_gurl) { + return base::WrapUnique<GURL>( + reinterpret_cast<GURL*>(Java_GURL_toNativeGURL(env, j_gurl))); +} + +// static +ScopedJavaLocalRef<jobject> GURLAndroid::FromNativeGURL(JNIEnv* env, + const GURL& gurl) { + ScopedJavaLocalRef<jobject> j_gurl = Java_GURL_Constructor(env); + InitFromGURL(env, gurl, j_gurl); + return j_gurl; +} + +static void JNI_GURL_GetOrigin(JNIEnv* env, + const JavaParamRef<jstring>& j_spec, + jboolean is_valid, + jlong parsed_ptr, + const JavaParamRef<jobject>& target) { + std::unique_ptr<GURL> gurl = FromJavaGURL(env, j_spec, is_valid, parsed_ptr); + InitFromGURL(env, gurl->GetOrigin(), target); +} + +static void JNI_GURL_Init(JNIEnv* env, + const base::android::JavaParamRef<jstring>& uri, + const base::android::JavaParamRef<jobject>& target) { + const GURL& gurl = FromJString(env, uri); + InitFromGURL(env, gurl, target); +} + +static jlong JNI_GURL_CreateNative(JNIEnv* env, + const JavaParamRef<jstring>& j_spec, + jboolean is_valid, + jlong parsed_ptr) { + return reinterpret_cast<intptr_t>( + FromJavaGURL(env, j_spec, is_valid, parsed_ptr).release()); +} + +} // namespace url diff --git a/chromium/url/android/gurl_android.h b/chromium/url/android/gurl_android.h new file mode 100644 index 00000000000..733ba0644e6 --- /dev/null +++ b/chromium/url/android/gurl_android.h @@ -0,0 +1,27 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef URL_ANDROID_GURL_ANDROID_H_ +#define URL_ANDROID_GURL_ANDROID_H_ + +#include <memory> + +#include "base/android/scoped_java_ref.h" +#include "url/gurl.h" + +namespace url { + +class GURLAndroid { + public: + static std::unique_ptr<GURL> ToNativeGURL( + JNIEnv* env, + const base::android::JavaRef<jobject>& j_gurl); + static base::android::ScopedJavaLocalRef<jobject> FromNativeGURL( + JNIEnv* env, + const GURL& gurl); +}; + +} // namespace url + +#endif // URL_ANDROID_GURL_ANDROID_H_ diff --git a/chromium/url/android/gurl_android_unittest.cc b/chromium/url/android/gurl_android_unittest.cc new file mode 100644 index 00000000000..fe2b7088e4e --- /dev/null +++ b/chromium/url/android/gurl_android_unittest.cc @@ -0,0 +1,78 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <stddef.h> + +#include "base/android/jni_android.h" +#include "base/android/jni_string.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "url/android/gurl_android.h" +#include "url/gurl.h" +#include "url/native_j_unittests_jni_headers/GURLJavaTest_jni.h" + +using base::android::AttachCurrentThread; + +namespace url { + +class GURLAndroidTest : public ::testing::Test { + public: + GURLAndroidTest() + : j_test_(Java_GURLJavaTest_Constructor(AttachCurrentThread())) {} + + const base::android::ScopedJavaGlobalRef<jobject>& j_test() { + return j_test_; + } + + private: + base::android::ScopedJavaGlobalRef<jobject> j_test_; +}; + +TEST_F(GURLAndroidTest, TestGURLEquivalence) { + const char* cases[] = { + // Common Standard URLs. + "https://www.google.com", + "https://www.google.com/", + "https://www.google.com/maps.htm", + "https://www.google.com/maps/", + "https://www.google.com/index.html", + "https://www.google.com/index.html?q=maps", + "https://www.google.com/index.html#maps/", + "https://foo:bar@www.google.com/maps.htm", + "https://www.google.com/maps/au/index.html", + "https://www.google.com/maps/au/north", + "https://www.google.com/maps/au/north/", + "https://www.google.com/maps/au/index.html?q=maps#fragment/", + "http://www.google.com:8000/maps/au/index.html?q=maps#fragment/", + "https://www.google.com/maps/au/north/?q=maps#fragment", + "https://www.google.com/maps/au/north?q=maps#fragment", + // Less common standard URLs. + "filesystem:http://www.google.com/temporary/bar.html?baz=22", + "file:///temporary/bar.html?baz=22", + "ftp://foo/test/index.html", + "gopher://foo/test/index.html", + "ws://foo/test/index.html", + // Non-standard, + "chrome://foo/bar.html", + "httpa://foo/test/index.html", + "blob:https://foo.bar/test/index.html", + "about:blank", + "data:foobar", + "scheme:opaque_data", + // Invalid URLs. + "foobar", + }; + JNIEnv* env = AttachCurrentThread(); + for (const char* uri : cases) { + GURL gurl(uri); + base::android::ScopedJavaLocalRef<jobject> j_gurl = + Java_GURLJavaTest_createGURL( + env, j_test(), base::android::ConvertUTF8ToJavaString(env, uri)); + std::unique_ptr<GURL> gurl2 = GURLAndroid::ToNativeGURL(env, j_gurl); + EXPECT_EQ(gurl, *gurl2); + } +} + +JAVA_TESTS(GURLAndroidTest, j_test()) + +} // namespace url diff --git a/chromium/url/android/java/src/org/chromium/url/GURL.java b/chromium/url/android/java/src/org/chromium/url/GURL.java new file mode 100644 index 00000000000..5042bbddc92 --- /dev/null +++ b/chromium/url/android/java/src/org/chromium/url/GURL.java @@ -0,0 +1,334 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.url; + +import android.os.SystemClock; +import android.text.TextUtils; + +import androidx.annotation.Nullable; + +import org.chromium.base.Log; +import org.chromium.base.ThreadUtils; +import org.chromium.base.annotations.CalledByNative; +import org.chromium.base.annotations.JNINamespace; +import org.chromium.base.annotations.MainDex; +import org.chromium.base.annotations.NativeMethods; +import org.chromium.base.library_loader.LibraryLoader; +import org.chromium.base.metrics.RecordHistogram; +import org.chromium.base.task.PostTask; +import org.chromium.base.task.TaskTraits; + +import java.util.Random; + +/** + * A Java wrapper for GURL, Chromium's URL parsing library. + * + * This class is safe to use during startup, but will block on the native library being sufficiently + * loaded to use native GURL (and will not wait for content initialization). In practice it's very + * unlikely that this will actually block startup unless used extremely early, in which case you + * should probably seek an alternative solution to using GURL. + * + * The design of this class avoids destruction/finalization by caching all values necessary to + * reconstruct a GURL in Java, allowing it to be much faster in the common case and easier to use. + */ +@JNINamespace("url") +@MainDex +public class GURL { + private static final String TAG = "GURL"; + /* package */ static final int SERIALIZER_VERSION = 1; + /* package */ static final char SERIALIZER_DELIMITER = '\0'; + + @FunctionalInterface + public interface ReportDebugThrowableCallback { + void run(Throwable throwable); + } + + // Right now this is only collecting reports on Canary which has a relatively small population. + private static final int DEBUG_REPORT_PERCENTAGE = 10; + private static ReportDebugThrowableCallback sReportCallback; + + // TODO(https://crbug.com/1039841): Right now we return a new String with each request for a + // GURL component other than the spec itself. Should we cache return Strings (as + // WeakReference?) so that callers can share String memory? + private String mSpec; + private boolean mIsValid; + private Parsed mParsed; + + private static class Holder { private static GURL sEmptyGURL = new GURL(""); } + + public static GURL emptyGURL() { + return Holder.sEmptyGURL; + } + + /** + * Create a new GURL. + * + * @param uri The string URI representation to parse into a GURL. + */ + public GURL(String uri) { + // Avoid a jni hop (and initializing the native library) for empty GURLs. + if (TextUtils.isEmpty(uri)) { + mSpec = ""; + mParsed = Parsed.createEmpty(); + return; + } + ensureNativeInitializedForGURL(); + GURLJni.get().init(uri, this); + } + + @CalledByNative + protected GURL() {} + + /** + * Enables debug stack trace gathering for GURL. + * + * TODO(https://crbug.com/783819): Remove this when the the fraction of users hitting this + * drops. + */ + public static void setReportDebugThrowableCallback(ReportDebugThrowableCallback callback) { + sReportCallback = callback; + } + + /** + * Ensures that the native library is sufficiently loaded for GURL usage. + * + * This function is public so that GURL-related usage like the UrlFormatter also counts towards + * the "Startup.Android.GURLEnsureMainDexInitialized" histogram. + */ + public static void ensureNativeInitializedForGURL() { + if (LibraryLoader.getInstance().isInitialized()) return; + long time = SystemClock.elapsedRealtime(); + LibraryLoader.getInstance().ensureMainDexInitialized(); + // Record metrics only for the UI thread where the delay in loading the library is relevant. + if (ThreadUtils.runningOnUiThread()) { + RecordHistogram.recordTimesHistogram("Startup.Android.GURLEnsureMainDexInitialized", + SystemClock.elapsedRealtime() - time); + if (sReportCallback != null && new Random().nextInt(100) < DEBUG_REPORT_PERCENTAGE) { + final Throwable throwable = new Throwable("This is not a crash, please ignore."); + // This isn't an assert, because by design this is possible, but we would prefer + // this path does not get hit more than necessary and getting stack traces from the + // wild will help find issues. + PostTask.postTask(TaskTraits.BEST_EFFORT_MAY_BLOCK, + () -> { sReportCallback.run(throwable); }); + } + } + } + + @CalledByNative + private void init(String spec, boolean isValid, Parsed parsed) { + mSpec = spec; + // Ensure that the spec only contains US-ASCII or the parsed indices will be wrong. + assert mSpec.matches("\\A\\p{ASCII}*\\z"); + mIsValid = isValid; + mParsed = parsed; + } + + @CalledByNative + private long toNativeGURL() { + return GURLJni.get().createNative(mSpec, mIsValid, mParsed.toNativeParsed()); + } + + /** + * See native GURL::is_valid(). + */ + public boolean isValid() { + return mIsValid; + } + + /** + * See native GURL::spec(). + */ + public String getSpec() { + if (isValid() || mSpec.isEmpty()) return mSpec; + assert false : "Trying to get the spec of an invalid URL!"; + return ""; + } + + /** + * @return Either a valid Spec (see {@link #getSpec}), or an empty string. + */ + public String getValidSpecOrEmpty() { + if (isValid()) return mSpec; + return ""; + } + + /** + * See native GURL::possibly_invalid_spec(). + */ + public String getPossiblyInvalidSpec() { + return mSpec; + } + + private String getComponent(int begin, int length) { + if (length <= 0) return ""; + return mSpec.substring(begin, begin + length); + } + + /** + * See native GURL::scheme(). + */ + public String getScheme() { + return getComponent(mParsed.mSchemeBegin, mParsed.mSchemeLength); + } + + /** + * See native GURL::username(). + */ + public String getUsername() { + return getComponent(mParsed.mUsernameBegin, mParsed.mUsernameLength); + } + + /** + * See native GURL::password(). + */ + public String getPassword() { + return getComponent(mParsed.mPasswordBegin, mParsed.mPasswordLength); + } + + /** + * See native GURL::host(). + */ + public String getHost() { + return getComponent(mParsed.mHostBegin, mParsed.mHostLength); + } + + /** + * See native GURL::port(). + * + * Note: Do not convert this to an integer yourself. See native GURL::IntPort(). + */ + public String getPort() { + return getComponent(mParsed.mPortBegin, mParsed.mPortLength); + } + + /** + * See native GURL::path(). + */ + public String getPath() { + return getComponent(mParsed.mPathBegin, mParsed.mPathLength); + } + + /** + * See native GURL::query(). + */ + public String getQuery() { + return getComponent(mParsed.mQueryBegin, mParsed.mQueryLength); + } + + /** + * See native GURL::ref(). + */ + public String getRef() { + return getComponent(mParsed.mRefBegin, mParsed.mRefLength); + } + + /** + * @return Whether the GURL is the empty String. + */ + public boolean isEmpty() { + return mSpec.isEmpty(); + } + + /** + * See native GURL::GetOrigin(). + */ + public GURL getOrigin() { + GURL target = new GURL(); + getOriginInternal(target); + return target; + } + + protected void getOriginInternal(GURL target) { + GURLJni.get().getOrigin(mSpec, mIsValid, mParsed.toNativeParsed(), target); + } + + @Override + public final int hashCode() { + return mSpec.hashCode(); + } + + @Override + public final boolean equals(Object other) { + if (other == this) return true; + if (!(other instanceof GURL)) return false; + return mSpec.equals(((GURL) other).mSpec); + } + + /** + * Serialize a GURL to a String, to be used with {@link GURL#deserialize(String)}. + * + * Note that a serialized GURL should only be used internally to Chrome, and should *never* be + * used if coming from an untrusted source. + * + * @return A serialzed GURL. + */ + public final String serialize() { + StringBuilder builder = new StringBuilder(); + builder.append(SERIALIZER_VERSION).append(SERIALIZER_DELIMITER); + builder.append(mIsValid).append(SERIALIZER_DELIMITER); + builder.append(mParsed.serialize()).append(SERIALIZER_DELIMITER); + builder.append(mSpec); + String serialization = builder.toString(); + return Integer.toString(serialization.length()) + SERIALIZER_DELIMITER + serialization; + } + + /** + * Deserialize a GURL serialized with {@link GURL#serialize()}. + * + * This function should *never* be used on a String coming from an untrusted source. + * + * @return The deserialized GURL (or null if the input is empty). + */ + public static GURL deserialize(@Nullable String gurl) { + try { + if (TextUtils.isEmpty(gurl)) return emptyGURL(); + String[] tokens = gurl.split(Character.toString(SERIALIZER_DELIMITER)); + + // First token MUST always be the length of the serialized data. + String length = tokens[0]; + if (gurl.length() != Integer.parseInt(length) + length.length() + 1) { + throw new IllegalArgumentException("Serialized GURL had the wrong length."); + } + + // Last token MUST always be the original spec - just re-parse the GURL on version + // changes. + String spec = tokens[tokens.length - 1]; + // Special case for empty spec - it won't get its own token. + if (gurl.endsWith(Character.toString(SERIALIZER_DELIMITER))) spec = ""; + + // Second token MUST always be the version number. + int version = Integer.parseInt(tokens[1]); + if (version != SERIALIZER_VERSION) return new GURL(spec); + + boolean isValid = Boolean.parseBoolean(tokens[2]); + Parsed parsed = Parsed.deserialize(tokens, 3); + GURL result = new GURL(); + result.init(spec, isValid, parsed); + return result; + } catch (Exception e) { + // This is unexpected, maybe the storage got corrupted somehow? + Log.w(TAG, "Exception while deserializing a GURL: " + gurl, e); + return emptyGURL(); + } + } + + @NativeMethods + interface Natives { + /** + * Initializes the provided |target| by parsing the provided |uri|. + */ + void init(String uri, GURL target); + + /** + * Reconstructs the native GURL for this Java GURL and initializes |target| with its Origin. + */ + void getOrigin(String spec, boolean isValid, long nativeParsed, GURL target); + + /** + * Reconstructs the native GURL for this Java GURL, returning its native pointer. + */ + long createNative(String spec, boolean isValid, long nativeParsed); + } +} diff --git a/chromium/url/android/java/src/org/chromium/url/Parsed.java b/chromium/url/android/java/src/org/chromium/url/Parsed.java new file mode 100644 index 00000000000..f78f8bdf6ef --- /dev/null +++ b/chromium/url/android/java/src/org/chromium/url/Parsed.java @@ -0,0 +1,141 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.url; + +import org.chromium.base.annotations.CalledByNative; +import org.chromium.base.annotations.JNINamespace; +import org.chromium.base.annotations.MainDex; +import org.chromium.base.annotations.NativeMethods; + +/** + * A java wrapper for Parsed, GURL's internal parsed URI representation. + */ +@MainDex +@JNINamespace("url") +/* package */ class Parsed { + /* package */ final int mSchemeBegin; + /* package */ final int mSchemeLength; + /* package */ final int mUsernameBegin; + /* package */ final int mUsernameLength; + /* package */ final int mPasswordBegin; + /* package */ final int mPasswordLength; + /* package */ final int mHostBegin; + /* package */ final int mHostLength; + /* package */ final int mPortBegin; + /* package */ final int mPortLength; + /* package */ final int mPathBegin; + /* package */ final int mPathLength; + /* package */ final int mQueryBegin; + /* package */ final int mQueryLength; + /* package */ final int mRefBegin; + /* package */ final int mRefLength; + private final Parsed mInnerUrl; + private final boolean mPotentiallyDanglingMarkup; + + /* packaged */ static Parsed createEmpty() { + return new Parsed(0, -1, 0, -1, 0, -1, 0, -1, 0, -1, 0, -1, 0, -1, 0, -1, false, null); + } + + @CalledByNative + private Parsed(int schemeBegin, int schemeLength, int usernameBegin, int usernameLength, + int passwordBegin, int passwordLength, int hostBegin, int hostLength, int portBegin, + int portLength, int pathBegin, int pathLength, int queryBegin, int queryLength, + int refBegin, int refLength, boolean potentiallyDanglingMarkup, Parsed innerUrl) { + mSchemeBegin = schemeBegin; + mSchemeLength = schemeLength; + mUsernameBegin = usernameBegin; + mUsernameLength = usernameLength; + mPasswordBegin = passwordBegin; + mPasswordLength = passwordLength; + mHostBegin = hostBegin; + mHostLength = hostLength; + mPortBegin = portBegin; + mPortLength = portLength; + mPathBegin = pathBegin; + mPathLength = pathLength; + mQueryBegin = queryBegin; + mQueryLength = queryLength; + mRefBegin = refBegin; + mRefLength = refLength; + mPotentiallyDanglingMarkup = potentiallyDanglingMarkup; + mInnerUrl = innerUrl; + } + + /* package */ long toNativeParsed() { + long inner = 0; + if (mInnerUrl != null) { + inner = mInnerUrl.toNativeParsed(); + } + return ParsedJni.get().createNative(mSchemeBegin, mSchemeLength, mUsernameBegin, + mUsernameLength, mPasswordBegin, mPasswordLength, mHostBegin, mHostLength, + mPortBegin, mPortLength, mPathBegin, mPathLength, mQueryBegin, mQueryLength, + mRefBegin, mRefLength, mPotentiallyDanglingMarkup, inner); + } + + /* package */ String serialize() { + StringBuilder builder = new StringBuilder(); + builder.append(mSchemeBegin).append(GURL.SERIALIZER_DELIMITER); + builder.append(mSchemeLength).append(GURL.SERIALIZER_DELIMITER); + builder.append(mUsernameBegin).append(GURL.SERIALIZER_DELIMITER); + builder.append(mUsernameLength).append(GURL.SERIALIZER_DELIMITER); + builder.append(mPasswordBegin).append(GURL.SERIALIZER_DELIMITER); + builder.append(mPasswordLength).append(GURL.SERIALIZER_DELIMITER); + builder.append(mHostBegin).append(GURL.SERIALIZER_DELIMITER); + builder.append(mHostLength).append(GURL.SERIALIZER_DELIMITER); + builder.append(mPortBegin).append(GURL.SERIALIZER_DELIMITER); + builder.append(mPortLength).append(GURL.SERIALIZER_DELIMITER); + builder.append(mPathBegin).append(GURL.SERIALIZER_DELIMITER); + builder.append(mPathLength).append(GURL.SERIALIZER_DELIMITER); + builder.append(mQueryBegin).append(GURL.SERIALIZER_DELIMITER); + builder.append(mQueryLength).append(GURL.SERIALIZER_DELIMITER); + builder.append(mRefBegin).append(GURL.SERIALIZER_DELIMITER); + builder.append(mRefLength).append(GURL.SERIALIZER_DELIMITER); + builder.append(mPotentiallyDanglingMarkup).append(GURL.SERIALIZER_DELIMITER); + builder.append(mInnerUrl != null); + if (mInnerUrl != null) { + builder.append(GURL.SERIALIZER_DELIMITER).append(mInnerUrl.serialize()); + } + return builder.toString(); + } + + /* package */ static Parsed deserialize(String[] tokens, int startIndex) { + int schemeBegin = Integer.parseInt(tokens[startIndex++]); + int schemeLength = Integer.parseInt(tokens[startIndex++]); + int usernameBegin = Integer.parseInt(tokens[startIndex++]); + int usernameLength = Integer.parseInt(tokens[startIndex++]); + int passwordBegin = Integer.parseInt(tokens[startIndex++]); + int passwordLength = Integer.parseInt(tokens[startIndex++]); + int hostBegin = Integer.parseInt(tokens[startIndex++]); + int hostLength = Integer.parseInt(tokens[startIndex++]); + int portBegin = Integer.parseInt(tokens[startIndex++]); + int portLength = Integer.parseInt(tokens[startIndex++]); + int pathBegin = Integer.parseInt(tokens[startIndex++]); + int pathLength = Integer.parseInt(tokens[startIndex++]); + int queryBegin = Integer.parseInt(tokens[startIndex++]); + int queryLength = Integer.parseInt(tokens[startIndex++]); + int refBegin = Integer.parseInt(tokens[startIndex++]); + int refLength = Integer.parseInt(tokens[startIndex++]); + boolean potentiallyDanglingMarkup = Boolean.parseBoolean(tokens[startIndex++]); + Parsed innerParsed = null; + if (Boolean.parseBoolean(tokens[startIndex++])) { + innerParsed = Parsed.deserialize(tokens, startIndex); + } + return new Parsed(schemeBegin, schemeLength, usernameBegin, usernameLength, passwordBegin, + passwordLength, hostBegin, hostLength, portBegin, portLength, pathBegin, pathLength, + queryBegin, queryLength, refBegin, refLength, potentiallyDanglingMarkup, + innerParsed); + } + + @NativeMethods + interface Natives { + /** + * Create and return the pointer to a native Parsed. + */ + long createNative(int schemeBegin, int schemeLength, int usernameBegin, int usernameLength, + int passwordBegin, int passwordLength, int hostBegin, int hostLength, int portBegin, + int portLength, int pathBegin, int pathLength, int queryBegin, int queryLength, + int refBegin, int refLength, boolean potentiallyDanglingMarkup, long innerUrl); + } +} diff --git a/chromium/url/android/java/src/org/chromium/url/URI.java b/chromium/url/android/java/src/org/chromium/url/URI.java new file mode 100644 index 00000000000..546be56223e --- /dev/null +++ b/chromium/url/android/java/src/org/chromium/url/URI.java @@ -0,0 +1,61 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.url; + +import java.net.URISyntaxException; + +/** + * An API shim around GURL that mostly matches the java.net.URI API. + * + * @deprecated Please use GURL directly in new code. + */ +@Deprecated +public class URI extends GURL { + /** + * Create a new GURL with a java.net.URI API shim. + */ + public URI(String uri) throws URISyntaxException { + super(uri); + if (!isValid()) { + throw new URISyntaxException(uri, "Uri could not be parsed as a valid GURL"); + } + } + + private URI() {} + + /** + * This function is a convenience wrapper around {@link URI#URI(String)}, that wraps the thrown + * thrown URISyntaxException in an IllegalArgumentException and throws that instead. + */ + public static URI create(String str) { + try { + return new URI(str); + } catch (URISyntaxException e) { + throw new IllegalArgumentException(e); + } + } + + @Override + public URI getOrigin() { + URI target = new URI(); + getOriginInternal(target); + return target; + } + + /** See {@link GURL#getRef()} */ + public String getFragment() { + return getRef(); + } + + /** See {@link java.net.URI#isAbsolute()} */ + public boolean isAbsolute() { + return !getScheme().isEmpty(); + } + + @Override + public String toString() { + return getPossiblyInvalidSpec(); + } +} diff --git a/chromium/url/android/native_java_unittests/src/org/chromium/url/GURLJavaTest.java b/chromium/url/android/native_java_unittests/src/org/chromium/url/GURLJavaTest.java new file mode 100644 index 00000000000..0517ddf7edc --- /dev/null +++ b/chromium/url/android/native_java_unittests/src/org/chromium/url/GURLJavaTest.java @@ -0,0 +1,253 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.url; + +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.doThrow; + +import org.junit.Assert; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import org.chromium.base.annotations.CalledByNative; +import org.chromium.base.annotations.CalledByNativeJavaTest; + +import java.net.URISyntaxException; + +/** + * Tests for {@link GURL}. GURL relies heavily on the native implementation, and the lion's share of + * the logic is tested there. This test is primarily to make sure everything is plumbed through + * correctly. + */ +public class GURLJavaTest { + @Mock + GURL.Natives mGURLMocks; + + @CalledByNative + private GURLJavaTest() { + MockitoAnnotations.initMocks(this); + } + + @CalledByNative + public GURL createGURL(String uri) { + return new GURL(uri); + } + + private void deepAssertEquals(GURL expected, GURL actual) { + Assert.assertEquals(expected, actual); + Assert.assertEquals(expected.getScheme(), actual.getScheme()); + Assert.assertEquals(expected.getUsername(), actual.getUsername()); + Assert.assertEquals(expected.getPassword(), actual.getPassword()); + Assert.assertEquals(expected.getHost(), actual.getHost()); + Assert.assertEquals(expected.getPort(), actual.getPort()); + Assert.assertEquals(expected.getPath(), actual.getPath()); + Assert.assertEquals(expected.getQuery(), actual.getQuery()); + Assert.assertEquals(expected.getRef(), actual.getRef()); + } + + private String prependLengthToSerialization(String serialization) { + return Integer.toString(serialization.length()) + GURL.SERIALIZER_DELIMITER + serialization; + } + + // Equivalent of GURLTest.Components + @CalledByNativeJavaTest + @SuppressWarnings(value = "AuthLeak") + public void testComponents() { + GURL empty = new GURL(""); + Assert.assertTrue(empty.isEmpty()); + Assert.assertFalse(empty.isValid()); + + GURL url = new GURL("http://user:pass@google.com:99/foo;bar?q=a#ref"); + Assert.assertFalse(url.isEmpty()); + Assert.assertTrue(url.isValid()); + Assert.assertTrue(url.getScheme().equals("http")); + + Assert.assertEquals("http://user:pass@google.com:99/foo;bar?q=a#ref", url.getSpec()); + + Assert.assertEquals("http", url.getScheme()); + Assert.assertEquals("user", url.getUsername()); + Assert.assertEquals("pass", url.getPassword()); + Assert.assertEquals("google.com", url.getHost()); + Assert.assertEquals("99", url.getPort()); + Assert.assertEquals("/foo;bar", url.getPath()); + Assert.assertEquals("q=a", url.getQuery()); + Assert.assertEquals("ref", url.getRef()); + + // Test parsing userinfo with special characters. + GURL urlSpecialPass = new GURL("http://user:%40!$&'()*+,;=:@google.com:12345"); + Assert.assertTrue(urlSpecialPass.isValid()); + // GURL canonicalizes some delimiters. + Assert.assertEquals("%40!$&%27()*+,%3B%3D%3A", urlSpecialPass.getPassword()); + Assert.assertEquals("google.com", urlSpecialPass.getHost()); + Assert.assertEquals("12345", urlSpecialPass.getPort()); + } + + // Equivalent of GURLTest.Empty + @CalledByNativeJavaTest + public void testEmpty() { + GURLJni.TEST_HOOKS.setInstanceForTesting(mGURLMocks); + doThrow(new RuntimeException("Should not need to parse empty URL")) + .when(mGURLMocks) + .init(any(), any()); + GURL url = new GURL(""); + Assert.assertFalse(url.isValid()); + Assert.assertEquals("", url.getSpec()); + + Assert.assertEquals("", url.getScheme()); + Assert.assertEquals("", url.getUsername()); + Assert.assertEquals("", url.getPassword()); + Assert.assertEquals("", url.getHost()); + Assert.assertEquals("", url.getPort()); + Assert.assertEquals("", url.getPath()); + Assert.assertEquals("", url.getQuery()); + Assert.assertEquals("", url.getRef()); + GURLJni.TEST_HOOKS.setInstanceForTesting(null); + } + + // Test that GURL and URI return the correct Origin. + @CalledByNativeJavaTest + @SuppressWarnings(value = "AuthLeak") + public void testOrigin() throws URISyntaxException { + final String kExpectedOrigin1 = "http://google.com:21/"; + final String kExpectedOrigin2 = ""; + GURL url1 = new GURL("filesystem:http://user:pass@google.com:21/blah#baz"); + GURL url2 = new GURL("javascript:window.alert(\"hello,world\");"); + URI uri = new URI("filesystem:http://user:pass@google.com:21/blah#baz"); + + Assert.assertEquals(kExpectedOrigin1, url1.getOrigin().getSpec()); + Assert.assertEquals(kExpectedOrigin2, url2.getOrigin().getSpec()); + URI origin = uri.getOrigin(); + Assert.assertEquals(kExpectedOrigin1, origin.getSpec()); + } + + @CalledByNativeJavaTest + public void testWideInput() throws URISyntaxException { + final String kExpectedSpec = "http://xn--1xa.com/"; + + GURL url = new GURL("http://\u03C0.com"); + Assert.assertEquals("http://xn--1xa.com/", url.getSpec()); + Assert.assertEquals("http", url.getScheme()); + Assert.assertEquals("", url.getUsername()); + Assert.assertEquals("", url.getPassword()); + Assert.assertEquals("xn--1xa.com", url.getHost()); + Assert.assertEquals("", url.getPort()); + Assert.assertEquals("/", url.getPath()); + Assert.assertEquals("", url.getQuery()); + Assert.assertEquals("", url.getRef()); + } + + @CalledByNativeJavaTest + @SuppressWarnings(value = "AuthLeak") + public void testSerialization() { + GURL cases[] = { + // Common Standard URLs. + new GURL("https://www.google.com"), + new GURL("https://www.google.com/"), + new GURL("https://www.google.com/maps.htm"), + new GURL("https://www.google.com/maps/"), + new GURL("https://www.google.com/index.html"), + new GURL("https://www.google.com/index.html?q=maps"), + new GURL("https://www.google.com/index.html#maps/"), + new GURL("https://foo:bar@www.google.com/maps.htm"), + new GURL("https://www.google.com/maps/au/index.html"), + new GURL("https://www.google.com/maps/au/north"), + new GURL("https://www.google.com/maps/au/north/"), + new GURL("https://www.google.com/maps/au/index.html?q=maps#fragment/"), + new GURL("http://www.google.com:8000/maps/au/index.html?q=maps#fragment/"), + new GURL("https://www.google.com/maps/au/north/?q=maps#fragment"), + new GURL("https://www.google.com/maps/au/north?q=maps#fragment"), + // Less common standard URLs. + new GURL("filesystem:http://www.google.com/temporary/bar.html?baz=22"), + new GURL("file:///temporary/bar.html?baz=22"), + new GURL("ftp://foo/test/index.html"), + new GURL("gopher://foo/test/index.html"), + new GURL("ws://foo/test/index.html"), + // Non-standard, + new GURL("chrome://foo/bar.html"), + new GURL("httpa://foo/test/index.html"), + new GURL("blob:https://foo.bar/test/index.html"), + new GURL("about:blank"), + new GURL("data:foobar"), + new GURL("scheme:opaque_data"), + // Invalid URLs. + new GURL("foobar"), + // URLs containing the delimiter + new GURL("https://www.google.ca/" + GURL.SERIALIZER_DELIMITER + ",foo"), + new GURL("https://www.foo" + GURL.SERIALIZER_DELIMITER + "bar.com"), + }; + + GURLJni.TEST_HOOKS.setInstanceForTesting(mGURLMocks); + doThrow(new RuntimeException("Should not re-initialize for deserialization when the " + + "version hasn't changed.")) + .when(mGURLMocks) + .init(any(), any()); + for (GURL url : cases) { + GURL out = GURL.deserialize(url.serialize()); + deepAssertEquals(url, out); + } + GURLJni.TEST_HOOKS.setInstanceForTesting(null); + } + + /** + * Tests that we re-parse the URL from the spec, which must always be the last token in the + * serialization, if the serialization version differs. + */ + @CalledByNativeJavaTest + public void testSerializationWithVersionSkew() { + GURL url = new GURL("https://www.google.com"); + String serialization = (GURL.SERIALIZER_VERSION + 1) + + ",0,0,0,0,foo,https://url.bad,blah,0,".replace(',', GURL.SERIALIZER_DELIMITER) + + url.getSpec(); + serialization = prependLengthToSerialization(serialization); + GURL out = GURL.deserialize(serialization); + deepAssertEquals(url, out); + } + + /** + * Tests that fields that aren't visible to java code are correctly serialized. + */ + @CalledByNativeJavaTest + public void testSerializationOfPrivateFields() { + String serialization = GURL.SERIALIZER_VERSION + + ",true," + // Outer Parsed. + + "1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,false,true," + // Inner Parsed. + + "17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,true,false," + + "chrome://foo/bar.html"; + serialization = serialization.replace(',', GURL.SERIALIZER_DELIMITER); + serialization = prependLengthToSerialization(serialization); + GURL url = GURL.deserialize(serialization); + Assert.assertEquals(url.serialize(), serialization); + } + + /** + * Tests serialized GURL truncated by storage. + */ + @CalledByNativeJavaTest + public void testTruncatedDeserialization() { + String serialization = "123,1,true,1,2,3,4,5,6,7,8,9,10"; + serialization = serialization.replace(',', GURL.SERIALIZER_DELIMITER); + GURL url = GURL.deserialize(serialization); + Assert.assertEquals(url, GURL.emptyGURL()); + } + + /** + * Tests serialized GURL truncated by storage. + */ + @CalledByNativeJavaTest + public void testCorruptedSerializations() { + String serialization = new GURL("https://www.google.ca").serialize(); + // Replace the scheme length (5) with an extra delimiter. + String corruptedParsed = serialization.replace('5', GURL.SERIALIZER_DELIMITER); + GURL url = GURL.deserialize(corruptedParsed); + Assert.assertEquals(GURL.emptyGURL(), url); + + String corruptedVersion = + serialization.replaceFirst(Integer.toString(GURL.SERIALIZER_VERSION), "x"); + url = GURL.deserialize(corruptedVersion); + Assert.assertEquals(GURL.emptyGURL(), url); + } +} diff --git a/chromium/url/android/parsed_android.cc b/chromium/url/android/parsed_android.cc new file mode 100644 index 00000000000..5fd47e73893 --- /dev/null +++ b/chromium/url/android/parsed_android.cc @@ -0,0 +1,96 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "url/android/parsed_android.h" + +#include <jni.h> + +#include "base/android/jni_android.h" +#include "url/gurl_jni_headers/Parsed_jni.h" + +using base::android::AttachCurrentThread; +using base::android::JavaRef; +using base::android::ScopedJavaLocalRef; + +namespace url { + +namespace { + +ScopedJavaLocalRef<jobject> CreateJavaParsed(JNIEnv* env, + const Parsed& parsed, + const JavaRef<jobject>& inner) { + static constexpr bool is_signed = + std::is_signed<decltype(parsed.scheme.begin)>::value; + static constexpr size_t offset_size = sizeof(parsed.scheme.begin); + static_assert((is_signed && sizeof(jint) >= offset_size) || + (!is_signed && sizeof(jint) > offset_size), + "Java size offsets for Parsed Components must be large enough " + "to store the full C++ offset."); + return Java_Parsed_Constructor( + env, parsed.scheme.begin, parsed.scheme.len, parsed.username.begin, + parsed.username.len, parsed.password.begin, parsed.password.len, + parsed.host.begin, parsed.host.len, parsed.port.begin, parsed.port.len, + parsed.path.begin, parsed.path.len, parsed.query.begin, parsed.query.len, + parsed.ref.begin, parsed.ref.len, parsed.potentially_dangling_markup, + inner); +} + +} // namespace + +// static +ScopedJavaLocalRef<jobject> ParsedAndroid::InitFromParsed( + JNIEnv* env, + const Parsed& parsed) { + ScopedJavaLocalRef<jobject> inner; + if (parsed.inner_parsed()) + inner = CreateJavaParsed(env, *parsed.inner_parsed(), nullptr); + return CreateJavaParsed(env, parsed, inner); +} + +static jlong JNI_Parsed_CreateNative(JNIEnv* env, + jint scheme_begin, + jint scheme_length, + jint username_begin, + jint username_length, + jint password_begin, + jint password_length, + jint host_begin, + jint host_length, + jint port_begin, + jint port_length, + jint path_begin, + jint path_length, + jint query_begin, + jint query_length, + jint ref_begin, + jint ref_length, + jboolean potentially_dangling_markup, + jlong inner_parsed) { + Parsed* parsed = new Parsed(); + parsed->scheme.begin = scheme_begin; + parsed->scheme.len = scheme_length; + parsed->username.begin = username_begin; + parsed->username.len = username_length; + parsed->password.begin = password_begin; + parsed->password.len = password_length; + parsed->host.begin = host_begin; + parsed->host.len = host_length; + parsed->port.begin = port_begin; + parsed->port.len = port_length; + parsed->path.begin = path_begin; + parsed->path.len = path_length; + parsed->query.begin = query_begin; + parsed->query.len = query_length; + parsed->ref.begin = ref_begin; + parsed->ref.len = ref_length; + parsed->potentially_dangling_markup = potentially_dangling_markup; + Parsed* inner = reinterpret_cast<Parsed*>(inner_parsed); + if (inner) { + parsed->set_inner_parsed(*inner); + delete inner; + } + return reinterpret_cast<intptr_t>(parsed); +} + +} // namespace url diff --git a/chromium/url/android/parsed_android.h b/chromium/url/android/parsed_android.h new file mode 100644 index 00000000000..cb8f2ba75b9 --- /dev/null +++ b/chromium/url/android/parsed_android.h @@ -0,0 +1,22 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef URL_ANDROID_PARSED_ANDROID_H_ +#define URL_ANDROID_PARSED_ANDROID_H_ + +#include "base/android/scoped_java_ref.h" +#include "url/third_party/mozilla/url_parse.h" + +namespace url { + +class ParsedAndroid { + public: + static base::android::ScopedJavaLocalRef<jobject> InitFromParsed( + JNIEnv* env, + const Parsed& parsed); +}; + +} // namespace url + +#endif // URL_ANDROID_PARSED_ANDROID_H_ diff --git a/chromium/url/gurl_unittest.cc b/chromium/url/gurl_unittest.cc index 31767db6546..7a4e8727be9 100644 --- a/chromium/url/gurl_unittest.cc +++ b/chromium/url/gurl_unittest.cc @@ -289,21 +289,42 @@ TEST(GURLTest, Resolve) { bool expected_valid; const char* expected; } resolve_cases[] = { - {"http://www.google.com/", "foo.html", true, "http://www.google.com/foo.html"}, - {"http://www.google.com/foo/", "bar", true, "http://www.google.com/foo/bar"}, - {"http://www.google.com/foo/", "/bar", true, "http://www.google.com/bar"}, - {"http://www.google.com/foo", "bar", true, "http://www.google.com/bar"}, - {"http://www.google.com/", "http://images.google.com/foo.html", true, "http://images.google.com/foo.html"}, - {"http://www.google.com/", "http://images.\tgoogle.\ncom/\rfoo.html", true, "http://images.google.com/foo.html"}, - {"http://www.google.com/blah/bloo?c#d", "../../../hello/./world.html?a#b", true, "http://www.google.com/hello/world.html?a#b"}, - {"http://www.google.com/foo#bar", "#com", true, "http://www.google.com/foo#com"}, - {"http://www.google.com/", "Https:images.google.com", true, "https://images.google.com/"}, + {"http://www.google.com/", "foo.html", true, + "http://www.google.com/foo.html"}, + {"http://www.google.com/foo/", "bar", true, + "http://www.google.com/foo/bar"}, + {"http://www.google.com/foo/", "/bar", true, "http://www.google.com/bar"}, + {"http://www.google.com/foo", "bar", true, "http://www.google.com/bar"}, + {"http://www.google.com/", "http://images.google.com/foo.html", true, + "http://images.google.com/foo.html"}, + {"http://www.google.com/", "http://images.\tgoogle.\ncom/\rfoo.html", + true, "http://images.google.com/foo.html"}, + {"http://www.google.com/blah/bloo?c#d", "../../../hello/./world.html?a#b", + true, "http://www.google.com/hello/world.html?a#b"}, + {"http://www.google.com/foo#bar", "#com", true, + "http://www.google.com/foo#com"}, + {"http://www.google.com/", "Https:images.google.com", true, + "https://images.google.com/"}, // A non-standard base can be replaced with a standard absolute URL. - {"data:blahblah", "http://google.com/", true, "http://google.com/"}, - {"data:blahblah", "http:google.com", true, "http://google.com/"}, + {"data:blahblah", "http://google.com/", true, "http://google.com/"}, + {"data:blahblah", "http:google.com", true, "http://google.com/"}, // Filesystem URLs have different paths to test. - {"filesystem:http://www.google.com/type/", "foo.html", true, "filesystem:http://www.google.com/type/foo.html"}, - {"filesystem:http://www.google.com/type/", "../foo.html", true, "filesystem:http://www.google.com/type/foo.html"}, + {"filesystem:http://www.google.com/type/", "foo.html", true, + "filesystem:http://www.google.com/type/foo.html"}, + {"filesystem:http://www.google.com/type/", "../foo.html", true, + "filesystem:http://www.google.com/type/foo.html"}, + // https://crbug.com/530123 - scheme validation (e.g. are "10.0.0.7:" + // or "x1:" valid schemes) when deciding if |relative| is an absolute url. + {"file:///some/dir/ip-relative.html", "10.0.0.7:8080/foo.html", true, + "file:///some/dir/10.0.0.7:8080/foo.html"}, + {"file:///some/dir/", "1://host", true, "file:///some/dir/1://host"}, + {"file:///some/dir/", "x1://host", true, "x1://host"}, + {"file:///some/dir/", "X1://host", true, "x1://host"}, + {"file:///some/dir/", "x.://host", true, "x.://host"}, + {"file:///some/dir/", "x+://host", true, "x+://host"}, + {"file:///some/dir/", "x-://host", true, "x-://host"}, + {"file:///some/dir/", "x!://host", true, "file:///some/dir/x!://host"}, + {"file:///some/dir/", "://host", true, "file:///some/dir/://host"}, }; for (size_t i = 0; i < base::size(resolve_cases); i++) { diff --git a/chromium/url/ipc/BUILD.gn b/chromium/url/ipc/BUILD.gn index 52968a19f17..9b3d9dbea8a 100644 --- a/chromium/url/ipc/BUILD.gn +++ b/chromium/url/ipc/BUILD.gn @@ -17,17 +17,13 @@ component("url_ipc") { "//ipc", "//url", ] - deps = [ - "//base", - ] + deps = [ "//base" ] } # IPC unit tests aren't build on iOS. if (!is_ios) { test("url_ipc_unittests") { - sources = [ - "url_param_traits_unittest.cc", - ] + sources = [ "url_param_traits_unittest.cc" ] deps = [ ":url_ipc", diff --git a/chromium/url/mojom/BUILD.gn b/chromium/url/mojom/BUILD.gn index e60a2a9058c..4c9f079ee9a 100644 --- a/chromium/url/mojom/BUILD.gn +++ b/chromium/url/mojom/BUILD.gn @@ -6,16 +6,45 @@ import("//mojo/public/tools/bindings/mojom.gni") mojom("url_mojom_gurl") { generate_java = true - sources = [ - "url.mojom", + sources = [ "url.mojom" ] + + cpp_typemaps = [ + { + types = [ + { + mojom = "url.mojom.Url" + cpp = "::GURL" + }, + ] + traits_headers = [ "//url/mojom/url_gurl_mojom_traits.h" ] + traits_public_deps = [ + ":mojom_traits", + "//url", + ] + }, + ] + + blink_cpp_typemaps = [ + { + types = [ + { + mojom = "url.mojom.Url" + cpp = "::blink::KURL" + force_serialize = true + }, + ] + traits_headers = [ + "//third_party/blink/renderer/platform/mojo/kurl_mojom_traits.h", + "//third_party/blink/renderer/platform/weborigin/kurl_hash.h", + ] + traits_public_deps = [ "//url" ] + }, ] } mojom("url_mojom_origin") { generate_java = true - sources = [ - "origin.mojom", - ] + sources = [ "origin.mojom" ] public_deps = [ ":url_mojom_gurl", @@ -23,15 +52,64 @@ mojom("url_mojom_origin") { ] check_includes_blink = false + + cpp_typemaps = [ + { + types = [ + { + mojom = "url.mojom.Origin" + cpp = "::url::Origin" + }, + ] + traits_headers = [ "//url/mojom/origin_mojom_traits.h" ] + traits_public_deps = [ + ":mojom_traits", + "//url", + ] + }, + ] + + blink_cpp_typemaps = [ + { + types = [ + { + mojom = "url.mojom.Origin" + cpp = "::scoped_refptr<const ::blink::SecurityOrigin>" + nullable_is_same_type = true + }, + ] + traits_headers = [ "//third_party/blink/renderer/platform/mojo/security_origin_mojom_traits.h" ] + traits_public_deps = [ "//url" ] + }, + ] } mojom("test_url_mojom_gurl") { - sources = [ - "url_test.mojom", - ] + sources = [ "url_test.mojom" ] public_deps = [ ":url_mojom_gurl", ":url_mojom_origin", ] } + +component("mojom_traits") { + output_name = "url_mojom_traits" + + sources = [ + "origin_mojom_traits.cc", + "origin_mojom_traits.h", + "url_gurl_mojom_traits.cc", + "url_gurl_mojom_traits.h", + ] + + defines = [ "IS_URL_MOJOM_TRAITS_IMPL" ] + + public_deps = [ + ":url_mojom_gurl_shared", + ":url_mojom_origin_shared", + "//base", + "//mojo/public/cpp/base:shared_typemap_traits", + "//url", + ] +} diff --git a/chromium/url/mojom/OWNERS b/chromium/url/mojom/OWNERS index ff78c432aff..06776eeacb1 100644 --- a/chromium/url/mojom/OWNERS +++ b/chromium/url/mojom/OWNERS @@ -2,6 +2,4 @@ per-file *.mojom=set noparent per-file *.mojom=file://ipc/SECURITY_OWNERS per-file *_mojom_traits*.*=set noparent per-file *_mojom_traits*.*=file://ipc/SECURITY_OWNERS -per-file *.typemap=set noparent -per-file *.typemap=file://ipc/SECURITY_OWNERS # COMPONENT: Internals>Mojo diff --git a/chromium/url/mojom/gurl.typemap b/chromium/url/mojom/gurl.typemap deleted file mode 100644 index 64d8507f7a9..00000000000 --- a/chromium/url/mojom/gurl.typemap +++ /dev/null @@ -1,11 +0,0 @@ -# Copyright 2016 The Chromium Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -mojom = "//url/mojom/url.mojom" -public_headers = [ "//url/gurl.h" ] -traits_headers = [ "//url/mojom/url_gurl_mojom_traits.h" ] -deps = [ - "//url", -] -type_mappings = [ "url.mojom.Url=::GURL" ] diff --git a/chromium/url/mojom/origin.typemap b/chromium/url/mojom/origin.typemap deleted file mode 100644 index 0dcf3bb0c09..00000000000 --- a/chromium/url/mojom/origin.typemap +++ /dev/null @@ -1,11 +0,0 @@ -# Copyright 2016 The Chromium Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -mojom = "//url/mojom/origin.mojom" -public_headers = [ "//url/origin.h" ] -traits_headers = [ "//url/mojom/origin_mojom_traits.h" ] -deps = [ - "//url", -] -type_mappings = [ "url.mojom.Origin=::url::Origin" ] diff --git a/chromium/url/mojom/origin_mojom_traits.cc b/chromium/url/mojom/origin_mojom_traits.cc new file mode 100644 index 00000000000..213c43bdd47 --- /dev/null +++ b/chromium/url/mojom/origin_mojom_traits.cc @@ -0,0 +1,34 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "url/mojom/origin_mojom_traits.h" + +#include "base/strings/string_piece.h" + +namespace mojo { + +// static +bool StructTraits<url::mojom::OriginDataView, url::Origin>::Read( + url::mojom::OriginDataView data, + url::Origin* out) { + base::StringPiece scheme, host; + base::Optional<base::UnguessableToken> nonce_if_opaque; + if (!data.ReadScheme(&scheme) || !data.ReadHost(&host) || + !data.ReadNonceIfOpaque(&nonce_if_opaque)) + return false; + + base::Optional<url::Origin> creation_result = + nonce_if_opaque + ? url::Origin::UnsafelyCreateOpaqueOriginWithoutNormalization( + scheme, host, data.port(), url::Origin::Nonce(*nonce_if_opaque)) + : url::Origin::UnsafelyCreateTupleOriginWithoutNormalization( + scheme, host, data.port()); + if (!creation_result) + return false; + + *out = std::move(creation_result.value()); + return true; +} + +} // namespace mojo diff --git a/chromium/url/mojom/origin_mojom_traits.h b/chromium/url/mojom/origin_mojom_traits.h index ac34fe91e96..79ee1617868 100644 --- a/chromium/url/mojom/origin_mojom_traits.h +++ b/chromium/url/mojom/origin_mojom_traits.h @@ -5,16 +5,18 @@ #ifndef URL_MOJO_ORIGIN_MOJOM_TRAITS_H_ #define URL_MOJO_ORIGIN_MOJOM_TRAITS_H_ -#include "base/strings/string_piece.h" +#include "base/component_export.h" +#include "base/optional.h" #include "base/unguessable_token.h" #include "mojo/public/cpp/base/unguessable_token_mojom_traits.h" -#include "url/mojom/origin.mojom.h" +#include "url/mojom/origin.mojom-shared.h" #include "url/origin.h" namespace mojo { template <> -struct StructTraits<url::mojom::OriginDataView, url::Origin> { +struct COMPONENT_EXPORT(URL_MOJOM_TRAITS) + StructTraits<url::mojom::OriginDataView, url::Origin> { static const std::string& scheme(const url::Origin& r) { return r.GetTupleOrPrecursorTupleIfOpaque().scheme(); } @@ -29,26 +31,7 @@ struct StructTraits<url::mojom::OriginDataView, url::Origin> { // TODO(nasko): Consider returning a const reference here. return r.GetNonceForSerialization(); } - static bool Read(url::mojom::OriginDataView data, url::Origin* out) { - base::StringPiece scheme, host; - base::Optional<base::UnguessableToken> nonce_if_opaque; - if (!data.ReadScheme(&scheme) || !data.ReadHost(&host) || - !data.ReadNonceIfOpaque(&nonce_if_opaque)) - return false; - - base::Optional<url::Origin> creation_result = - nonce_if_opaque - ? url::Origin::UnsafelyCreateOpaqueOriginWithoutNormalization( - scheme, host, data.port(), - url::Origin::Nonce(*nonce_if_opaque)) - : url::Origin::UnsafelyCreateTupleOriginWithoutNormalization( - scheme, host, data.port()); - if (!creation_result) - return false; - - *out = std::move(creation_result.value()); - return true; - } + static bool Read(url::mojom::OriginDataView data, url::Origin* out); }; } // namespace mojo diff --git a/chromium/url/mojom/typemaps.gni b/chromium/url/mojom/typemaps.gni deleted file mode 100644 index 53875c3cd0a..00000000000 --- a/chromium/url/mojom/typemaps.gni +++ /dev/null @@ -1,8 +0,0 @@ -# Copyright 2016 The Chromium Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -typemaps = [ - "//url/mojom/origin.typemap", - "//url/mojom/gurl.typemap", -] diff --git a/chromium/url/mojom/url_gurl_mojom_traits.cc b/chromium/url/mojom/url_gurl_mojom_traits.cc new file mode 100644 index 00000000000..4d776dba9c4 --- /dev/null +++ b/chromium/url/mojom/url_gurl_mojom_traits.cc @@ -0,0 +1,40 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "url/mojom/url_gurl_mojom_traits.h" + +#include "url/url_constants.h" + +namespace mojo { + +// static +base::StringPiece StructTraits<url::mojom::UrlDataView, GURL>::url( + const GURL& r) { + if (r.possibly_invalid_spec().length() > url::kMaxURLChars || !r.is_valid()) { + return base::StringPiece(); + } + + return base::StringPiece(r.possibly_invalid_spec().c_str(), + r.possibly_invalid_spec().length()); +} + +// static +bool StructTraits<url::mojom::UrlDataView, GURL>::Read( + url::mojom::UrlDataView data, + GURL* out) { + base::StringPiece url_string; + if (!data.ReadUrl(&url_string)) + return false; + + if (url_string.length() > url::kMaxURLChars) + return false; + + *out = GURL(url_string); + if (!url_string.empty() && !out->is_valid()) + return false; + + return true; +} + +} // namespace mojo diff --git a/chromium/url/mojom/url_gurl_mojom_traits.h b/chromium/url/mojom/url_gurl_mojom_traits.h index 57a36c96334..7d668209ce0 100644 --- a/chromium/url/mojom/url_gurl_mojom_traits.h +++ b/chromium/url/mojom/url_gurl_mojom_traits.h @@ -5,38 +5,19 @@ #ifndef URL_MOJOM_URL_GURL_MOJOM_TRAITS_H_ #define URL_MOJOM_URL_GURL_MOJOM_TRAITS_H_ +#include "base/component_export.h" #include "base/strings/string_piece.h" +#include "mojo/public/cpp/bindings/struct_traits.h" #include "url/gurl.h" -#include "url/mojom/url.mojom.h" -#include "url/url_constants.h" +#include "url/mojom/url.mojom-shared.h" namespace mojo { template <> -struct StructTraits<url::mojom::UrlDataView, GURL> { - static base::StringPiece url(const GURL& r) { - if (r.possibly_invalid_spec().length() > url::kMaxURLChars || - !r.is_valid()) { - return base::StringPiece(); - } - - return base::StringPiece(r.possibly_invalid_spec().c_str(), - r.possibly_invalid_spec().length()); - } - static bool Read(url::mojom::UrlDataView data, GURL* out) { - base::StringPiece url_string; - if (!data.ReadUrl(&url_string)) - return false; - - if (url_string.length() > url::kMaxURLChars) - return false; - - *out = GURL(url_string); - if (!url_string.empty() && !out->is_valid()) - return false; - - return true; - } +struct COMPONENT_EXPORT(URL_MOJOM_TRAITS) + StructTraits<url::mojom::UrlDataView, GURL> { + static base::StringPiece url(const GURL& r); + static bool Read(url::mojom::UrlDataView data, GURL* out); }; } // namespace mojo diff --git a/chromium/url/origin.cc b/chromium/url/origin.cc index 0686e0525f0..26079d7730b 100644 --- a/chromium/url/origin.cc +++ b/chromium/url/origin.cc @@ -7,8 +7,12 @@ #include <stdint.h> #include <algorithm> +#include <vector> +#include "base/base64.h" +#include "base/containers/span.h" #include "base/logging.h" +#include "base/pickle.h" #include "base/stl_util.h" #include "base/strings/strcat.h" #include "base/strings/string_number_conversions.h" @@ -42,12 +46,12 @@ Origin Origin::Create(const GURL& url) { // It's SchemeHostPort's responsibility to filter out unrecognized schemes; // sanity check that this is happening. - DCHECK(tuple.IsInvalid() || url.IsStandard() || + DCHECK(!tuple.IsValid() || url.IsStandard() || base::Contains(GetLocalSchemes(), url.scheme_piece()) || AllowNonStandardSchemesForAndroidWebView()); } - if (tuple.IsInvalid()) + if (!tuple.IsValid()) return Origin(); return Origin(std::move(tuple)); } @@ -74,7 +78,7 @@ base::Optional<Origin> Origin::UnsafelyCreateTupleOriginWithoutNormalization( uint16_t port) { SchemeHostPort tuple(scheme.as_string(), host.as_string(), port, SchemeHostPort::CHECK_CANONICALIZATION); - if (tuple.IsInvalid()) + if (!tuple.IsValid()) return base::nullopt; return Origin(std::move(tuple)); } @@ -91,7 +95,7 @@ base::Optional<Origin> Origin::UnsafelyCreateOpaqueOriginWithoutNormalization( // For opaque origins, it is okay for the SchemeHostPort to be invalid; // however, this should only arise when the arguments indicate the // canonical representation of the invalid SchemeHostPort. - if (precursor.IsInvalid() && + if (!precursor.IsValid() && !(precursor_scheme.empty() && precursor_host.empty() && precursor_port == 0)) { return base::nullopt; @@ -105,7 +109,7 @@ Origin Origin::CreateFromNormalizedTuple(std::string scheme, uint16_t port) { SchemeHostPort tuple(std::move(scheme), std::move(host), port, SchemeHostPort::ALREADY_CANONICALIZED); - if (tuple.IsInvalid()) + if (!tuple.IsValid()) return Origin(); return Origin(std::move(tuple)); } @@ -171,7 +175,7 @@ bool Origin::CanBeDerivedFrom(const GURL& url) const { // And if it is unique opaque origin, it definitely is fine. But if there // is a precursor stored, we should fall through to compare the tuples. - if (tuple_.IsInvalid()) + if (!tuple_.IsValid()) return true; } @@ -198,7 +202,7 @@ bool Origin::CanBeDerivedFrom(const GURL& url) const { // opaque origin. It is valid case, as any browser-initiated navigation // to about:blank or data: URL will result in a document with such // origin and it is valid for it to create blob: URLs. - if (tuple_.IsInvalid()) + if (!tuple_.IsValid()) return true; url_tuple = SchemeHostPort(GURL(url.GetContent())); @@ -221,7 +225,7 @@ bool Origin::CanBeDerivedFrom(const GURL& url) const { // If |this| does not have valid precursor tuple, it is unique opaque origin, // which is what we expect non-standard schemes to get. - if (tuple_.IsInvalid()) + if (!tuple_.IsValid()) return true; // However, when there is precursor present, the schemes must match. @@ -257,7 +261,7 @@ std::string Origin::GetDebugString() const { : nonce_->raw_token().ToString(); std::string out = base::StrCat({Serialize(), " [internally: (", nonce, ")"}); - if (tuple_.IsInvalid()) + if (!tuple_.IsValid()) base::StrAppend(&out, {" anonymous]"}); else base::StrAppend(&out, {" derived from ", tuple_.Serialize(), "]"}); @@ -266,7 +270,7 @@ std::string Origin::GetDebugString() const { Origin::Origin(SchemeHostPort tuple) : tuple_(std::move(tuple)) { DCHECK(!opaque()); - DCHECK(!tuple_.IsInvalid()); + DCHECK(tuple_.IsValid()); } // Constructs an opaque origin derived from |precursor|. @@ -279,6 +283,82 @@ Origin::Origin(const Nonce& nonce, SchemeHostPort precursor) DCHECK_EQ(0U, port()); } +// The pickle is saved in the following format, in order: +// string - tuple_.GetURL().spec(). +// uint64_t (if opaque) - high bits of nonce if opaque. 0 if not initialized. +// uint64_t (if opaque) - low bits of nonce if opaque. 0 if not initialized. +base::Optional<std::string> Origin::SerializeWithNonce() const { + if (!opaque() && !tuple_.IsValid()) + return base::nullopt; + + base::Pickle pickle; + pickle.WriteString(tuple_.Serialize()); + if (opaque() && !nonce_->raw_token().is_empty()) { + pickle.WriteUInt64(nonce_->token().GetHighForSerialization()); + pickle.WriteUInt64(nonce_->token().GetLowForSerialization()); + } else if (opaque()) { + // Nonce hasn't been initialized. + pickle.WriteUInt64(0); + pickle.WriteUInt64(0); + } + + base::span<const uint8_t> data( + static_cast<const uint8_t*>(pickle.data()), + static_cast<const uint8_t*>(pickle.data()) + pickle.size()); + // Base64 encode the data to make it nicer to play with. + return base::Base64Encode(data); +} + +// static +base::Optional<Origin> Origin::Deserialize(const std::string& value) { + std::string data; + if (!base::Base64Decode(value, &data)) + return base::nullopt; + base::Pickle pickle(reinterpret_cast<char*>(&data[0]), data.size()); + base::PickleIterator reader(pickle); + + std::string pickled_url; + if (!reader.ReadString(&pickled_url)) + return base::nullopt; + GURL url(pickled_url); + + // If only a tuple was serialized, then this origin is not opaque. For opaque + // origins, we expect two uint64's to be left in the pickle. + bool is_opaque = !reader.ReachedEnd(); + + // Opaque origins without a tuple are ok. + if (!is_opaque && !url.is_valid()) + return base::nullopt; + SchemeHostPort tuple(url); + + // Possible successful early return if the pickled Origin was not opaque. + if (!is_opaque) { + Origin origin(tuple); + if (origin.opaque()) + return base::nullopt; // Something went horribly wrong. + return origin; + } + + uint64_t nonce_high = 0; + if (!reader.ReadUInt64(&nonce_high)) + return base::nullopt; + + uint64_t nonce_low = 0; + if (!reader.ReadUInt64(&nonce_low)) + return base::nullopt; + + Origin::Nonce nonce; + if (nonce_high != 0 && nonce_low != 0) { + // The serialized nonce wasn't empty, so copy it here. + nonce = Origin::Nonce( + base::UnguessableToken::Deserialize(nonce_high, nonce_low)); + } + Origin origin; + origin.nonce_ = std::move(nonce); + origin.tuple_ = tuple; + return origin; +} + std::ostream& operator<<(std::ostream& out, const url::Origin& origin) { out << origin.GetDebugString(); return out; @@ -351,4 +431,17 @@ bool Origin::Nonce::operator!=(const Origin::Nonce& other) const { return !(*this == other); } +namespace debug { + +ScopedOriginCrashKey::ScopedOriginCrashKey( + base::debug::CrashKeyString* crash_key, + const url::Origin* value) + : base::debug::ScopedCrashKeyString( + crash_key, + value ? value->GetDebugString() : "nullptr") {} + +ScopedOriginCrashKey::~ScopedOriginCrashKey() = default; + +} // namespace debug + } // namespace url diff --git a/chromium/url/origin.h b/chromium/url/origin.h index f0b85a39628..845d5730513 100644 --- a/chromium/url/origin.h +++ b/chromium/url/origin.h @@ -7,10 +7,12 @@ #include <stdint.h> +#include <memory> #include <string> #include "base/component_export.h" #include "base/debug/alias.h" +#include "base/debug/crash_logging.h" #include "base/optional.h" #include "base/strings/string16.h" #include "base/strings/string_piece.h" @@ -53,6 +55,11 @@ struct StructTraits; struct UrlOriginAdapter; } // namespace mojo +namespace net { +class NetworkIsolationKey; +class OpaqueNonTransientNetworkIsolationKeyTest; +} // namespace net + namespace url { namespace mojom { @@ -144,6 +151,9 @@ class COMPONENT_EXPORT(URL) Origin { // 2. 'filesystem' URLs behave as 'blob' URLs (that is, the origin is parsed // out of everything in the URL which follows the scheme). // 3. 'file' URLs all parse as ("file", "", 0). + // + // Note that the returned Origin may have a different scheme and host from + // |url| (e.g. in case of blob URLs - see OriginTest.ConstructFromGURL). static Origin Create(const GURL& url); // Creates an Origin for the resource |url| as if it were requested @@ -288,6 +298,8 @@ class COMPONENT_EXPORT(URL) Origin { private: friend class blink::SecurityOrigin; + friend class net::NetworkIsolationKey; + friend class net::OpaqueNonTransientNetworkIsolationKeyTest; friend class OriginTest; friend struct mojo::UrlOriginAdapter; friend struct ipc_fuzzer::FuzzTraits<Origin>; @@ -382,6 +394,16 @@ class COMPONENT_EXPORT(URL) Origin { // used only when trying to send an Origin across an IPC pipe. base::Optional<base::UnguessableToken> GetNonceForSerialization() const; + // Serializes this Origin, including its nonce if it is opaque. If an opaque + // origin's |tuple_| is invalid or the nonce isn't initialized, nullopt is + // returned. Use of this method should be limited as an opaque origin will + // never be matchable in future browser sessions. + base::Optional<std::string> SerializeWithNonce() const; + + // Deserializes an origin from |ToValueWithNonce|. Returns nullopt if the + // value was invalid in any way. + static base::Optional<Origin> Deserialize(const std::string& value); + // The tuple is used for both tuple origins (e.g. https://example.com:80), as // well as for opaque origins, where it tracks the tuple origin from which // the opaque origin was initially derived (we call this the "precursor" @@ -408,6 +430,21 @@ COMPONENT_EXPORT(URL) bool IsSameOriginWith(const GURL& a, const GURL& b); #define DEBUG_ALIAS_FOR_ORIGIN(var_name, origin) \ DEBUG_ALIAS_FOR_CSTR(var_name, (origin).Serialize().c_str(), 128) +namespace debug { + +class COMPONENT_EXPORT(URL) ScopedOriginCrashKey + : public base::debug::ScopedCrashKeyString { + public: + ScopedOriginCrashKey(base::debug::CrashKeyString* crash_key, + const url::Origin* value); + ~ScopedOriginCrashKey(); + + ScopedOriginCrashKey(const ScopedOriginCrashKey&) = delete; + ScopedOriginCrashKey& operator=(const ScopedOriginCrashKey&) = delete; +}; + +} // namespace debug + } // namespace url #endif // URL_ORIGIN_H_ diff --git a/chromium/url/origin_unittest.cc b/chromium/url/origin_unittest.cc index 1cd9889ea16..a41281d1e8d 100644 --- a/chromium/url/origin_unittest.cc +++ b/chromium/url/origin_unittest.cc @@ -55,7 +55,6 @@ class OriginTest : public ::testing::Test { AddStandardScheme("standard-but-noaccess", SchemeType::SCHEME_WITH_HOST); AddNoAccessScheme("standard-but-noaccess"); } - void TearDown() override { url::ResetForTests(); } ::testing::AssertionResult DoEqualityComparisons(const url::Origin& a, const url::Origin& b, @@ -96,7 +95,8 @@ class OriginTest : public ::testing::Test { return origin.GetNonceForSerialization(); } - // Wrapper around url::Origin method to expose it to tests. + // Wrappers around url::Origin methods to expose it to tests. + base::Optional<Origin> UnsafelyCreateOpaqueOriginWithoutNormalization( base::StringPiece precursor_scheme, base::StringPiece precursor_host, @@ -105,6 +105,17 @@ class OriginTest : public ::testing::Test { return Origin::UnsafelyCreateOpaqueOriginWithoutNormalization( precursor_scheme, precursor_host, precursor_port, nonce); } + + base::Optional<std::string> SerializeWithNonce(const Origin& origin) { + return origin.SerializeWithNonce(); + } + + base::Optional<Origin> Deserialize(const std::string& value) { + return Origin::Deserialize(value); + } + + private: + ScopedSchemeRegistryForTests scoped_registry_; }; TEST_F(OriginTest, OpaqueOriginComparison) { @@ -116,14 +127,14 @@ TEST_F(OriginTest, OpaqueOriginComparison) { EXPECT_EQ("", opaque_a.host()); EXPECT_EQ(0, opaque_a.port()); EXPECT_EQ(SchemeHostPort(), opaque_a.GetTupleOrPrecursorTupleIfOpaque()); - EXPECT_TRUE(opaque_a.GetTupleOrPrecursorTupleIfOpaque().IsInvalid()); + EXPECT_FALSE(opaque_a.GetTupleOrPrecursorTupleIfOpaque().IsValid()); EXPECT_TRUE(opaque_b.opaque()); EXPECT_EQ("", opaque_b.scheme()); EXPECT_EQ("", opaque_b.host()); EXPECT_EQ(0, opaque_b.port()); EXPECT_EQ(SchemeHostPort(), opaque_b.GetTupleOrPrecursorTupleIfOpaque()); - EXPECT_TRUE(opaque_b.GetTupleOrPrecursorTupleIfOpaque().IsInvalid()); + EXPECT_FALSE(opaque_b.GetTupleOrPrecursorTupleIfOpaque().IsValid()); // Two default-constructed Origins should always be cross origin to each // other. @@ -377,7 +388,7 @@ TEST_F(OriginTest, ConstructFromGURL) { .DeriveNewOpaqueOrigin(); EXPECT_TRUE(derived_opaque.opaque()); EXPECT_NE(origin, derived_opaque); - EXPECT_FALSE(derived_opaque.GetTupleOrPrecursorTupleIfOpaque().IsInvalid()); + EXPECT_TRUE(derived_opaque.GetTupleOrPrecursorTupleIfOpaque().IsValid()); EXPECT_EQ(origin.GetTupleOrPrecursorTupleIfOpaque(), derived_opaque.GetTupleOrPrecursorTupleIfOpaque()); EXPECT_EQ(derived_opaque, derived_opaque); @@ -386,8 +397,8 @@ TEST_F(OriginTest, ConstructFromGURL) { Origin::Resolve(GURL("data:text/html,baz"), origin); EXPECT_TRUE(derived_opaque_via_data_url.opaque()); EXPECT_NE(origin, derived_opaque_via_data_url); - EXPECT_FALSE(derived_opaque_via_data_url.GetTupleOrPrecursorTupleIfOpaque() - .IsInvalid()); + EXPECT_TRUE(derived_opaque_via_data_url.GetTupleOrPrecursorTupleIfOpaque() + .IsValid()); EXPECT_EQ(origin.GetTupleOrPrecursorTupleIfOpaque(), derived_opaque_via_data_url.GetTupleOrPrecursorTupleIfOpaque()); EXPECT_NE(derived_opaque, derived_opaque_via_data_url); @@ -623,9 +634,9 @@ TEST_F(OriginTest, DomainIs) { }; for (const auto& test_case : kTestCases) { - SCOPED_TRACE(testing::Message() << "(url, domain): (" << test_case.url - << ", " << test_case.lower_ascii_domain - << ")"); + SCOPED_TRACE(testing::Message() + << "(url, domain): (" << test_case.url << ", " + << test_case.lower_ascii_domain << ")"); GURL url(test_case.url); ASSERT_TRUE(url.is_valid()); Origin origin = Origin::Create(url); @@ -656,6 +667,7 @@ TEST_F(OriginTest, NonStandardScheme) { Origin origin = Origin::Create(GURL("cow://")); EXPECT_TRUE(origin.opaque()); } + TEST_F(OriginTest, NonStandardSchemeWithAndroidWebViewHack) { EnableNonStandardSchemesForAndroidWebView(); Origin origin = Origin::Create(GURL("cow://")); @@ -663,10 +675,10 @@ TEST_F(OriginTest, NonStandardSchemeWithAndroidWebViewHack) { EXPECT_EQ("cow", origin.scheme()); EXPECT_EQ("", origin.host()); EXPECT_EQ(0, origin.port()); - ResetForTests(); } TEST_F(OriginTest, CanBeDerivedFrom) { + AddStandardScheme("new-standard", SchemeType::SCHEME_WITH_HOST); Origin opaque_unique_origin = Origin(); Origin regular_origin = Origin::Create(GURL("https://a.com/")); @@ -684,7 +696,6 @@ TEST_F(OriginTest, CanBeDerivedFrom) { non_standard_scheme_origin.DeriveNewOpaqueOrigin(); // Also, add new standard scheme that is local to the test. - AddStandardScheme("new-standard", SchemeType::SCHEME_WITH_HOST); Origin new_standard_origin = Origin::Create(GURL("new-standard://host/")); Origin new_standard_opaque_precursor_origin = new_standard_origin.DeriveNewOpaqueOrigin(); @@ -859,4 +870,78 @@ TEST_F(OriginTest, GetDebugString) { "file:// [internally: file://example.com]"); } +TEST_F(OriginTest, Deserialize) { + std::vector<GURL> valid_urls = { + GURL("https://a.com"), GURL("http://a"), + GURL("http://a:80"), GURL("file://a.com/etc/passwd"), + GURL("file:///etc/passwd"), GURL("http://192.168.1.1"), + GURL("http://[2001:db8::1]/"), + }; + for (const GURL& url : valid_urls) { + SCOPED_TRACE(url.spec()); + Origin origin = Origin::Create(url); + base::Optional<std::string> serialized = SerializeWithNonce(origin); + ASSERT_TRUE(serialized); + + base::Optional<Origin> deserialized = Deserialize(std::move(*serialized)); + ASSERT_TRUE(deserialized.has_value()); + + EXPECT_TRUE(DoEqualityComparisons(origin, deserialized.value(), true)); + EXPECT_EQ(origin.GetDebugString(), deserialized.value().GetDebugString()); + } +} + +TEST_F(OriginTest, DeserializeInvalid) { + EXPECT_EQ(base::nullopt, Deserialize(std::string())); + EXPECT_EQ(base::nullopt, Deserialize("deadbeef")); + EXPECT_EQ(base::nullopt, Deserialize("0123456789")); + EXPECT_EQ(base::nullopt, Deserialize("https://a.com")); + EXPECT_EQ(base::nullopt, Deserialize("https://192.168.1.1")); +} + +TEST_F(OriginTest, SerializeTBDNonce) { + std::vector<GURL> invalid_urls = { + GURL("data:uniqueness"), GURL("data:,"), + GURL("data:text/html,Hello!"), GURL("javascript:alert(1)"), + GURL("about:blank"), GURL("google.com"), + }; + for (const GURL& url : invalid_urls) { + SCOPED_TRACE(url.spec()); + Origin origin = Origin::Create(url); + base::Optional<std::string> serialized = SerializeWithNonce(origin); + base::Optional<Origin> deserialized = Deserialize(std::move(*serialized)); + ASSERT_TRUE(deserialized.has_value()); + + // Can't use DoEqualityComparisons here since empty nonces are never == + // unless they are the same object. + EXPECT_EQ(origin.GetDebugString(), deserialized.value().GetDebugString()); + } + + // Same basic test as above, but without a GURL to create tuple_. + Origin opaque; + base::Optional<std::string> serialized = SerializeWithNonce(opaque); + ASSERT_TRUE(serialized); + + base::Optional<Origin> deserialized = Deserialize(std::move(*serialized)); + ASSERT_TRUE(deserialized.has_value()); + + // Can't use DoEqualityComparisons here since empty nonces are never == unless + // they are the same object. + EXPECT_EQ(opaque.GetDebugString(), deserialized.value().GetDebugString()); +} + +TEST_F(OriginTest, DeserializeValidNonce) { + Origin opaque; + GetNonce(opaque); + + base::Optional<std::string> serialized = SerializeWithNonce(opaque); + ASSERT_TRUE(serialized); + + base::Optional<Origin> deserialized = Deserialize(std::move(*serialized)); + ASSERT_TRUE(deserialized.has_value()); + + EXPECT_TRUE(DoEqualityComparisons(opaque, deserialized.value(), true)); + EXPECT_EQ(opaque.GetDebugString(), deserialized.value().GetDebugString()); +} + } // namespace url diff --git a/chromium/url/scheme_host_port.cc b/chromium/url/scheme_host_port.cc index 18b0e8cd607..96d491267b4 100644 --- a/chromium/url/scheme_host_port.cc +++ b/chromium/url/scheme_host_port.cc @@ -134,15 +134,15 @@ SchemeHostPort::SchemeHostPort(std::string scheme, ConstructPolicy policy) : port_(0) { if (!IsValidInput(scheme, host, port, policy)) { - DCHECK(IsInvalid()); + DCHECK(!IsValid()); return; } scheme_ = std::move(scheme); host_ = std::move(host); port_ = port; - DCHECK(!IsInvalid()) << "Scheme: " << scheme_ << " Host: " << host_ - << " Port: " << port; + DCHECK(IsValid()) << "Scheme: " << scheme_ << " Host: " << host_ + << " Port: " << port; } SchemeHostPort::SchemeHostPort(base::StringPiece scheme, @@ -172,19 +172,19 @@ SchemeHostPort::SchemeHostPort(const GURL& url) : port_(0) { if (!IsValidInput(scheme, host, port, ALREADY_CANONICALIZED)) return; - scheme.CopyToString(&scheme_); - host.CopyToString(&host_); + scheme_ = std::string(scheme); + host_ = std::string(host); port_ = port; } SchemeHostPort::~SchemeHostPort() = default; -bool SchemeHostPort::IsInvalid() const { +bool SchemeHostPort::IsValid() const { // It suffices to just check |scheme_| for emptiness; the other fields are // never present without it. DCHECK(!scheme_.empty() || host_.empty()); DCHECK(!scheme_.empty() || port_ == 0); - return scheme_.empty(); + return !scheme_.empty(); } std::string SchemeHostPort::Serialize() const { @@ -198,7 +198,7 @@ GURL SchemeHostPort::GetURL() const { url::Parsed parsed; std::string serialized = SerializeInternal(&parsed); - if (IsInvalid()) + if (!IsValid()) return GURL(std::move(serialized), parsed, false); // SchemeHostPort does not have enough information to determine if an empty @@ -223,7 +223,7 @@ bool SchemeHostPort::operator<(const SchemeHostPort& other) const { std::string SchemeHostPort::SerializeInternal(url::Parsed* parsed) const { std::string result; - if (IsInvalid()) + if (!IsValid()) return result; // Reserve enough space for the "normal" case of scheme://host/. diff --git a/chromium/url/scheme_host_port.h b/chromium/url/scheme_host_port.h index cf88cb1ea90..6a438fe14e0 100644 --- a/chromium/url/scheme_host_port.h +++ b/chromium/url/scheme_host_port.h @@ -122,7 +122,7 @@ class COMPONENT_EXPORT(URL) SchemeHostPort { const std::string& host() const { return host_; } const std::string& scheme() const { return scheme_; } uint16_t port() const { return port_; } - bool IsInvalid() const; + bool IsValid() const; // Serializes the SchemeHostPort tuple to a canonical form. // diff --git a/chromium/url/scheme_host_port_unittest.cc b/chromium/url/scheme_host_port_unittest.cc index 874c100767b..2ebf7e78c9b 100644 --- a/chromium/url/scheme_host_port_unittest.cc +++ b/chromium/url/scheme_host_port_unittest.cc @@ -16,12 +16,11 @@ namespace { class SchemeHostPortTest : public testing::Test { public: SchemeHostPortTest() = default; - ~SchemeHostPortTest() override { - // Reset any added schemes. - url::ResetForTests(); - } + ~SchemeHostPortTest() override = default; private: + url::ScopedSchemeRegistryForTests scoped_registry_; + DISALLOW_COPY_AND_ASSIGN(SchemeHostPortTest); }; @@ -52,7 +51,7 @@ TEST_F(SchemeHostPortTest, Invalid) { EXPECT_EQ("", invalid.scheme()); EXPECT_EQ("", invalid.host()); EXPECT_EQ(0, invalid.port()); - EXPECT_TRUE(invalid.IsInvalid()); + EXPECT_FALSE(invalid.IsValid()); EXPECT_EQ(invalid, invalid); const char* urls[] = { @@ -76,7 +75,7 @@ TEST_F(SchemeHostPortTest, Invalid) { EXPECT_EQ("", tuple.scheme()); EXPECT_EQ("", tuple.host()); EXPECT_EQ(0, tuple.port()); - EXPECT_TRUE(tuple.IsInvalid()); + EXPECT_FALSE(tuple.IsValid()); EXPECT_EQ(tuple, tuple); EXPECT_EQ(tuple, invalid); EXPECT_EQ(invalid, tuple); @@ -105,7 +104,7 @@ TEST_F(SchemeHostPortTest, ExplicitConstruction) { EXPECT_EQ(test.scheme, tuple.scheme()); EXPECT_EQ(test.host, tuple.host()); EXPECT_EQ(test.port, tuple.port()); - EXPECT_FALSE(tuple.IsInvalid()); + EXPECT_TRUE(tuple.IsValid()); EXPECT_EQ(tuple, tuple); ExpectParsedUrlsEqual(GURL(tuple.Serialize()), tuple.GetURL()); } @@ -141,7 +140,7 @@ TEST_F(SchemeHostPortTest, InvalidConstruction) { EXPECT_EQ("", tuple.scheme()); EXPECT_EQ("", tuple.host()); EXPECT_EQ(0, tuple.port()); - EXPECT_TRUE(tuple.IsInvalid()); + EXPECT_FALSE(tuple.IsValid()); EXPECT_EQ(tuple, tuple); ExpectParsedUrlsEqual(GURL(tuple.Serialize()), tuple.GetURL()); } @@ -170,7 +169,7 @@ TEST_F(SchemeHostPortTest, InvalidConstructionWithEmbeddedNulls) { EXPECT_EQ("", tuple.scheme()); EXPECT_EQ("", tuple.host()); EXPECT_EQ(0, tuple.port()); - EXPECT_TRUE(tuple.IsInvalid()); + EXPECT_FALSE(tuple.IsValid()); ExpectParsedUrlsEqual(GURL(tuple.Serialize()), tuple.GetURL()); } } @@ -205,7 +204,7 @@ TEST_F(SchemeHostPortTest, GURLConstruction) { EXPECT_EQ(test.scheme, tuple.scheme()); EXPECT_EQ(test.host, tuple.host()); EXPECT_EQ(test.port, tuple.port()); - EXPECT_FALSE(tuple.IsInvalid()); + EXPECT_TRUE(tuple.IsValid()); EXPECT_EQ(tuple, tuple); ExpectParsedUrlsEqual(GURL(tuple.Serialize()), tuple.GetURL()); } diff --git a/chromium/url/url_canon_relative.cc b/chromium/url/url_canon_relative.cc index 607392897a3..de47861d498 100644 --- a/chromium/url/url_canon_relative.cc +++ b/chromium/url/url_canon_relative.cc @@ -7,6 +7,7 @@ #include <algorithm> #include "base/logging.h" +#include "base/strings/string_util.h" #include "url/url_canon.h" #include "url/url_canon_internal.h" #include "url/url_constants.h" @@ -62,6 +63,39 @@ bool DoesBeginSlashWindowsDriveSpec(const CHAR* spec, int start_offset, #endif // WIN32 +template <typename CHAR> +bool IsValidScheme(const CHAR* url, const Component& scheme) { + // Caller should ensure that the |scheme| is not empty. + DCHECK_NE(0, scheme.len); + + // From https://url.spec.whatwg.org/#scheme-start-state: + // scheme start state: + // 1. If c is an ASCII alpha, append c, lowercased, to buffer, and set + // state to scheme state. + // 2. Otherwise, if state override is not given, set state to no scheme + // state, and decrease pointer by one. + // 3. Otherwise, validation error, return failure. + // Note that both step 2 and step 3 mean that the scheme was not valid. + if (!base::IsAsciiAlpha(url[scheme.begin])) + return false; + + // From https://url.spec.whatwg.org/#scheme-state: + // scheme state: + // 1. If c is an ASCII alphanumeric, U+002B (+), U+002D (-), or U+002E + // (.), append c, lowercased, to buffer. + // 2. Otherwise, if c is U+003A (:), then [...] + // + // We begin at |scheme.begin + 1|, because the character at |scheme.begin| has + // already been checked by base::IsAsciiAlpha above. + int scheme_end = scheme.end(); + for (int i = scheme.begin + 1; i < scheme_end; i++) { + if (!CanonicalSchemeChar(url[i])) + return false; + } + + return true; +} + // See IsRelativeURL in the header file for usage. template<typename CHAR> bool DoIsRelativeURL(const char* base, @@ -126,17 +160,14 @@ bool DoIsRelativeURL(const char* base, } // If the scheme isn't valid, then it's relative. - int scheme_end = scheme.end(); - for (int i = scheme.begin; i < scheme_end; i++) { - if (!CanonicalSchemeChar(url[i])) { - if (!is_base_hierarchical) { - // Don't allow relative URLs if the base scheme doesn't support it. - return false; - } - *relative_component = MakeRange(begin, url_len); - *is_relative = true; - return true; + if (!IsValidScheme(url, scheme)) { + if (!is_base_hierarchical) { + // Don't allow relative URLs if the base scheme doesn't support it. + return false; } + *relative_component = MakeRange(begin, url_len); + *is_relative = true; + return true; } // If the scheme is not the same, then we can't count it as relative. diff --git a/chromium/url/url_util.cc b/chromium/url/url_util.cc index cafe468fad7..93a106328c3 100644 --- a/chromium/url/url_util.cc +++ b/chromium/url/url_util.cc @@ -6,8 +6,9 @@ #include <stddef.h> #include <string.h> +#include <atomic> -#include "base/debug/leak_annotations.h" +#include "base/compiler_specific.h" #include "base/logging.h" #include "base/no_destructor.h" #include "base/stl_util.h" @@ -21,6 +22,12 @@ namespace url { namespace { +// A pair for representing a standard scheme name and the SchemeType for it. +struct SchemeWithType { + std::string scheme; + SchemeType type; +}; + // List of currently registered schemes and associated properties. struct SchemeRegistry { // Standard format schemes (see header for details). @@ -90,11 +97,26 @@ struct SchemeRegistry { bool allow_non_standard_schemes = false; }; -SchemeRegistry* GetSchemeRegistry() { +// See the LockSchemeRegistries declaration in the header. +bool scheme_registries_locked = false; + +// Ensure that the schemes aren't modified after first use. +static std::atomic<bool> g_scheme_registries_used{false}; + +// Gets the scheme registry without locking the schemes. This should *only* be +// used for adding schemes to the registry. +SchemeRegistry* GetSchemeRegistryWithoutLocking() { static base::NoDestructor<SchemeRegistry> registry; return registry.get(); } +const SchemeRegistry& GetSchemeRegistry() { +#if DCHECK_IS_ON() + g_scheme_registries_used.store(true); +#endif + return *GetSchemeRegistryWithoutLocking(); +} + // Pass this enum through for methods which would like to know if whitespace // removal is necessary. enum WhitespaceRemovalPolicy { @@ -102,9 +124,6 @@ enum WhitespaceRemovalPolicy { DO_NOT_REMOVE_WHITESPACE, }; -// See the LockSchemeRegistries declaration in the header. -bool scheme_registries_locked = false; - // This template converts a given character type to the corresponding // StringPiece type. template<typename CHAR> struct CharToStringPiece { @@ -154,7 +173,7 @@ bool DoIsInSchemes(const CHAR* spec, template<typename CHAR> bool DoIsStandard(const CHAR* spec, const Component& scheme, SchemeType* type) { return DoIsInSchemes(spec, scheme, type, - GetSchemeRegistry()->standard_schemes); + GetSchemeRegistry().standard_schemes); } @@ -165,7 +184,7 @@ bool DoFindAndCompareScheme(const CHAR* str, Component* found_scheme) { // Before extracting scheme, canonicalize the URL to remove any whitespace. // This matches the canonicalization done in DoCanonicalize function. - RawCanonOutputT<CHAR> whitespace_buffer; + STACK_UNINITIALIZED RawCanonOutputT<CHAR> whitespace_buffer; int spec_len; const CHAR* spec = RemoveURLWhitespace(str, str_len, &whitespace_buffer, &spec_len, nullptr); @@ -194,7 +213,7 @@ bool DoCanonicalize(const CHAR* spec, // Remove any whitespace from the middle of the relative URL if necessary. // Possibly this will result in copying to the new buffer. - RawCanonOutputT<CHAR> whitespace_buffer; + STACK_UNINITIALIZED RawCanonOutputT<CHAR> whitespace_buffer; if (whitespace_policy == REMOVE_WHITESPACE) { spec = RemoveURLWhitespace(spec, spec_len, &whitespace_buffer, &spec_len, &output_parsed->potentially_dangling_markup); @@ -273,7 +292,7 @@ bool DoResolveRelative(const char* base_spec, Parsed* output_parsed) { // Remove any whitespace from the middle of the relative URL, possibly // copying to the new buffer. - RawCanonOutputT<CHAR> whitespace_buffer; + STACK_UNINITIALIZED RawCanonOutputT<CHAR> whitespace_buffer; int relative_length; const CHAR* relative = RemoveURLWhitespace( in_relative, in_relative_length, &whitespace_buffer, &relative_length, @@ -314,7 +333,7 @@ bool DoResolveRelative(const char* base_spec, Parsed base_parsed_authority; ParseStandardURL(base_spec, base_spec_len, &base_parsed_authority); if (base_parsed_authority.host.is_nonempty()) { - RawCanonOutputT<char> temporary_output; + STACK_UNINITIALIZED RawCanonOutputT<char> temporary_output; bool did_resolve_succeed = ResolveRelativeURL(base_spec, base_parsed_authority, false, relative, relative_component, charset_converter, @@ -366,7 +385,7 @@ bool DoReplaceComponents(const char* spec, if (replacements.IsSchemeOverridden()) { // Canonicalize the new scheme so it is 8-bit and can be concatenated with // the existing spec. - RawCanonOutput<128> scheme_replaced; + STACK_UNINITIALIZED RawCanonOutput<128> scheme_replaced; Component scheme_replaced_parsed; CanonicalizeScheme(replacements.sources().scheme, replacements.components().scheme, @@ -383,7 +402,7 @@ bool DoReplaceComponents(const char* spec, // We now need to completely re-parse the resulting string since its meaning // may have changed with the different scheme. - RawCanonOutput<128> recanonicalized; + STACK_UNINITIALIZED RawCanonOutput<128> recanonicalized; Parsed recanonicalized_parsed; DoCanonicalize(scheme_replaced.data(), scheme_replaced.length(), true, REMOVE_WHITESPACE, charset_converter, &recanonicalized, @@ -438,8 +457,16 @@ bool DoReplaceComponents(const char* spec, return ReplacePathURL(spec, parsed, replacements, output, out_parsed); } -void DoAddScheme(const char* new_scheme, std::vector<std::string>* schemes) { - DCHECK(schemes); +void DoSchemeModificationPreamble() { + // If this assert triggers, it means you've called Add*Scheme after + // the SchemeRegistry has been used. + // + // This normally means you're trying to set up a new scheme too late or using + // the SchemeRegistry too early in your application's init process. Make sure + // that you haven't added any static GURL initializers in tests. + DCHECK(!g_scheme_registries_used.load()) + << "Trying to add a scheme after the lists have been used."; + // If this assert triggers, it means you've called Add*Scheme after // LockSchemeRegistries has been called (see the header file for // LockSchemeRegistries for more). @@ -449,122 +476,145 @@ void DoAddScheme(const char* new_scheme, std::vector<std::string>* schemes) { // and calls LockSchemeRegistries, and add your new scheme there. DCHECK(!scheme_registries_locked) << "Trying to add a scheme after the lists have been locked."; +} - size_t scheme_len = strlen(new_scheme); - if (scheme_len == 0) - return; - +void DoAddScheme(const char* new_scheme, std::vector<std::string>* schemes) { + DoSchemeModificationPreamble(); + DCHECK(schemes); + DCHECK(strlen(new_scheme) > 0); DCHECK_EQ(base::ToLowerASCII(new_scheme), new_scheme); - schemes->push_back(std::string(new_scheme)); + DCHECK(std::find(schemes->begin(), schemes->end(), new_scheme) == + schemes->end()); + schemes->push_back(new_scheme); } void DoAddSchemeWithType(const char* new_scheme, SchemeType type, std::vector<SchemeWithType>* schemes) { + DoSchemeModificationPreamble(); DCHECK(schemes); - // If this assert triggers, it means you've called Add*Scheme after - // LockSchemeRegistries has been called (see the header file for - // LockSchemeRegistries for more). - // - // This normally means you're trying to set up a new scheme too late in your - // application's init process. Locate where your app does this initialization - // and calls LockSchemeRegistries, and add your new scheme there. - DCHECK(!scheme_registries_locked) - << "Trying to add a scheme after the lists have been locked."; - - size_t scheme_len = strlen(new_scheme); - if (scheme_len == 0) - return; - + DCHECK(strlen(new_scheme) > 0); DCHECK_EQ(base::ToLowerASCII(new_scheme), new_scheme); - // Duplicate the scheme into a new buffer and add it to the list of standard - // schemes. This pointer will be leaked on shutdown. - char* dup_scheme = new char[scheme_len + 1]; - ANNOTATE_LEAKING_OBJECT_PTR(dup_scheme); - memcpy(dup_scheme, new_scheme, scheme_len + 1); - - SchemeWithType scheme_with_type; - scheme_with_type.scheme = dup_scheme; - scheme_with_type.type = type; - schemes->push_back(scheme_with_type); + DCHECK(std::find_if(schemes->begin(), schemes->end(), + [&new_scheme](const SchemeWithType& scheme) { + return scheme.scheme == new_scheme; + }) == schemes->end()); + schemes->push_back({new_scheme, type}); } } // namespace -void ResetForTests() { - *GetSchemeRegistry() = SchemeRegistry(); -} +void ClearSchemesForTests() { + DCHECK(!g_scheme_registries_used.load()) + << "Schemes already used " + << "(use ScopedSchemeRegistryForTests to relax for tests)."; + DCHECK(!scheme_registries_locked) + << "Schemes already locked " + << "(use ScopedSchemeRegistryForTests to relax for tests)."; + *GetSchemeRegistryWithoutLocking() = SchemeRegistry(); +} + +class ScopedSchemeRegistryInternal { + public: + ScopedSchemeRegistryInternal() + : registry_(std::make_unique<SchemeRegistry>( + *GetSchemeRegistryWithoutLocking())) { + g_scheme_registries_used.store(false); + scheme_registries_locked = false; + } + ~ScopedSchemeRegistryInternal() { + *GetSchemeRegistryWithoutLocking() = *registry_; + g_scheme_registries_used.store(true); + scheme_registries_locked = true; + } + + private: + std::unique_ptr<SchemeRegistry> registry_; +}; + +ScopedSchemeRegistryForTests::ScopedSchemeRegistryForTests() + : internal_(std::make_unique<ScopedSchemeRegistryInternal>()) {} + +ScopedSchemeRegistryForTests::~ScopedSchemeRegistryForTests() = default; void EnableNonStandardSchemesForAndroidWebView() { - GetSchemeRegistry()->allow_non_standard_schemes = true; + DoSchemeModificationPreamble(); + GetSchemeRegistryWithoutLocking()->allow_non_standard_schemes = true; } bool AllowNonStandardSchemesForAndroidWebView() { - return GetSchemeRegistry()->allow_non_standard_schemes; + return GetSchemeRegistry().allow_non_standard_schemes; } void AddStandardScheme(const char* new_scheme, SchemeType type) { - DoAddSchemeWithType(new_scheme, type, &GetSchemeRegistry()->standard_schemes); + DoAddSchemeWithType(new_scheme, type, + &GetSchemeRegistryWithoutLocking()->standard_schemes); } void AddReferrerScheme(const char* new_scheme, SchemeType type) { - DoAddSchemeWithType(new_scheme, type, &GetSchemeRegistry()->referrer_schemes); + DoAddSchemeWithType(new_scheme, type, + &GetSchemeRegistryWithoutLocking()->referrer_schemes); } void AddSecureScheme(const char* new_scheme) { - DoAddScheme(new_scheme, &GetSchemeRegistry()->secure_schemes); + DoAddScheme(new_scheme, &GetSchemeRegistryWithoutLocking()->secure_schemes); } const std::vector<std::string>& GetSecureSchemes() { - return GetSchemeRegistry()->secure_schemes; + return GetSchemeRegistry().secure_schemes; } void AddLocalScheme(const char* new_scheme) { - DoAddScheme(new_scheme, &GetSchemeRegistry()->local_schemes); + DoAddScheme(new_scheme, &GetSchemeRegistryWithoutLocking()->local_schemes); } const std::vector<std::string>& GetLocalSchemes() { - return GetSchemeRegistry()->local_schemes; + return GetSchemeRegistry().local_schemes; } void AddNoAccessScheme(const char* new_scheme) { - DoAddScheme(new_scheme, &GetSchemeRegistry()->no_access_schemes); + DoAddScheme(new_scheme, + &GetSchemeRegistryWithoutLocking()->no_access_schemes); } const std::vector<std::string>& GetNoAccessSchemes() { - return GetSchemeRegistry()->no_access_schemes; + return GetSchemeRegistry().no_access_schemes; } void AddCorsEnabledScheme(const char* new_scheme) { - DoAddScheme(new_scheme, &GetSchemeRegistry()->cors_enabled_schemes); + DoAddScheme(new_scheme, + &GetSchemeRegistryWithoutLocking()->cors_enabled_schemes); } const std::vector<std::string>& GetCorsEnabledSchemes() { - return GetSchemeRegistry()->cors_enabled_schemes; + return GetSchemeRegistry().cors_enabled_schemes; } void AddWebStorageScheme(const char* new_scheme) { - DoAddScheme(new_scheme, &GetSchemeRegistry()->web_storage_schemes); + DoAddScheme(new_scheme, + &GetSchemeRegistryWithoutLocking()->web_storage_schemes); } const std::vector<std::string>& GetWebStorageSchemes() { - return GetSchemeRegistry()->web_storage_schemes; + return GetSchemeRegistry().web_storage_schemes; } void AddCSPBypassingScheme(const char* new_scheme) { - DoAddScheme(new_scheme, &GetSchemeRegistry()->csp_bypassing_schemes); + DoAddScheme(new_scheme, + &GetSchemeRegistryWithoutLocking()->csp_bypassing_schemes); } const std::vector<std::string>& GetCSPBypassingSchemes() { - return GetSchemeRegistry()->csp_bypassing_schemes; + return GetSchemeRegistry().csp_bypassing_schemes; } void AddEmptyDocumentScheme(const char* new_scheme) { - DoAddScheme(new_scheme, &GetSchemeRegistry()->empty_document_schemes); + DoAddScheme(new_scheme, + &GetSchemeRegistryWithoutLocking()->empty_document_schemes); } const std::vector<std::string>& GetEmptyDocumentSchemes() { - return GetSchemeRegistry()->empty_document_schemes; + return GetSchemeRegistry().empty_document_schemes; } void LockSchemeRegistries() { @@ -596,7 +646,7 @@ bool IsStandard(const base::char16* spec, const Component& scheme) { bool IsReferrerScheme(const char* spec, const Component& scheme) { SchemeType unused_scheme_type; return DoIsInSchemes(spec, scheme, &unused_scheme_type, - GetSchemeRegistry()->referrer_schemes); + GetSchemeRegistry().referrer_schemes); } bool FindAndCompareScheme(const char* str, @@ -650,7 +700,7 @@ bool DomainIs(base::StringPiece canonical_host, } bool HostIsIPAddress(base::StringPiece host) { - url::RawCanonOutputT<char, 128> ignored_output; + STACK_UNINITIALIZED url::RawCanonOutputT<char, 128> ignored_output; url::CanonHostInfo host_info; url::CanonicalizeIPAddress(host.data(), Component(0, host.length()), &ignored_output, &host_info); @@ -729,7 +779,7 @@ void DecodeURLEscapeSequences(const char* input, int length, DecodeURLMode mode, CanonOutputW* output) { - RawCanonOutputT<char> unescaped_chars; + STACK_UNINITIALIZED RawCanonOutputT<char> unescaped_chars; for (int i = 0; i < length; i++) { if (input[i] == '%') { unsigned char ch; diff --git a/chromium/url/url_util.h b/chromium/url/url_util.h index 3ea449b1b60..d4f5e1798dd 100644 --- a/chromium/url/url_util.h +++ b/chromium/url/url_util.h @@ -5,6 +5,7 @@ #ifndef URL_URL_UTIL_H_ #define URL_URL_UTIL_H_ +#include <memory> #include <string> #include <vector> @@ -19,8 +20,22 @@ namespace url { // Init ------------------------------------------------------------------------ -// Resets all custom schemes to the default values. Not thread-safe. -COMPONENT_EXPORT(URL) void ResetForTests(); +// Used for tests that need to reset schemes. Note that this can only be used +// in conjunction with ScopedSchemeRegistryForTests. +COMPONENT_EXPORT(URL) void ClearSchemesForTests(); + +class ScopedSchemeRegistryInternal; + +// Stores the SchemeRegistry upon creation, allowing tests to modify a copy of +// it, and restores the original SchemeRegistry when deleted. +class COMPONENT_EXPORT(URL) ScopedSchemeRegistryForTests { + public: + ScopedSchemeRegistryForTests(); + ~ScopedSchemeRegistryForTests(); + + private: + std::unique_ptr<ScopedSchemeRegistryInternal> internal_; +}; // Schemes --------------------------------------------------------------------- @@ -37,15 +52,9 @@ COMPONENT_EXPORT(URL) void EnableNonStandardSchemesForAndroidWebView(); // Whether or not SchemeHostPort and Origin allow non-standard schemes. COMPONENT_EXPORT(URL) bool AllowNonStandardSchemesForAndroidWebView(); -// A pair for representing a standard scheme name and the SchemeType for it. -struct COMPONENT_EXPORT(URL) SchemeWithType { - const char* scheme; - SchemeType type; -}; - // The following Add*Scheme method are not threadsafe and can not be called // concurrently with any other url_util function. They will assert if the lists -// of schemes have been locked (see LockSchemeRegistries). +// of schemes have been locked (see LockSchemeRegistries), or used. // Adds an application-defined scheme to the internal list of "standard-format" // URL schemes. A standard-format scheme adheres to what RFC 3986 calls "generic diff --git a/chromium/url/url_util_unittest.cc b/chromium/url/url_util_unittest.cc index aed5fa899ac..ea4cd82aa7a 100644 --- a/chromium/url/url_util_unittest.cc +++ b/chromium/url/url_util_unittest.cc @@ -17,12 +17,11 @@ namespace url { class URLUtilTest : public testing::Test { public: URLUtilTest() = default; - ~URLUtilTest() override { - // Reset any added schemes. - ResetForTests(); - } + ~URLUtilTest() override = default; private: + ScopedSchemeRegistryForTests scoped_registry_; + DISALLOW_COPY_AND_ASSIGN(URLUtilTest); }; @@ -92,21 +91,24 @@ TEST_F(URLUtilTest, IsReferrerScheme) { } TEST_F(URLUtilTest, AddReferrerScheme) { - const char kFooScheme[] = "foo"; + static const char kFooScheme[] = "foo"; EXPECT_FALSE(IsReferrerScheme(kFooScheme, Component(0, strlen(kFooScheme)))); + url::ScopedSchemeRegistryForTests scoped_registry; AddReferrerScheme(kFooScheme, url::SCHEME_WITH_HOST); EXPECT_TRUE(IsReferrerScheme(kFooScheme, Component(0, strlen(kFooScheme)))); } TEST_F(URLUtilTest, ShutdownCleansUpSchemes) { - const char kFooScheme[] = "foo"; + static const char kFooScheme[] = "foo"; EXPECT_FALSE(IsReferrerScheme(kFooScheme, Component(0, strlen(kFooScheme)))); - AddReferrerScheme(kFooScheme, url::SCHEME_WITH_HOST); - EXPECT_TRUE(IsReferrerScheme(kFooScheme, Component(0, strlen(kFooScheme)))); + { + url::ScopedSchemeRegistryForTests scoped_registry; + AddReferrerScheme(kFooScheme, url::SCHEME_WITH_HOST); + EXPECT_TRUE(IsReferrerScheme(kFooScheme, Component(0, strlen(kFooScheme)))); + } - ResetForTests(); EXPECT_FALSE(IsReferrerScheme(kFooScheme, Component(0, strlen(kFooScheme)))); } |