chromium/ios/web/common/referrer_util_unittest.cc

// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ios/web/common/referrer_util.h"

#include "ios/web/public/navigation/referrer.h"
#include "net/url_request/referrer_policy.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/platform_test.h"
#include "url/gurl.h"

namespace {

const char* const kTestUrls[] = {
    "",
    "http://user:[email protected]/a/b/c.html",
    "https://foo.com/d.html#fragment",
    "http://user:[email protected]/e/f.html#fragment",
    "https://user:[email protected]/g/h.html",
};

}  // namespace

namespace web {

using ReferrerUtilTest = PlatformTest;

// Tests that no matter what the transition and policy, the result is always
// stripped of things that should not be in a referrer (e.g., passwords).
TEST_F(ReferrerUtilTest, ReferrerSanitization) {
  for (unsigned int source = 0; source < std::size(kTestUrls); ++source) {
    for (unsigned int dest = 0; dest < std::size(kTestUrls); ++dest) {
      for (unsigned int policy = 0; policy <= ReferrerPolicyLast; ++policy) {
        Referrer referrer(GURL(kTestUrls[source]),
                          static_cast<ReferrerPolicy>(policy));
        std::string value =
            ReferrerHeaderValueForNavigation(GURL(kTestUrls[dest]), referrer);

        EXPECT_EQ(GURL(value).GetAsReferrer().spec(), value);
      }
    }
  }
}

// Tests that the Always policy works as expected.
TEST_F(ReferrerUtilTest, AlwaysPolicy) {
  for (unsigned int source = 0; source < std::size(kTestUrls); ++source) {
    for (unsigned int dest = 1; dest < std::size(kTestUrls); ++dest) {
      GURL source_url(kTestUrls[source]);
      GURL dest_url(kTestUrls[dest]);
      Referrer referrer(source_url, ReferrerPolicyAlways);
      std::string value = ReferrerHeaderValueForNavigation(dest_url, referrer);

      // Everything should have a full referrer.
      EXPECT_EQ(source_url.GetAsReferrer().spec(), value);
    }
  }
}

// Tests that the Default policy works as expected, and matches
// NoReferrerWhenDowngrade.
TEST_F(ReferrerUtilTest, DefaultPolicy) {
  for (unsigned int source = 0; source < std::size(kTestUrls); ++source) {
    for (unsigned int dest = 1; dest < std::size(kTestUrls); ++dest) {
      GURL source_url(kTestUrls[source]);
      GURL dest_url(kTestUrls[dest]);
      Referrer referrer(source_url, ReferrerPolicyDefault);
      std::string value = ReferrerHeaderValueForNavigation(dest_url, referrer);

      // All but secure->insecure should have a full referrer.
      if (source_url.SchemeIsCryptographic() &&
          !dest_url.SchemeIsCryptographic())
        EXPECT_EQ("", value);
      else
        EXPECT_EQ(source_url.GetAsReferrer().spec(), value);

      // Default should match NoReferrerWhenDowngrade in all cases.
      referrer.policy = ReferrerPolicyNoReferrerWhenDowngrade;
      EXPECT_EQ(value, ReferrerHeaderValueForNavigation(dest_url, referrer));
    }
  }
}

// Tests that the Never policy works as expected.
TEST_F(ReferrerUtilTest, NeverPolicy) {
  for (unsigned int source = 0; source < std::size(kTestUrls); ++source) {
    for (unsigned int dest = 1; dest < std::size(kTestUrls); ++dest) {
      GURL source_url(kTestUrls[source]);
      GURL dest_url(kTestUrls[dest]);
      Referrer referrer(source_url, ReferrerPolicyNever);
      std::string value = ReferrerHeaderValueForNavigation(dest_url, referrer);

      // No referrer in any navigation.
      EXPECT_EQ("", value);
    }
  }
}

// Tests that the Origin policy works as expected.
TEST_F(ReferrerUtilTest, OriginPolicy) {
  for (unsigned int source = 0; source < std::size(kTestUrls); ++source) {
    for (unsigned int dest = 1; dest < std::size(kTestUrls); ++dest) {
      GURL source_url(kTestUrls[source]);
      GURL dest_url(kTestUrls[dest]);
      Referrer referrer(source_url, ReferrerPolicyOrigin);
      std::string value = ReferrerHeaderValueForNavigation(dest_url, referrer);

      // Origin should be sent in all cases, even secure->insecure.
      EXPECT_EQ(source_url.DeprecatedGetOriginAsURL().spec(), value);
    }
  }
}

// Tests that the OriginWhenCrossOrigin policy works as expected.
TEST_F(ReferrerUtilTest, OriginWhenCrossOriginPolicy) {
  for (unsigned int source = 0; source < std::size(kTestUrls); ++source) {
    for (unsigned int dest = 1; dest < std::size(kTestUrls); ++dest) {
      GURL source_url(kTestUrls[source]);
      GURL dest_url(kTestUrls[dest]);
      Referrer referrer(source_url, ReferrerPolicyOriginWhenCrossOrigin);
      std::string value = ReferrerHeaderValueForNavigation(dest_url, referrer);

      // Full URL for the same origin, and origin for all other cases (even
      // secure->insecure).
      if (source_url.DeprecatedGetOriginAsURL() ==
          dest_url.DeprecatedGetOriginAsURL())
        EXPECT_EQ(source_url.GetAsReferrer().spec(), value);
      else
        EXPECT_EQ(source_url.DeprecatedGetOriginAsURL().spec(), value);
    }
  }
}

// Tests that the same-origin policy works as expected.
TEST_F(ReferrerUtilTest, SameOriginPolicy) {
  for (unsigned int source = 0; source < std::size(kTestUrls); ++source) {
    for (unsigned int dest = 1; dest < std::size(kTestUrls); ++dest) {
      GURL source_url(kTestUrls[source]);
      GURL dest_url(kTestUrls[dest]);
      Referrer referrer(source_url, ReferrerPolicySameOrigin);
      std::string value = ReferrerHeaderValueForNavigation(dest_url, referrer);

      // Full URL for the same origin, and nothing for all other cases.
      if (source_url.DeprecatedGetOriginAsURL() ==
          dest_url.DeprecatedGetOriginAsURL())
        EXPECT_EQ(source_url.GetAsReferrer().spec(), value);
      else
        EXPECT_EQ(std::string(), value);
    }
  }
}

// Tests that the strict-origin policy works as expected.
TEST_F(ReferrerUtilTest, StrictOriginPolicy) {
  for (unsigned int source = 0; source < std::size(kTestUrls); ++source) {
    for (unsigned int dest = 1; dest < std::size(kTestUrls); ++dest) {
      GURL source_url(kTestUrls[source]);
      GURL dest_url(kTestUrls[dest]);
      Referrer referrer(source_url, ReferrerPolicyStrictOrigin);
      std::string value = ReferrerHeaderValueForNavigation(dest_url, referrer);

      // No referrer when downgrading, and origin otherwise.
      if (source_url.SchemeIsCryptographic() &&
          !dest_url.SchemeIsCryptographic())
        EXPECT_EQ("", value);
      else
        EXPECT_EQ(source_url.DeprecatedGetOriginAsURL().spec(), value);
    }
  }
}

// Tests that the strict-origin-when-cross-origin policy works as expected.
TEST_F(ReferrerUtilTest, StrictOriginWhenCrossOriginPolicy) {
  for (unsigned int source = 0; source < std::size(kTestUrls); ++source) {
    for (unsigned int dest = 1; dest < std::size(kTestUrls); ++dest) {
      GURL source_url(kTestUrls[source]);
      GURL dest_url(kTestUrls[dest]);
      Referrer referrer(source_url, ReferrerPolicyStrictOriginWhenCrossOrigin);
      std::string value = ReferrerHeaderValueForNavigation(dest_url, referrer);

      // No referrer when downgrading, origin when cross-origin but not
      // downgrading, and full referrer otherwise.
      if (source_url.SchemeIsCryptographic() &&
          !dest_url.SchemeIsCryptographic())
        EXPECT_EQ("", value);
      else if (source_url.DeprecatedGetOriginAsURL() ==
               dest_url.DeprecatedGetOriginAsURL())
        EXPECT_EQ(source_url.GetAsReferrer().spec(), value);
      else
        EXPECT_EQ(source_url.DeprecatedGetOriginAsURL().spec(), value);
    }
  }
}

// Tests that PolicyForNavigation gives the right values.
TEST_F(ReferrerUtilTest, PolicyForNavigation) {
  // The request and destination URLs are unused in the current implementation,
  // so use a dummy value.
  GURL dummy_url;
  for (unsigned int policy = 0; policy <= ReferrerPolicyLast; ++policy) {
    Referrer referrer(dummy_url, static_cast<ReferrerPolicy>(policy));
    net::ReferrerPolicy net_request_policy =
        PolicyForNavigation(dummy_url, referrer);
    // The test here is deliberately backward from the way the test would
    // intuitively work so that it's structured differently from the code it's
    // testing, and thus less likely to have a copy/paste bug that passes
    // incorrect mappings.
    switch (net_request_policy) {
      case net::ReferrerPolicy::CLEAR_ON_TRANSITION_FROM_SECURE_TO_INSECURE:
        // This corresponds directly to ReferrerPolicyNoReferrerWhenDowngrade,
        // which is also how Default works on iOS.
        EXPECT_TRUE(policy == ReferrerPolicyDefault ||
                    policy == ReferrerPolicyNoReferrerWhenDowngrade);
        break;
      case net::ReferrerPolicy::REDUCE_GRANULARITY_ON_TRANSITION_CROSS_ORIGIN:
        EXPECT_EQ(ReferrerPolicyStrictOriginWhenCrossOrigin, policy);
        break;
      case net::ReferrerPolicy::ORIGIN_ONLY_ON_TRANSITION_CROSS_ORIGIN:
        EXPECT_EQ(ReferrerPolicyOriginWhenCrossOrigin, policy);
        break;
      case net::ReferrerPolicy::NEVER_CLEAR:
        EXPECT_EQ(ReferrerPolicyAlways, policy);
        break;
      case net::ReferrerPolicy::ORIGIN:
        EXPECT_EQ(ReferrerPolicyOrigin, policy);
        break;
      case net::ReferrerPolicy::CLEAR_ON_TRANSITION_CROSS_ORIGIN:
        EXPECT_EQ(ReferrerPolicySameOrigin, policy);
        break;
      case net::ReferrerPolicy::
          ORIGIN_CLEAR_ON_TRANSITION_FROM_SECURE_TO_INSECURE:
        EXPECT_EQ(ReferrerPolicyStrictOrigin, policy);
        break;
      case net::ReferrerPolicy::NO_REFERRER:
        EXPECT_EQ(ReferrerPolicyNever, policy);
        break;
    }
  }
}

// Tests that all the strings corresponding to web::ReferrerPolicy values are
// correctly handled.
TEST_F(ReferrerUtilTest, PolicyFromString) {
  // The ordering here must match web::ReferrerPolicy; this makes the test
  // simpler, at the cost of needing to re-order if the enum is re-ordered.
  const char* const kPolicyStrings[] = {
      "unsafe-url",
      nullptr,  // Default is skipped, because no string maps to Default.
      "no-referrer-when-downgrade",
      "no-referrer",
      "origin",
      "origin-when-cross-origin",
      "same-origin",
      "strict-origin",
      "strict-origin-when-cross-origin",
  };
  // Test that all the values are supported.
  for (int i = 0; i < ReferrerPolicyLast; ++i) {
    if (!kPolicyStrings[i])
      continue;
    EXPECT_EQ(i, ReferrerPolicyFromString(kPolicyStrings[i]));
  }

  // Verify that if something is added to the enum, its string value gets added
  // to the mapping function.
  EXPECT_EQ(ReferrerPolicyLast + 1,
            static_cast<int>(std::size(kPolicyStrings)));

  // Test the legacy policy names.
  EXPECT_EQ(ReferrerPolicyNever, ReferrerPolicyFromString("never"));
  // Note that per the spec, "default" maps to NoReferrerWhenDowngrade; the
  // Default enum value is not actually a spec'd value.
  EXPECT_EQ(ReferrerPolicyNoReferrerWhenDowngrade,
            ReferrerPolicyFromString("default"));
  EXPECT_EQ(ReferrerPolicyAlways, ReferrerPolicyFromString("always"));

  // Test that invalid values map to Default.
  EXPECT_EQ(ReferrerPolicyDefault, ReferrerPolicyFromString(""));
  EXPECT_EQ(ReferrerPolicyDefault, ReferrerPolicyFromString("made-up"));
}

}  // namespace web