text: // Copyright (c) 2011 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 <set>
  #include <string>
  
  #include "base/memory/ref_counted.h"
  #include "base/memory/scoped_ptr.h"
  #include "base/strings/string_util.h"
  #include "net/base/net_errors.h"
  #include "net/dns/mock_host_resolver.h"
  #include "net/http/http_auth.h"
  #include "net/http/http_auth_challenge_tokenizer.h"
  #include "net/http/http_auth_filter.h"
  #include "net/http/http_auth_handler.h"
  #include "net/http/http_auth_handler_factory.h"
  #include "net/http/http_auth_handler_mock.h"
  #include "net/http/http_auth_scheme.h"
  #include "net/http/http_response_headers.h"
  #include "net/http/http_util.h"
  #include "net/http/mock_allow_http_auth_preferences.h"
  #include "net/ssl/ssl_info.h"
  #include "testing/gtest/include/gtest/gtest.h"
  
  namespace net {
  
  namespace {
  
  HttpAuthHandlerMock* CreateMockHandler(bool connection_based) {
    HttpAuthHandlerMock* auth_handler = new HttpAuthHandlerMock();
    auth_handler->set_connection_based(connection_based);
    std::string challenge_text = "Basic";
    HttpAuthChallengeTokenizer challenge(challenge_text.begin(),
                                           challenge_text.end());
    GURL origin("www.example.com");
    SSLInfo null_ssl_info;
    EXPECT_TRUE(auth_handler->InitFromChallenge(
        &challenge, HttpAuth::AUTH_SERVER, null_ssl_info, origin, BoundNetLog()));
    return auth_handler;
  }
  
  HttpResponseHeaders* HeadersFromResponseText(const std::string& response) {
    return new HttpResponseHeaders(
        HttpUtil::AssembleRawHeaders(response.c_str(), response.length()));
  }
  
  HttpAuth::AuthorizationResult HandleChallengeResponse(
      bool connection_based,
      const std::string& headers_text,
      std::string* challenge_used) {
    scoped_ptr<HttpAuthHandlerMock> mock_handler(
        CreateMockHandler(connection_based));
    std::set<HttpAuth::Scheme> disabled_schemes;
    scoped_refptr<HttpResponseHeaders> headers(
        HeadersFromResponseText(headers_text));
    return HttpAuth::HandleChallengeResponse(mock_handler.get(), *headers,
                                             HttpAuth::AUTH_SERVER,
                                             disabled_schemes, challenge_used);
  }
  
  }  // namespace
  
  TEST(HttpAuthTest, ChooseBestChallenge) {
    static const struct {
      const char* headers;
      HttpAuth::Scheme challenge_scheme;
      const char* challenge_realm;
    } tests[] = {
        {
         // Basic is the only challenge type, pick it.
         "Y: Digest realm=\"X\", nonce=\"aaaaaaaaaa\"\n"
         "www-authenticate: Basic realm=\"BasicRealm\"\n",
  
         HttpAuth::AUTH_SCHEME_BASIC,
         "BasicRealm",
        },
        {
         // Fake is the only challenge type, but it is unsupported.
         "Y: Digest realm=\"FooBar\", nonce=\"aaaaaaaaaa\"\n"
         "www-authenticate: Fake realm=\"FooBar\"\n",
  
         HttpAuth::AUTH_SCHEME_MAX,
         "",
        },
        {
         // Pick Digest over Basic.
         "www-authenticate: Basic realm=\"FooBar\"\n"
         "www-authenticate: Fake realm=\"FooBar\"\n"
         "www-authenticate: nonce=\"aaaaaaaaaa\"\n"
         "www-authenticate: Digest realm=\"DigestRealm\", nonce=\"aaaaaaaaaa\"\n",
  
         HttpAuth::AUTH_SCHEME_DIGEST,
         "DigestRealm",
        },
        {
         // Handle an empty header correctly.
         "Y: Digest realm=\"X\", nonce=\"aaaaaaaaaa\"\n"
         "www-authenticate:\n",
  
         HttpAuth::AUTH_SCHEME_MAX,
         "",
        },
        {
         "WWW-Authenticate: Negotiate\n"
         "WWW-Authenticate: NTLM\n",
  
  #if defined(USE_KERBEROS) && !defined(OS_ANDROID)
         // Choose Negotiate over NTLM on all platforms.
         // TODO(ahendrickson): This may be flaky on Linux and OSX as it
         // relies on being able to load one of the known .so files
         // for gssapi.
         HttpAuth::AUTH_SCHEME_NEGOTIATE,
  #else
         // On systems that don't use Kerberos fall back to NTLM.
         HttpAuth::AUTH_SCHEME_NTLM,
  #endif  // defined(USE_KERBEROS)
         "",
        }};
    GURL origin("http://www.example.com");
    std::set<HttpAuth::Scheme> disabled_schemes;
    MockAllowHttpAuthPreferences http_auth_preferences;
    scoped_ptr<HostResolver> host_resolver(new MockHostResolver());
    scoped_ptr<HttpAuthHandlerRegistryFactory> http_auth_handler_factory(
        HttpAuthHandlerFactory::CreateDefault(host_resolver.get()));
    http_auth_handler_factory->SetHttpAuthPreferences(kNegotiateAuthScheme,
                                                      &http_auth_preferences);
  
    for (size_t i = 0; i < arraysize(tests); ++i) {
      // Make a HttpResponseHeaders object.
      std::string headers_with_status_line("HTTP/1.1 401 Unauthorized\n");
      headers_with_status_line += tests[i].headers;
      scoped_refptr<HttpResponseHeaders> headers(
          HeadersFromResponseText(headers_with_status_line));
  
      SSLInfo null_ssl_info;
      scoped_ptr<HttpAuthHandler> handler;
      HttpAuth::ChooseBestChallenge(http_auth_handler_factory.get(), *headers,
                                    null_ssl_info, HttpAuth::AUTH_SERVER, origin,
                                    disabled_schemes, BoundNetLog(), &handler);
  
      if (handler.get()) {
        EXPECT_EQ(tests[i].challenge_scheme, handler->auth_scheme());
        EXPECT_STREQ(tests[i].challenge_realm, handler->realm().c_str());
      } else {
        EXPECT_EQ(HttpAuth::AUTH_SCHEME_MAX, tests[i].challenge_scheme);
        EXPECT_STREQ("", tests[i].challenge_realm);
      }
    }
  }
  
  TEST(HttpAuthTest, HandleChallengeResponse) {
    std::string challenge_used;
    const char* const kMockChallenge =
        "HTTP/1.1 401 Unauthorized\n"
        "WWW-Authenticate: Mock token_here\n";
    const char* const kBasicChallenge =
        "HTTP/1.1 401 Unauthorized\n"
        "WWW-Authenticate: Basic realm=\"happy\"\n";
    const char* const kMissingChallenge =
        "HTTP/1.1 401 Unauthorized\n";
    const char* const kEmptyChallenge =
        "HTTP/1.1 401 Unauthorized\n"
        "WWW-Authenticate: \n";
    const char* const kBasicAndMockChallenges =
        "HTTP/1.1 401 Unauthorized\n"
        "WWW-Authenticate: Basic realm=\"happy\"\n"
        "WWW-Authenticate: Mock token_here\n";
    const char* const kTwoMockChallenges =
        "HTTP/1.1 401 Unauthorized\n"
        "WWW-Authenticate: Mock token_a\n"
        "WWW-Authenticate: Mock token_b\n";
  
    // Request based schemes should treat any new challenges as rejections of the
    // previous authentication attempt. (There is a slight exception for digest
    // authentication and the stale parameter, but that is covered in the
    // http_auth_handler_digest_unittests).
    EXPECT_EQ(
        HttpAuth::AUTHORIZATION_RESULT_REJECT,
        HandleChallengeResponse(false, kMockChallenge, &challenge_used));
    EXPECT_EQ("Mock token_here", challenge_used);
  
    EXPECT_EQ(
        HttpAuth::AUTHORIZATION_RESULT_REJECT,
        HandleChallengeResponse(false, kBasicChallenge, &challenge_used));
    EXPECT_EQ("", challenge_used);
  
    EXPECT_EQ(
        HttpAuth::AUTHORIZATION_RESULT_REJECT,
        HandleChallengeResponse(false, kMissingChallenge, &challenge_used));
    EXPECT_EQ("", challenge_used);
  
    EXPECT_EQ(
        HttpAuth::AUTHORIZATION_RESULT_REJECT,
        HandleChallengeResponse(false, kEmptyChallenge, &challenge_used));
    EXPECT_EQ("", challenge_used);
  
    EXPECT_EQ(
        HttpAuth::AUTHORIZATION_RESULT_REJECT,
        HandleChallengeResponse(false, kBasicAndMockChallenges, &challenge_used));
    EXPECT_EQ("Mock token_here", challenge_used);
  
    EXPECT_EQ(
        HttpAuth::AUTHORIZATION_RESULT_REJECT,
        HandleChallengeResponse(false, kTwoMockChallenges, &challenge_used));
    EXPECT_EQ("Mock token_a", challenge_used);
  
    // Connection based schemes will treat new auth challenges for the same scheme
    // as acceptance (and continuance) of the current approach. If there are
    // no auth challenges for the same scheme, the response will be treated as
    // a rejection.
    EXPECT_EQ(
        HttpAuth::AUTHORIZATION_RESULT_ACCEPT,
        HandleChallengeResponse(true, kMockChallenge, &challenge_used));
    EXPECT_EQ("Mock token_here", challenge_used);
  
    EXPECT_EQ(
        HttpAuth::AUTHORIZATION_RESULT_REJECT,
        HandleChallengeResponse(true, kBasicChallenge, &challenge_used));
    EXPECT_EQ("", challenge_used);
  
    EXPECT_EQ(
        HttpAuth::AUTHORIZATION_RESULT_REJECT,
        HandleChallengeResponse(true, kMissingChallenge, &challenge_used));
    EXPECT_EQ("", challenge_used);
  
    EXPECT_EQ(
        HttpAuth::AUTHORIZATION_RESULT_REJECT,
        HandleChallengeResponse(true, kEmptyChallenge, &challenge_used));
    EXPECT_EQ("", challenge_used);
  
    EXPECT_EQ(
        HttpAuth::AUTHORIZATION_RESULT_ACCEPT,
        HandleChallengeResponse(true, kBasicAndMockChallenges, &challenge_used));
    EXPECT_EQ("Mock token_here", challenge_used);
  
    EXPECT_EQ(
        HttpAuth::AUTHORIZATION_RESULT_ACCEPT,
        HandleChallengeResponse(true, kTwoMockChallenges, &challenge_used));
    EXPECT_EQ("Mock token_a", challenge_used);
  }
  
  TEST(HttpAuthTest, GetChallengeHeaderName) {
    std::string name;
  
    name = HttpAuth::GetChallengeHeaderName(HttpAuth::AUTH_SERVER);
    EXPECT_STREQ("WWW-Authenticate", name.c_str());
  
    name = HttpAuth::GetChallengeHeaderName(HttpAuth::AUTH_PROXY);
    EXPECT_STREQ("Proxy-Authenticate", name.c_str());
  }
  
  TEST(HttpAuthTest, GetAuthorizationHeaderName) {
    std::string name;
  
    name = HttpAuth::GetAuthorizationHeaderName(HttpAuth::AUTH_SERVER);
    EXPECT_STREQ("Authorization", name.c_str());
  
    name = HttpAuth::GetAuthorizationHeaderName(HttpAuth::AUTH_PROXY);
    EXPECT_STREQ("Proxy-Authorization", name.c_str());
  }
  
  }  // namespace net
  