// Copyright (c) 2008 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 "base/string_util.h"
#include "net/http/http_auth_cache.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace net {
namespace {
class MockAuthHandler : public HttpAuthHandler {
public:
MockAuthHandler(const char* scheme, const std::string& realm,
HttpAuth::Target target) {
// Can't use initializer list since these are members of the base class.
scheme_ = scheme;
realm_ = realm;
score_ = 1;
target_ = target;
properties_ = 0;
}
virtual std::string GenerateCredentials(const std::wstring&,
const std::wstring&,
const HttpRequestInfo*,
const ProxyInfo*) {
return "mock-credentials"; // Unused.
}
protected:
virtual bool Init(std::string::const_iterator, std::string::const_iterator) {
return false; // Unused.
}
private:
~MockAuthHandler() {}
};
} // namespace
// Test adding and looking-up cache entries (both by realm and by path).
TEST(HttpAuthCacheTest, Basic) {
GURL origin("http://www.google.com");
HttpAuthCache cache;
HttpAuthCache::Entry* entry;
// Add cache entries for 3 realms: "Realm1", "Realm2", "Realm3"
scoped_refptr<HttpAuthHandler> realm1_handler =
new MockAuthHandler("basic", "Realm1", HttpAuth::AUTH_SERVER);
cache.Add(origin, realm1_handler, L"realm1-user", L"realm1-password",
"/foo/bar/index.html");
scoped_refptr<HttpAuthHandler> realm2_handler =
new MockAuthHandler("basic", "Realm2", HttpAuth::AUTH_SERVER);
cache.Add(origin, realm2_handler, L"realm2-user", L"realm2-password",
"/foo2/index.html");
scoped_refptr<HttpAuthHandler> realm3_handler =
new MockAuthHandler("basic", "Realm3", HttpAuth::AUTH_PROXY);
cache.Add(origin, realm3_handler, L"realm3-user", L"realm3-password", "");
// There is no Realm4
entry = cache.LookupByRealm(origin, "Realm4");
EXPECT_TRUE(NULL == entry);
// While Realm3 does exist, the origin scheme is wrong.
entry = cache.LookupByRealm(GURL("https://www.google.com"), "Realm3");
EXPECT_TRUE(NULL == entry);
// Valid lookup by realm.
entry = cache.LookupByRealm(GURL("http://www.google.com:80"), "Realm3");
EXPECT_FALSE(NULL == entry);
EXPECT_TRUE(entry->handler() == realm3_handler.get());
EXPECT_EQ(L"realm3-user", entry->username());
EXPECT_EQ(L"realm3-password", entry->password());
// Valid lookup by realm.
entry = cache.LookupByRealm(origin, "Realm2");
EXPECT_FALSE(NULL == entry);
EXPECT_TRUE(entry->handler() == realm2_handler.get());
EXPECT_EQ(L"realm2-user", entry->username());
EXPECT_EQ(L"realm2-password", entry->password());
// Check that subpaths are recognized.
HttpAuthCache::Entry* realm2Entry = cache.LookupByRealm(origin, "Realm2");
EXPECT_FALSE(NULL == realm2Entry);
// Positive tests:
entry = cache.LookupByPath(origin, "/foo2/index.html");
EXPECT_TRUE(realm2Entry == entry);
entry = cache.LookupByPath(origin, "/foo2/foobar.html");
EXPECT_TRUE(realm2Entry == entry);
entry = cache.LookupByPath(origin, "/foo2/bar/index.html");
EXPECT_TRUE(realm2Entry == entry);
entry = cache.LookupByPath(origin, "/foo2/");
EXPECT_TRUE(realm2Entry == entry);
// Negative tests:
entry = cache.LookupByPath(origin, "/foo2");
EXPECT_FALSE(realm2Entry == entry);
entry = cache.LookupByPath(origin, "/foo3/index.html");
EXPECT_FALSE(realm2Entry == entry);
entry = cache.LookupByPath(origin, "");
EXPECT_FALSE(realm2Entry == entry);
entry = cache.LookupByPath(origin, "/");
EXPECT_FALSE(realm2Entry == entry);
// Lookup using empty path (may be used for proxy).
entry = cache.LookupByPath(origin, "");
EXPECT_FALSE(NULL == entry);
EXPECT_TRUE(entry->handler() == realm3_handler.get());
EXPECT_EQ("Realm3", entry->realm());
}
TEST(HttpAuthCacheTest, AddPath) {
HttpAuthCache::Entry entry;
// All of these paths have a common root /1/2/2/4/5/
entry.AddPath("/1/2/3/4/5/x.txt");
entry.AddPath("/1/2/3/4/5/y.txt");
entry.AddPath("/1/2/3/4/5/z.txt");
EXPECT_EQ(1U, entry.paths_.size());
EXPECT_EQ("/1/2/3/4/5/", entry.paths_.front());
// Add a new entry (not a subpath).
entry.AddPath("/1/XXX/q");
EXPECT_EQ(2U, entry.paths_.size());
EXPECT_EQ("/1/XXX/", entry.paths_.front());
EXPECT_EQ("/1/2/3/4/5/", entry.paths_.back());
// Add containing paths of /1/2/3/4/5/ -- should swallow up the deeper paths.
entry.AddPath("/1/2/3/4/x.txt");
EXPECT_EQ(2U, entry.paths_.size());
EXPECT_EQ("/1/2/3/4/", entry.paths_.front());
EXPECT_EQ("/1/XXX/", entry.paths_.back());
entry.AddPath("/1/2/3/x");
EXPECT_EQ(2U, entry.paths_.size());
EXPECT_EQ("/1/2/3/", entry.paths_.front());
EXPECT_EQ("/1/XXX/", entry.paths_.back());
entry.AddPath("/index.html");
EXPECT_EQ(1U, entry.paths_.size());
EXPECT_EQ("/", entry.paths_.front());
}
// Calling Add when the realm entry already exists, should append that
// path.
TEST(HttpAuthCacheTest, AddToExistingEntry) {
HttpAuthCache cache;
GURL origin("http://www.foobar.com:70");
scoped_refptr<HttpAuthHandler> handler =
new MockAuthHandler("basic", "MyRealm", HttpAuth::AUTH_SERVER);
HttpAuthCache::Entry* orig_entry = cache.Add(
origin, handler, L"user1", L"password1", "/x/y/z/");
cache.Add(origin, handler, L"user2", L"password2", "/z/y/x/");
cache.Add(origin, handler, L"user3", L"password3", "/z/y");
HttpAuthCache::Entry* entry = cache.LookupByRealm(origin, "MyRealm");
EXPECT_TRUE(entry == orig_entry);
EXPECT_EQ(L"user3", entry->username());
EXPECT_EQ(L"password3", entry->password());
EXPECT_EQ(2U, entry->paths_.size());
EXPECT_EQ("/z/", entry->paths_.front());
EXPECT_EQ("/x/y/z/", entry->paths_.back());
}
TEST(HttpAuthCacheTest, Remove) {
GURL origin("http://foobar2.com");
scoped_refptr<HttpAuthHandler> realm1_handler =
new MockAuthHandler("basic", "Realm1", HttpAuth::AUTH_SERVER);
scoped_refptr<HttpAuthHandler> realm2_handler =
new MockAuthHandler("basic", "Realm2", HttpAuth::AUTH_SERVER);
scoped_refptr<HttpAuthHandler> realm3_handler =
new MockAuthHandler("basic", "Realm3", HttpAuth::AUTH_SERVER);
HttpAuthCache cache;
cache.Add(origin, realm1_handler, L"alice", L"123", "/");
cache.Add(origin, realm2_handler, L"bob", L"princess", "/");
cache.Add(origin, realm3_handler, L"admin", L"password", "/");
// Fails, because there is no realm "Realm4".
EXPECT_FALSE(cache.Remove(origin, "Realm4", L"alice", L"123"));
// Fails because the origin is wrong.
EXPECT_FALSE(cache.Remove(
GURL("http://foobar2.com:100"), "Realm1", L"alice", L"123"));
// Fails because the username is wrong.
EXPECT_FALSE(cache.Remove(origin, "Realm1", L"alice2", L"123"));
// Fails because the password is wrong.
EXPECT_FALSE(cache.Remove(origin, "Realm1", L"alice", L"1234"));
// Succeeds.
EXPECT_TRUE(cache.Remove(origin, "Realm1", L"alice", L"123"));
// Fails because we just deleted the entry!
EXPECT_FALSE(cache.Remove(origin, "Realm1", L"alice", L"123"));
}
// Test fixture class for eviction tests (contains helpers for bulk
// insertion and existence testing).
class HttpAuthCacheEvictionTest : public testing::Test {
protected:
HttpAuthCacheEvictionTest() : origin_("http://www.google.com") { }
std::string GenerateRealm(int realm_i) {
return StringPrintf("Realm %d", realm_i);
}
std::string GeneratePath(int realm_i, int path_i) {
return StringPrintf("/%d/%d/x/y", realm_i, path_i);
}
void AddRealm(int realm_i) {
AddPathToRealm(realm_i, 0);
}
void AddPathToRealm(int realm_i, int path_i) {
scoped_refptr<HttpAuthHandler> handler = new MockAuthHandler("basic",
GenerateRealm(realm_i), HttpAuth::AUTH_SERVER);
std::string path = GeneratePath(realm_i, path_i);
cache_.Add(origin_, handler, L"username", L"password", path);
}
void CheckRealmExistence(int realm_i, bool exists) {
const HttpAuthCache::Entry* entry =
cache_.LookupByRealm(origin_, GenerateRealm(realm_i));
if (exists) {
EXPECT_FALSE(entry == NULL);
EXPECT_EQ(GenerateRealm(realm_i), entry->realm());
} else {
EXPECT_TRUE(entry == NULL);
}
}
void CheckPathExistence(int realm_i, int path_i, bool exists) {
const HttpAuthCache::Entry* entry =
cache_.LookupByPath(origin_, GeneratePath(realm_i, path_i));
if (exists) {
EXPECT_FALSE(entry == NULL);
EXPECT_EQ(GenerateRealm(realm_i), entry->realm());
} else {
EXPECT_TRUE(entry == NULL);
}
}
GURL origin_;
HttpAuthCache cache_;
static const int kMaxPaths = HttpAuthCache::kMaxNumPathsPerRealmEntry;
static const int kMaxRealms = HttpAuthCache::kMaxNumRealmEntries;
};
// Add the maxinim number of realm entries to the cache. Each of these entries
// must still be retrievable. Next add three more entries -- since the cache is
// full this causes FIFO eviction of the first three entries.
TEST_F(HttpAuthCacheEvictionTest, RealmEntryEviction) {
for (int i = 0; i < kMaxRealms; ++i)
AddRealm(i);
for (int i = 0; i < kMaxRealms; ++i)
CheckRealmExistence(i, true);
for (int i = 0; i < 3; ++i)
AddRealm(i + kMaxRealms);
for (int i = 0; i < 3; ++i)
CheckRealmExistence(i, false);
for (int i = 0; i < kMaxRealms; ++i)
CheckRealmExistence(i + 3, true);
}
// Add the maximum number of paths to a single realm entry. Each of these
// paths should be retrievable. Next add 3 more paths -- since the cache is
// full this causes FIFO eviction of the first three paths.
TEST_F(HttpAuthCacheEvictionTest, RealmPathEviction) {
for (int i = 0; i < kMaxPaths; ++i)
AddPathToRealm(0, i);
for (int i = 1; i < kMaxRealms; ++i)
AddRealm(i);
for (int i = 0; i < 3; ++i)
AddPathToRealm(0, i + kMaxPaths);
for (int i = 0; i < 3; ++i)
CheckPathExistence(0, i, false);
for (int i = 0; i < kMaxPaths; ++i)
CheckPathExistence(0, i + 3, true);
for (int i = 0; i < kMaxRealms; ++i)
CheckRealmExistence(i, true);
}
} // namespace net