1// Copyright (c) 2011 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "net/http/http_auth.h"
6
7#include <algorithm>
8
9#include "base/basictypes.h"
10#include "base/strings/string_tokenizer.h"
11#include "base/strings/string_util.h"
12#include "net/base/net_errors.h"
13#include "net/http/http_auth_handler.h"
14#include "net/http/http_auth_handler_factory.h"
15#include "net/http/http_request_headers.h"
16#include "net/http/http_response_headers.h"
17#include "net/http/http_util.h"
18
19namespace net {
20
21HttpAuth::Identity::Identity() : source(IDENT_SRC_NONE), invalid(true) {}
22
23// static
24void HttpAuth::ChooseBestChallenge(
25    HttpAuthHandlerFactory* http_auth_handler_factory,
26    const HttpResponseHeaders* headers,
27    Target target,
28    const GURL& origin,
29    const std::set<Scheme>& disabled_schemes,
30    const BoundNetLog& net_log,
31    scoped_ptr<HttpAuthHandler>* handler) {
32  DCHECK(http_auth_handler_factory);
33  DCHECK(handler->get() == NULL);
34
35  // Choose the challenge whose authentication handler gives the maximum score.
36  scoped_ptr<HttpAuthHandler> best;
37  const std::string header_name = GetChallengeHeaderName(target);
38  std::string cur_challenge;
39  void* iter = NULL;
40  while (headers->EnumerateHeader(&iter, header_name, &cur_challenge)) {
41    scoped_ptr<HttpAuthHandler> cur;
42    int rv = http_auth_handler_factory->CreateAuthHandlerFromString(
43        cur_challenge, target, origin, net_log, &cur);
44    if (rv != OK) {
45      VLOG(1) << "Unable to create AuthHandler. Status: "
46              << ErrorToString(rv) << " Challenge: " << cur_challenge;
47      continue;
48    }
49    if (cur.get() && (!best.get() || best->score() < cur->score()) &&
50        (disabled_schemes.find(cur->auth_scheme()) == disabled_schemes.end()))
51      best.swap(cur);
52  }
53  handler->swap(best);
54}
55
56// static
57HttpAuth::AuthorizationResult HttpAuth::HandleChallengeResponse(
58    HttpAuthHandler* handler,
59    const HttpResponseHeaders* headers,
60    Target target,
61    const std::set<Scheme>& disabled_schemes,
62    std::string* challenge_used) {
63  DCHECK(handler);
64  DCHECK(headers);
65  DCHECK(challenge_used);
66  challenge_used->clear();
67  HttpAuth::Scheme current_scheme = handler->auth_scheme();
68  if (disabled_schemes.find(current_scheme) != disabled_schemes.end())
69    return HttpAuth::AUTHORIZATION_RESULT_REJECT;
70  std::string current_scheme_name = SchemeToString(current_scheme);
71  const std::string header_name = GetChallengeHeaderName(target);
72  void* iter = NULL;
73  std::string challenge;
74  HttpAuth::AuthorizationResult authorization_result =
75      HttpAuth::AUTHORIZATION_RESULT_INVALID;
76  while (headers->EnumerateHeader(&iter, header_name, &challenge)) {
77    HttpAuth::ChallengeTokenizer props(challenge.begin(), challenge.end());
78    if (!LowerCaseEqualsASCII(props.scheme(), current_scheme_name.c_str()))
79      continue;
80    authorization_result = handler->HandleAnotherChallenge(&props);
81    if (authorization_result != HttpAuth::AUTHORIZATION_RESULT_INVALID) {
82      *challenge_used = challenge;
83      return authorization_result;
84    }
85  }
86  // Finding no matches is equivalent to rejection.
87  return HttpAuth::AUTHORIZATION_RESULT_REJECT;
88}
89
90HttpAuth::ChallengeTokenizer::ChallengeTokenizer(
91    std::string::const_iterator begin,
92    std::string::const_iterator end)
93    : begin_(begin),
94      end_(end),
95      scheme_begin_(begin),
96      scheme_end_(begin),
97      params_begin_(end),
98      params_end_(end) {
99  Init(begin, end);
100}
101
102HttpUtil::NameValuePairsIterator HttpAuth::ChallengeTokenizer::param_pairs()
103    const {
104  return HttpUtil::NameValuePairsIterator(params_begin_, params_end_, ',');
105}
106
107std::string HttpAuth::ChallengeTokenizer::base64_param() const {
108  // Strip off any padding.
109  // (See https://bugzilla.mozilla.org/show_bug.cgi?id=230351.)
110  //
111  // Our base64 decoder requires that the length be a multiple of 4.
112  int encoded_length = params_end_ - params_begin_;
113  while (encoded_length > 0 && encoded_length % 4 != 0 &&
114         params_begin_[encoded_length - 1] == '=') {
115    --encoded_length;
116  }
117  return std::string(params_begin_, params_begin_ + encoded_length);
118}
119
120void HttpAuth::ChallengeTokenizer::Init(std::string::const_iterator begin,
121                                        std::string::const_iterator end) {
122  // The first space-separated token is the auth-scheme.
123  // NOTE: we are more permissive than RFC 2617 which says auth-scheme
124  // is separated by 1*SP.
125  base::StringTokenizer tok(begin, end, HTTP_LWS);
126  if (!tok.GetNext()) {
127    // Default param and scheme iterators provide empty strings
128    return;
129  }
130
131  // Save the scheme's position.
132  scheme_begin_ = tok.token_begin();
133  scheme_end_ = tok.token_end();
134
135  params_begin_ = scheme_end_;
136  params_end_ = end;
137  HttpUtil::TrimLWS(&params_begin_, &params_end_);
138}
139
140// static
141std::string HttpAuth::GetChallengeHeaderName(Target target) {
142  switch (target) {
143    case AUTH_PROXY:
144      return "Proxy-Authenticate";
145    case AUTH_SERVER:
146      return "WWW-Authenticate";
147    default:
148      NOTREACHED();
149      return std::string();
150  }
151}
152
153// static
154std::string HttpAuth::GetAuthorizationHeaderName(Target target) {
155  switch (target) {
156    case AUTH_PROXY:
157      return HttpRequestHeaders::kProxyAuthorization;
158    case AUTH_SERVER:
159      return HttpRequestHeaders::kAuthorization;
160    default:
161      NOTREACHED();
162      return std::string();
163  }
164}
165
166// static
167std::string HttpAuth::GetAuthTargetString(Target target) {
168  switch (target) {
169    case AUTH_PROXY:
170      return "proxy";
171    case AUTH_SERVER:
172      return "server";
173    default:
174      NOTREACHED();
175      return std::string();
176  }
177}
178
179// static
180const char* HttpAuth::SchemeToString(Scheme scheme) {
181  static const char* const kSchemeNames[] = {
182    "basic",
183    "digest",
184    "ntlm",
185    "negotiate",
186    "spdyproxy",
187    "mock",
188  };
189  COMPILE_ASSERT(arraysize(kSchemeNames) == AUTH_SCHEME_MAX,
190                 http_auth_scheme_names_incorrect_size);
191  if (scheme < AUTH_SCHEME_BASIC || scheme >= AUTH_SCHEME_MAX) {
192    NOTREACHED();
193    return "invalid_scheme";
194  }
195  return kSchemeNames[scheme];
196}
197
198}  // namespace net
199