summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--net/SConscript4
-rw-r--r--net/base/net_util.cc22
-rw-r--r--net/base/net_util.h5
-rw-r--r--net/base/net_util_unittest.cc28
-rw-r--r--net/build/net.vcproj64
-rw-r--r--net/build/net_unittests.vcproj12
-rw-r--r--net/http/http_auth.cc147
-rw-r--r--net/http/http_auth.h120
-rw-r--r--net/http/http_auth_handler.cc27
-rw-r--r--net/http/http_auth_handler.h77
-rw-r--r--net/http/http_auth_handler_basic.cc46
-rw-r--r--net/http/http_auth_handler_basic.h27
-rw-r--r--net/http/http_auth_handler_basic_unittest.cc34
-rw-r--r--net/http/http_auth_handler_digest.cc278
-rw-r--r--net/http/http_auth_handler_digest.h105
-rw-r--r--net/http/http_auth_handler_digest_unittest.cc210
-rw-r--r--net/http/http_auth_unittest.cc185
-rw-r--r--net/http/http_network_transaction.cc158
-rw-r--r--net/http/http_network_transaction.h45
-rw-r--r--net/http/http_response_headers_unittest.cc20
-rw-r--r--net/http/http_util.cc73
-rw-r--r--net/http/http_util.h17
-rw-r--r--net/http/http_util_unittest.cc31
-rw-r--r--net/net.xcodeproj/project.pbxproj24
24 files changed, 1747 insertions, 12 deletions
diff --git a/net/SConscript b/net/SConscript
index 225647d..806904a 100644
--- a/net/SConscript
+++ b/net/SConscript
@@ -63,6 +63,10 @@ input_files = [
'disk_cache/stats_histogram.cc',
'disk_cache/trace.cc',
'http/cert_status_cache.cc',
+ 'http/http_auth.cc',
+ 'http/http_auth_handler.cc',
+ 'http/http_auth_handler_basic.cc',
+ 'http/http_auth_handler_digest.cc',
'http/http_cache.cc',
'http/http_chunked_decoder.cc',
'http/http_response_headers.cc',
diff --git a/net/base/net_util.cc b/net/base/net_util.cc
index 305cbcc..57448af 100644
--- a/net/base/net_util.cc
+++ b/net/base/net_util.cc
@@ -913,4 +913,26 @@ bool IsPortAllowedByFtp(int port) {
return IsPortAllowedByDefault(port);
}
+std::string GetImplicitPort(const GURL& url) {
+ if (url.has_port())
+ return url.port();
+
+ // TODO(eroman): unify with DefaultPortForScheme()
+ // [url_canon_stdurl.cc]
+
+ static const struct {
+ const char* scheme;
+ const char* port;
+ } scheme_map[] = {
+ { "http", "80" },
+ { "https", "443" },
+ { "ftp", "21" }
+ };
+ for (int i = 0; i < static_cast<int>(ARRAYSIZE_UNSAFE(scheme_map)); ++i) {
+ if (url.SchemeIs(scheme_map[i].scheme))
+ return scheme_map[i].port;
+ }
+ return std::string("");
+}
+
} // namespace net
diff --git a/net/base/net_util.h b/net/base/net_util.h
index db28a76..6ceead7 100644
--- a/net/base/net_util.h
+++ b/net/base/net_util.h
@@ -131,6 +131,11 @@ bool IsPortAllowedByDefault(int port);
// restricted.
bool IsPortAllowedByFtp(int port);
+// Get the port number for the URL. If the URL does not have a port number,
+// then returns the default port for the scheme. If the scheme is unrecognized
+// returns empty string.
+std::string GetImplicitPort(const GURL& url);
+
} // namespace net
#endif // NET_BASE_NET_UTIL_H__
diff --git a/net/base/net_util_unittest.cc b/net/base/net_util_unittest.cc
index 22fb7d4..884f2fe 100644
--- a/net/base/net_util_unittest.cc
+++ b/net/base/net_util_unittest.cc
@@ -698,6 +698,34 @@ TEST(NetUtilTest, GetSuggestedFilename) {
}
}
+TEST(NetUtilTest, GetImplicitPort) {
+ {
+ GURL url("http://foo.bar/baz");
+ EXPECT_STREQ("80", net::GetImplicitPort(url).c_str());
+ }
+ {
+ GURL url("http://foo.bar:443/baz");
+ EXPECT_STREQ("443", net::GetImplicitPort(url).c_str());
+ }
+ {
+ GURL url("https://foo.bar/baz");
+ EXPECT_STREQ("443", net::GetImplicitPort(url).c_str());
+ }
+ {
+ GURL url("https://foo.bar:80/baz");
+ EXPECT_STREQ("80", net::GetImplicitPort(url).c_str());
+ }
+ {
+ // Invalid input.
+ GURL url("file://foobar/baz");
+ EXPECT_STREQ("", net::GetImplicitPort(url).c_str());
+ }
+ {
+ GURL url("ftp://google.com");
+ EXPECT_STREQ("21", net::GetImplicitPort(url).c_str());
+ }
+}
+
// This is currently a windows specific function.
#if defined(OS_WIN)
namespace {
diff --git a/net/build/net.vcproj b/net/build/net.vcproj
index 68703cd..938c1e4 100644
--- a/net/build/net.vcproj
+++ b/net/build/net.vcproj
@@ -745,6 +745,70 @@
>
</File>
<File
+ RelativePath="..\http\http_auth.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\http\http_auth.h"
+ >
+ </File>
+ <File
+ RelativePath="..\http\http_auth_handler.h"
+ >
+ </File>
+ <File
+ RelativePath="..\http\http_auth_handler.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\http\http_auth_handler_basic.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\http\http_auth_handler_basic.h"
+ >
+ </File>
+ <File
+ RelativePath="..\http\http_auth_handler_digest.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\http\http_auth_handler_digest.h"
+ >
+ </File>
+ <File
+ RelativePath="..\http\http_auth.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\http\http_auth.h"
+ >
+ </File>
+ <File
+ RelativePath="..\http\http_auth_handler.h"
+ >
+ </File>
+ <File
+ RelativePath="..\http\http_auth_handler.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\http\http_auth_handler_basic.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\http\http_auth_handler_basic.h"
+ >
+ </File>
+ <File
+ RelativePath="..\http\http_auth_handler_digest.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\http\http_auth_handler_digest.h"
+ >
+ </File>
+ <File
RelativePath="..\http\http_vary_data.cc"
>
</File>
diff --git a/net/build/net_unittests.vcproj b/net/build/net_unittests.vcproj
index 479c34b..f2fbb7d 100644
--- a/net/build/net_unittests.vcproj
+++ b/net/build/net_unittests.vcproj
@@ -227,6 +227,18 @@
Name="http"
>
<File
+ RelativePath="..\http\http_auth_unittest.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\http\http_auth_handler_basic_unittest.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\http\http_auth_handler_digest_unittest.cc"
+ >
+ </File>
+ <File
RelativePath="..\http\http_cache_unittest.cc"
>
</File>
diff --git a/net/http/http_auth.cc b/net/http/http_auth.cc
new file mode 100644
index 0000000..324f901
--- /dev/null
+++ b/net/http/http_auth.cc
@@ -0,0 +1,147 @@
+// Copyright (c) 2006-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 "net/http/http_auth.h"
+
+#include <algorithm>
+
+#include "base/basictypes.h"
+#include "base/string_util.h"
+#include "net/http/http_auth_handler_basic.h"
+#include "net/http/http_auth_handler_digest.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_util.h"
+
+namespace net {
+
+// static
+HttpAuthHandler* HttpAuth::ChooseBestChallenge(
+ const HttpResponseHeaders* headers, Target target) {
+ // Choose the challenge whose authentication handler gives the maximum score.
+ scoped_ptr<HttpAuthHandler> best;
+ const std::string header_name = GetChallengeHeaderName(target);
+ std::string cur_challenge;
+ void* iter = NULL;
+ while (headers->EnumerateHeader(&iter, header_name, &cur_challenge)) {
+ scoped_ptr<HttpAuthHandler> cur(
+ CreateAuthHandler(cur_challenge, target));
+ if (cur.get() && (!best.get() || best->score() < cur->score()))
+ best.reset(cur.release());
+ }
+ return best.release();
+}
+
+// static
+HttpAuthHandler* HttpAuth::CreateAuthHandler(const std::string& challenge,
+ Target target) {
+ // Find the right auth handler for the challenge's scheme.
+ ChallengeTokenizer props(challenge.begin(), challenge.end());
+ scoped_ptr<HttpAuthHandler> handler;
+
+ if (LowerCaseEqualsASCII(props.scheme(), "basic")) {
+ handler.reset(new HttpAuthHandlerBasic());
+ } else if (LowerCaseEqualsASCII(props.scheme(), "digest")) {
+ handler.reset(new HttpAuthHandlerDigest());
+ }
+ if (handler.get()) {
+ if (!handler->InitFromChallenge(challenge.begin(), challenge.end(),
+ target)) {
+ // Invalid/unsupported challenge.
+ return NULL;
+ }
+ }
+ return handler.release();
+}
+
+void HttpAuth::ChallengeTokenizer::Init(std::string::const_iterator begin,
+ std::string::const_iterator end) {
+ // The first space-separated token is the auth-scheme.
+ // NOTE: we are more permissive than RFC 2617 which says auth-scheme
+ // is separated by 1*SP.
+ StringTokenizer tok(begin, end, HTTP_LWS);
+ if (!tok.GetNext()) {
+ valid_ = false;
+ return;
+ }
+
+ // Save the scheme's position.
+ scheme_begin_ = tok.token_begin();
+ scheme_end_ = tok.token_end();
+
+ // Everything past scheme_end_ is a (comma separated) value list.
+ if (scheme_end_ != end)
+ props_ = HttpUtil::ValuesIterator(scheme_end_ + 1, end, ',');
+}
+
+// We expect properties to be formatted as one of:
+// name="value"
+// name=value
+// name=
+bool HttpAuth::ChallengeTokenizer::GetNext() {
+ if (!props_.GetNext())
+ return false;
+
+ // Set the value as everything. Next we will split out the name.
+ value_begin_ = props_.value_begin();
+ value_end_ = props_.value_end();
+ name_begin_ = name_end_ = value_end_;
+
+ // Scan for the equals sign.
+ std::string::const_iterator equals = std::find(value_begin_, value_end_, '=');
+ if (equals == value_end_ || equals == value_begin_)
+ return valid_ = false; // Malformed
+
+ // Verify that the equals sign we found wasn't inside of quote marks.
+ for (std::string::const_iterator it = value_begin_; it != equals; ++it) {
+ if (HttpUtil::IsQuote(*it))
+ return valid_ = false; // Malformed
+ }
+
+ name_begin_ = value_begin_;
+ name_end_ = equals;
+ value_begin_ = equals + 1;
+
+ if (value_begin_ != value_end_ && HttpUtil::IsQuote(*value_begin_)) {
+ // Trim surrounding quotemarks off the value
+ if (*value_begin_ != *(value_end_ - 1))
+ return valid_ = false; // Malformed -- mismatching quotes.
+ value_is_quoted_ = true;
+ } else {
+ value_is_quoted_ = false;
+ }
+ return true;
+}
+
+// If value() has quotemarks, unquote it.
+std::string HttpAuth::ChallengeTokenizer::unquoted_value() const {
+ return HttpUtil::Unquote(value_begin_, value_end_);
+}
+
+// static
+std::string HttpAuth::GetChallengeHeaderName(Target target) {
+ switch(target) {
+ case AUTH_PROXY:
+ return "Proxy-Authenticate";
+ case AUTH_SERVER:
+ return "WWW-Authenticate";
+ default:
+ NOTREACHED();
+ return "";
+ }
+}
+
+// static
+std::string HttpAuth::GetAuthorizationHeaderName(Target target) {
+ switch(target) {
+ case AUTH_PROXY:
+ return "Proxy-Authorization";
+ case AUTH_SERVER:
+ return "Authorization";
+ default:
+ NOTREACHED();
+ return "";
+ }
+}
+
+} // namespace net
diff --git a/net/http/http_auth.h b/net/http/http_auth.h
new file mode 100644
index 0000000..34d10f0
--- /dev/null
+++ b/net/http/http_auth.h
@@ -0,0 +1,120 @@
+// Copyright (c) 2006-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.
+
+#ifndef NET_HTTP_HTTP_AUTH_H_
+#define NET_HTTP_HTTP_AUTH_H_
+
+#include "net/http/http_util.h"
+
+namespace net {
+
+class HttpAuthHandler;
+class HttpResponseHeaders;
+
+// Utility class for http authentication.
+class HttpAuth {
+ public:
+
+ // Http authentication can be done the the proxy server, origin server,
+ // or both. This enum tracks who the target is.
+ enum Target {
+ AUTH_PROXY = 0,
+ AUTH_SERVER = 1,
+ };
+
+ // Get the name of the header containing the auth challenge
+ // (either WWW-Authenticate or Proxy-Authenticate).
+ static std::string GetChallengeHeaderName(Target target);
+
+ // Get the name of the header where the credentials go
+ // (either Authorization or Proxy-Authorization).
+ static std::string GetAuthorizationHeaderName(Target target);
+
+ // Create a handler to generate credentials for the challenge. If the
+ // challenge is unsupported or invalid, returns NULL.
+ // The caller owns the returned pointer.
+ static HttpAuthHandler* CreateAuthHandler(const std::string& challenge,
+ Target target);
+
+ // Iterate through the challenge headers, and pick the best one that
+ // we support. Returns the implementation class for handling the challenge.
+ // If no supported challenge was found, returns NULL.
+ // The caller owns the returned pointer.
+ static HttpAuthHandler* ChooseBestChallenge(
+ const HttpResponseHeaders* headers, Target target);
+
+ // ChallengeTokenizer breaks up a challenge string into the the auth scheme
+ // and parameter list, according to RFC 2617 Sec 1.2:
+ // challenge = auth-scheme 1*SP 1#auth-param
+ //
+ // Check valid() after each iteration step in case it was malformed.
+ // Also note that value() will give whatever is to the right of the equals
+ // sign, quotemarks and all. Use unquoted_value() to get the logical value.
+ class ChallengeTokenizer {
+ public:
+ ChallengeTokenizer(std::string::const_iterator begin,
+ std::string::const_iterator end)
+ : props_(begin, end, ','), valid_(true) {
+ Init(begin, end);
+ }
+
+ // Get the auth scheme of the challenge.
+ std::string::const_iterator scheme_begin() const { return scheme_begin_; }
+ std::string::const_iterator scheme_end() const { return scheme_end_; }
+ std::string scheme() const {
+ return std::string(scheme_begin_, scheme_end_);
+ }
+
+ // Returns false if there was a parse error.
+ bool valid() const {
+ return valid_;
+ }
+
+ // Advances the iterator to the next name-value pair, if any.
+ // Returns true if there is none to consume.
+ bool GetNext();
+
+ // The name of the current name-value pair.
+ std::string::const_iterator name_begin() const { return name_begin_; }
+ std::string::const_iterator name_end() const { return name_end_; }
+ std::string name() const {
+ return std::string(name_begin_, name_end_);
+ }
+
+ // The value of the current name-value pair.
+ std::string::const_iterator value_begin() const { return value_begin_; }
+ std::string::const_iterator value_end() const { return value_end_; }
+ std::string value() const {
+ return std::string(value_begin_, value_end_);
+ }
+
+ // If value() has quotemarks, unquote it.
+ std::string unquoted_value() const;
+
+ // True if the name-value pair's value has quote marks.
+ bool value_is_quoted() const { return value_is_quoted_; }
+
+ private:
+ void Init(std::string::const_iterator begin,
+ std::string::const_iterator end);
+
+ HttpUtil::ValuesIterator props_;
+ bool valid_;
+
+ std::string::const_iterator scheme_begin_;
+ std::string::const_iterator scheme_end_;
+
+ std::string::const_iterator name_begin_;
+ std::string::const_iterator name_end_;
+
+ std::string::const_iterator value_begin_;
+ std::string::const_iterator value_end_;
+
+ bool value_is_quoted_;
+ };
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_AUTH_H_
diff --git a/net/http/http_auth_handler.cc b/net/http/http_auth_handler.cc
new file mode 100644
index 0000000..b0a7b97
--- /dev/null
+++ b/net/http/http_auth_handler.cc
@@ -0,0 +1,27 @@
+// Copyright (c) 2006-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/logging.h"
+#include "net/http/http_auth_handler.h"
+
+namespace net {
+
+bool HttpAuthHandler::InitFromChallenge(std::string::const_iterator begin,
+ std::string::const_iterator end,
+ HttpAuth::Target target) {
+ target_ = target;
+ score_ = -1;
+ scheme_ = NULL;
+
+ bool ok = Init(begin, end);
+
+ // Init() is expected to set the scheme, realm, and score.
+ DCHECK(!ok || !scheme().empty());
+ DCHECK(!ok || !realm().empty());
+ DCHECK(!ok || score_ != -1);
+
+ return ok;
+}
+
+} // namespace net
diff --git a/net/http/http_auth_handler.h b/net/http/http_auth_handler.h
new file mode 100644
index 0000000..0608f7b
--- /dev/null
+++ b/net/http/http_auth_handler.h
@@ -0,0 +1,77 @@
+// Copyright (c) 2006-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.
+
+#ifndef NET_HTTP_HTTP_AUTH_HANDLER_H_
+#define NET_HTTP_HTTP_AUTH_HANDLER_H_
+
+#include <string>
+
+#include "net/http/http_auth.h"
+
+namespace net {
+
+class HttpRequestInfo;
+class ProxyInfo;
+
+// HttpAuthHandler is the interface for the authentication schemes
+// (basic, digest, ...)
+// The registry mapping auth-schemes to implementations is hardcoded in
+// HttpAuth::CreateAuthHandler().
+class HttpAuthHandler {
+ public:
+ // Initialize the handler by parsing a challenge string.
+ bool InitFromChallenge(std::string::const_iterator begin,
+ std::string::const_iterator end,
+ HttpAuth::Target target);
+
+ // Lowercase name of the auth scheme
+ virtual std::string scheme() const {
+ return scheme_;
+ }
+
+ // The realm value that was parsed during Init().
+ std::string realm() const {
+ return realm_;
+ }
+
+ // Numeric rank based on the challenge's security level. Higher
+ // numbers are better. Used by HttpAuth::ChooseBestChallenge().
+ int score() const {
+ return score_;
+ }
+
+ HttpAuth::Target target() const {
+ return target_;
+ }
+
+ // Generate the Authorization header value.
+ virtual std::string GenerateCredentials(const std::wstring& username,
+ const std::wstring& password,
+ const HttpRequestInfo* request,
+ const ProxyInfo* proxy) = 0;
+
+ protected:
+ // Initialize the handler by parsing a challenge string.
+ // Implementations are expcted to initialize the following members:
+ // score_, realm_, scheme_
+ virtual bool Init(std::string::const_iterator challenge_begin,
+ std::string::const_iterator challenge_end) = 0;
+
+ // The lowercase auth-scheme {"basic", "digest", "ntlm", ...}
+ const char* scheme_;
+
+ // The realm.
+ std::string realm_;
+
+ // The score for this challenge. Higher numbers are better.
+ int score_;
+
+ // Whether this authentication request is for a proxy server, or an
+ // origin server.
+ HttpAuth::Target target_;
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_AUTH_HANDLER_H_
diff --git a/net/http/http_auth_handler_basic.cc b/net/http/http_auth_handler_basic.cc
new file mode 100644
index 0000000..51c165c
--- /dev/null
+++ b/net/http/http_auth_handler_basic.cc
@@ -0,0 +1,46 @@
+// Copyright (c) 2006-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 "net/http/http_auth_handler_basic.h"
+
+#include "base/string_util.h"
+#include "net/http/http_auth.h"
+#include "net/base/base64.h"
+
+namespace net {
+
+bool HttpAuthHandlerBasic::Init(std::string::const_iterator challenge_begin,
+ std::string::const_iterator challenge_end) {
+ scheme_ = "basic";
+ score_ = 1;
+
+ // Verify the challenge's auth-scheme.
+ HttpAuth::ChallengeTokenizer challenge_tok(challenge_begin, challenge_end);
+ if (!challenge_tok.valid() ||
+ !LowerCaseEqualsASCII(challenge_tok.scheme(), "basic"))
+ return false;
+
+ // Extract the realm.
+ while (challenge_tok.GetNext()) {
+ if (LowerCaseEqualsASCII(challenge_tok.name(), "realm"))
+ realm_ = challenge_tok.unquoted_value();
+ }
+
+ return challenge_tok.valid() && !realm_.empty();
+}
+
+std::string HttpAuthHandlerBasic::GenerateCredentials(
+ const std::wstring& username,
+ const std::wstring& password,
+ const HttpRequestInfo*,
+ const ProxyInfo*) {
+ // TODO(eroman): is this the right encoding of username/password?
+ std::string base64_username_password;
+ if (!Base64Encode(WideToUTF8(username) + ":" + WideToUTF8(password),
+ &base64_username_password))
+ return std::string(); // FAIL
+ return std::string("Basic ") + base64_username_password;
+}
+
+} // namespace net
diff --git a/net/http/http_auth_handler_basic.h b/net/http/http_auth_handler_basic.h
new file mode 100644
index 0000000..2db2fcb
--- /dev/null
+++ b/net/http/http_auth_handler_basic.h
@@ -0,0 +1,27 @@
+// Copyright (c) 2006-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.
+
+#ifndef NET_HTTP_HTTP_AUTH_HANDLER_BASIC_H_
+#define NET_HTTP_HTTP_AUTH_HANDLER_BASIC_H_
+
+#include "net/http/http_auth_handler.h"
+
+namespace net {
+
+// Code for handling http basic authentication.
+class HttpAuthHandlerBasic : public HttpAuthHandler {
+ public:
+ virtual std::string GenerateCredentials(const std::wstring& username,
+ const std::wstring& password,
+ const HttpRequestInfo*,
+ const ProxyInfo*);
+ protected:
+ virtual bool Init(std::string::const_iterator challenge_begin,
+ std::string::const_iterator challenge_end);
+
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_AUTH_HANDLER_BASIC_H_
diff --git a/net/http/http_auth_handler_basic_unittest.cc b/net/http/http_auth_handler_basic_unittest.cc
new file mode 100644
index 0000000..bf860e3
--- /dev/null
+++ b/net/http/http_auth_handler_basic_unittest.cc
@@ -0,0 +1,34 @@
+// Copyright (c) 2006-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 "testing/gtest/include/gtest/gtest.h"
+
+#include "base/basictypes.h"
+#include "net/http/http_auth_handler_basic.h"
+
+namespace net {
+
+TEST(HttpAuthHandlerBasicTest, GenerateCredentials) {
+ static const struct {
+ const wchar_t* username;
+ const wchar_t* password;
+ const char* expected_credentials;
+ } tests[] = {
+ { L"foo", L"bar", "Basic Zm9vOmJhcg==" },
+ // Empty password
+ { L"anon", L"", "Basic YW5vbjo=" },
+ };
+ for (int i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ std::string challenge = "Basic realm=\"Atlantis\"";
+ HttpAuthHandlerBasic basic;
+ basic.InitFromChallenge(challenge.begin(), challenge.end(),
+ HttpAuth::AUTH_SERVER);
+ std::string credentials = basic.GenerateCredentials(tests[i].username,
+ tests[i].password,
+ NULL, NULL);
+ EXPECT_STREQ(tests[i].expected_credentials, credentials.c_str());
+ }
+}
+
+} // namespace net
diff --git a/net/http/http_auth_handler_digest.cc b/net/http/http_auth_handler_digest.cc
new file mode 100644
index 0000000..e665a0b
--- /dev/null
+++ b/net/http/http_auth_handler_digest.cc
@@ -0,0 +1,278 @@
+// Copyright (c) 2006-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 "net/http/http_auth_handler_digest.h"
+
+#include "base/md5.h"
+#include "base/string_util.h"
+#include "net/base/net_util.h"
+#include "net/http/http_auth.h"
+#include "net/http/http_request_info.h"
+#include "net/http/http_util.h"
+
+// TODO(eroman): support qop=auth-int
+
+namespace net {
+
+// Digest authentication is specified in RFC 2617.
+// The expanded derivations are listed in the tables below.
+
+//==========+==========+==========================================+
+// qop |algorithm | response |
+//==========+==========+==========================================+
+// ? | ?, md5, | MD5(MD5(A1):nonce:MD5(A2)) |
+// | md5-sess | |
+//--------- +----------+------------------------------------------+
+// auth, | ?, md5, | MD5(MD5(A1):nonce:nc:cnonce:qop:MD5(A2)) |
+// auth-int | md5-sess | |
+//==========+==========+==========================================+
+// qop |algorithm | A1 |
+//==========+==========+==========================================+
+// | ?, md5 | user:realm:password |
+//----------+----------+------------------------------------------+
+// | md5-sess | MD5(user:realm:password):nonce:cnonce |
+//==========+==========+==========================================+
+// qop |algorithm | A2 |
+//==========+==========+==========================================+
+// ?, auth | | req-method:req-uri |
+//----------+----------+------------------------------------------+
+// auth-int | | req-method:req-uri:MD5(req-entity-body) |
+//=====================+==========================================+
+
+
+// static
+std::string HttpAuthHandlerDigest::GenerateNonce() {
+ // This is how mozilla generates their cnonce -- a 16 digit hex string.
+ static const char domain[] = "0123456789abcdef";
+ std::string cnonce;
+ cnonce.reserve(16);
+ // TODO(eroman): use rand_util::RandIntSecure()
+ for (int i = 0; i < 16; ++i)
+ cnonce.push_back(domain[rand() % 16]);
+ return cnonce;
+}
+
+// static
+std::string HttpAuthHandlerDigest::QopToString(int qop) {
+ switch (qop) {
+ case QOP_AUTH:
+ return "auth";
+ case QOP_AUTH_INT:
+ return "auth-int";
+ default:
+ return "";
+ }
+}
+
+// static
+std::string HttpAuthHandlerDigest::AlgorithmToString(int algorithm) {
+ switch (algorithm) {
+ case ALGORITHM_MD5:
+ return "MD5";
+ case ALGORITHM_MD5_SESS:
+ return "MD5-sess";
+ default:
+ return "";
+ }
+}
+
+std::string HttpAuthHandlerDigest::GenerateCredentials(
+ const std::wstring& username,
+ const std::wstring& password,
+ const HttpRequestInfo* request,
+ const ProxyInfo* proxy) {
+ // Generate a random client nonce.
+ std::string cnonce = GenerateNonce();
+
+ // The nonce-count should be incremented after re-use per the spec.
+ // This may not be possible when there are multiple connections to the
+ // server though:
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=114451
+ // TODO(eroman): leave as 1 for now, and possibly permanently.
+ int nonce_count = 1;
+
+ // Extract the request method and path -- the meaning of 'path' is overloaded
+ // in certain cases, to be a hostname.
+ std::string method;
+ std::string path;
+ GetRequestMethodAndPath(request, proxy, &method, &path);
+
+ return AssembleCredentials(method, path,
+ // TODO(eroman): is this the right encoding?
+ WideToUTF8(username),
+ WideToUTF8(password),
+ cnonce, nonce_count);
+}
+
+void HttpAuthHandlerDigest::GetRequestMethodAndPath(
+ const HttpRequestInfo* request,
+ const ProxyInfo* proxy,
+ std::string* method,
+ std::string* path) const {
+ DCHECK(request);
+ DCHECK(proxy);
+
+ const GURL& url = request->url;
+
+ if (target_ == HttpAuth::AUTH_PROXY && url.SchemeIs("https")) {
+ *method = "CONNECT";
+ *path = url.host() + ":" + GetImplicitPort(url);
+ } else {
+ *method = request->method;
+ *path = HttpUtil::PathForRequest(url);
+ }
+}
+
+std::string HttpAuthHandlerDigest::AssembleResponseDigest(
+ const std::string& method,
+ const std::string& path,
+ const std::string& username,
+ const std::string& password,
+ const std::string& cnonce,
+ const std::string& nc) const {
+ // ha1 = MD5(A1)
+ std::string ha1 = MD5String(username + ":" + realm_ + ":" + password);
+ if (algorithm_ == HttpAuthHandlerDigest::ALGORITHM_MD5_SESS)
+ ha1 = MD5String(ha1 + ":" + nonce_ + ":" + cnonce);
+
+ // ha2 = MD5(A2)
+ // TODO(eroman): need to add MD5(req-entity-body) for qop=auth-int.
+ std::string ha2 = MD5String(method + ":" + path);
+
+ std::string nc_part;
+ if (qop_ != HttpAuthHandlerDigest::QOP_UNSPECIFIED) {
+ nc_part = nc + ":" + cnonce + ":" + QopToString(qop_) + ":";
+ }
+
+ return MD5String(ha1 + ":" + nonce_ + ":" + nc_part + ha2);
+}
+
+std::string HttpAuthHandlerDigest::AssembleCredentials(
+ const std::string& method,
+ const std::string& path,
+ const std::string& username,
+ const std::string& password,
+ const std::string& cnonce,
+ int nonce_count) const {
+ // the nonce-count is an 8 digit hex string.
+ std::string nc = StringPrintf("%08x", nonce_count);
+
+ std::string authorization = std::string("Digest username=") +
+ HttpUtil::Quote(username);
+ authorization += ", realm=" + HttpUtil::Quote(realm_);
+ authorization += ", nonce=" + HttpUtil::Quote(nonce_);
+ authorization += ", uri=" + HttpUtil::Quote(path);
+
+ if (algorithm_ != ALGORITHM_UNSPECIFIED) {
+ authorization += ", algorithm=" + AlgorithmToString(algorithm_);
+ }
+ std::string response = AssembleResponseDigest(method, path, username,
+ password, cnonce, nc);
+ // No need to call HttpUtil::Quote() as the response digest cannot contain
+ // any characters needing to be escaped.
+ authorization += ", response=\"" + response + "\"";
+
+ if (!opaque_.empty()) {
+ authorization += ", opaque=" + HttpUtil::Quote(opaque_);
+ }
+ if (qop_ != QOP_UNSPECIFIED) {
+ // TODO(eroman): Supposedly IIS server requires quotes surrounding qop.
+ authorization += ", qop=" + QopToString(qop_);
+ authorization += ", nc=" + nc;
+ authorization += ", cnonce=" + HttpUtil::Quote(cnonce);
+ }
+
+ return authorization;
+}
+
+// The digest challenge header looks like:
+// WWW-Authenticate: Digest
+// realm="<realm-value>"
+// nonce="<nonce-value>"
+// [domain="<list-of-URIs>"]
+// [opaque="<opaque-token-value>"]
+// [stale="<true-or-false>"]
+// [algorithm="<digest-algorithm>"]
+// [qop="<list-of-qop-values>"]
+// [<extension-directive>]
+bool HttpAuthHandlerDigest::ParseChallenge(
+ std::string::const_iterator challenge_begin,
+ std::string::const_iterator challenge_end) {
+ scheme_ = "digest";
+ score_ = 2;
+
+ // Initialize to defaults.
+ stale_ = false;
+ algorithm_ = ALGORITHM_UNSPECIFIED;
+ qop_ = QOP_UNSPECIFIED;
+ realm_ = nonce_ = domain_ = opaque_ = std::string();
+
+ HttpAuth::ChallengeTokenizer props(challenge_begin, challenge_end);
+
+ if (!props.valid() || !LowerCaseEqualsASCII(props.scheme(), "digest"))
+ return false; // FAIL -- Couldn't match auth-scheme.
+
+ // Loop through all the properties.
+ while (props.GetNext()) {
+ if (props.value().empty()) {
+ DLOG(INFO) << "Invalid digest property";
+ return false;
+ }
+
+ if (!ParseChallengeProperty(props.name(), props.unquoted_value()))
+ return false; // FAIL -- couldn't parse a property.
+ }
+
+ // Check if tokenizer failed.
+ if (!props.valid())
+ return false; // FAIL
+
+ // Check that a minimum set of properties were provided.
+ if (realm_.empty() || nonce_.empty())
+ return false; // FAIL
+
+ return true;
+}
+
+bool HttpAuthHandlerDigest::ParseChallengeProperty(const std::string& name,
+ const std::string& value) {
+ if (LowerCaseEqualsASCII(name, "realm")) {
+ realm_ = value;
+ } else if (LowerCaseEqualsASCII(name, "nonce")) {
+ nonce_ = value;
+ } else if (LowerCaseEqualsASCII(name, "domain")) {
+ domain_ = value;
+ } else if (LowerCaseEqualsASCII(name, "opaque")) {
+ opaque_ = value;
+ } else if (LowerCaseEqualsASCII(name, "stale")) {
+ // Parse the stale boolean.
+ stale_ = LowerCaseEqualsASCII(value, "true");
+ } else if (LowerCaseEqualsASCII(name, "algorithm")) {
+ // Parse the algorithm.
+ if (LowerCaseEqualsASCII(value, "md5")) {
+ algorithm_ = ALGORITHM_MD5;
+ } else if (LowerCaseEqualsASCII(value, "md5-sess")) {
+ algorithm_ = ALGORITHM_MD5_SESS;
+ } else {
+ DLOG(INFO) << "Unknown value of algorithm";
+ return false; // FAIL -- unsupported value of algorithm.
+ }
+ } else if (LowerCaseEqualsASCII(name, "qop")) {
+ // Parse the comma separated list of qops.
+ HttpUtil::ValuesIterator qop_values(value.begin(), value.end(), ',');
+ while (qop_values.GetNext()) {
+ if (LowerCaseEqualsASCII(qop_values.value(), "auth")) {
+ qop_ |= QOP_AUTH;
+ } else if (LowerCaseEqualsASCII(qop_values.value(), "auth-int")) {
+ qop_ |= QOP_AUTH_INT;
+ }
+ }
+ } else {
+ DLOG(INFO) << "Skipping unrecognized digest property";
+ // TODO(eroman): perhaps we should fail instead of silently skipping?
+ }
+ return true;
+}
+
+} // namespace net
diff --git a/net/http/http_auth_handler_digest.h b/net/http/http_auth_handler_digest.h
new file mode 100644
index 0000000..5289cc5
--- /dev/null
+++ b/net/http/http_auth_handler_digest.h
@@ -0,0 +1,105 @@
+// Copyright (c) 2006-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.
+
+#ifndef NET_HTTP_HTTP_AUTH_HANDLER_DIGEST_H_
+#define NET_HTTP_HTTP_AUTH_HANDLER_DIGEST_H_
+
+#include "net/http/http_auth_handler.h"
+
+// This is needed for the FRIEND_TEST() macro.
+#include "testing/gtest/include/gtest/gtest_prod.h"
+
+namespace net {
+
+// Code for handling http digest authentication.
+class HttpAuthHandlerDigest : public HttpAuthHandler {
+ public:
+ virtual std::string GenerateCredentials(const std::wstring& username,
+ const std::wstring& password,
+ const HttpRequestInfo* request,
+ const ProxyInfo* proxy);
+
+ protected:
+ virtual bool Init(std::string::const_iterator challenge_begin,
+ std::string::const_iterator challenge_end) {
+ return ParseChallenge(challenge_begin, challenge_end);
+ }
+
+ private:
+ FRIEND_TEST(HttpAuthHandlerDigestTest, ParseChallenge);
+ FRIEND_TEST(HttpAuthHandlerDigestTest, AssembleCredentials);
+
+ // Possible values for the "algorithm" property.
+ enum DigestAlgorithm {
+ // No algorithm was specified. According to RFC 2617 this means
+ // we should default to ALGORITHM_MD5.
+ ALGORITHM_UNSPECIFIED,
+
+ // Hashes are run for every request.
+ ALGORITHM_MD5,
+
+ // Hash is run only once during the first WWW-Authenticate handshake.
+ // (SESS means session).
+ ALGORITHM_MD5_SESS,
+ };
+
+ // Possible values for "qop" -- may be or-ed together if there were
+ // multiple comma separated values.
+ enum QualityOfProtection {
+ QOP_UNSPECIFIED = 0,
+ QOP_AUTH = 1 << 0,
+ QOP_AUTH_INT = 1 << 1,
+ };
+
+ // Parse the challenge, saving the results into this instance.
+ // Returns true on success.
+ bool ParseChallenge(std::string::const_iterator challenge_begin,
+ std::string::const_iterator challenge_end);
+
+ // Parse an individual property. Returns true on success.
+ bool ParseChallengeProperty(const std::string& name,
+ const std::string& value);
+
+ // Generates a random string, to be used for client-nonce.
+ static std::string GenerateNonce();
+
+ // Convert enum value back to string.
+ static std::string QopToString(int qop);
+ static std::string AlgorithmToString(int algorithm);
+
+ // Extract the method and path of the request, as needed by
+ // the 'A2' production. (path may be a hostname for proxy).
+ void GetRequestMethodAndPath(const HttpRequestInfo* request,
+ const ProxyInfo* proxy,
+ std::string* method,
+ std::string* path) const;
+
+ // Build up the 'response' production.
+ std::string AssembleResponseDigest(const std::string& method,
+ const std::string& path,
+ const std::string& username,
+ const std::string& password,
+ const std::string& cnonce,
+ const std::string& nc) const;
+
+ // Build up the value for (Authorization/Proxy-Authorization).
+ std::string AssembleCredentials(const std::string& method,
+ const std::string& path,
+ const std::string& username,
+ const std::string& password,
+ const std::string& cnonce,
+ int nonce_count) const;
+
+ // Information parsed from the challenge.
+ std::string nonce_;
+ std::string domain_;
+ std::string opaque_;
+ bool stale_;
+ DigestAlgorithm algorithm_;
+ int qop_; // Bitfield of QualityOfProtection
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_AUTH_HANDLER_DIGEST_H_
diff --git a/net/http/http_auth_handler_digest_unittest.cc b/net/http/http_auth_handler_digest_unittest.cc
new file mode 100644
index 0000000..41df781
--- /dev/null
+++ b/net/http/http_auth_handler_digest_unittest.cc
@@ -0,0 +1,210 @@
+// Copyright (c) 2006-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 "testing/gtest/include/gtest/gtest.h"
+
+#include "base/basictypes.h"
+#include "net/http/http_auth_handler_digest.h"
+
+namespace net {
+
+TEST(HttpAuthHandlerDigestTest, ParseChallenge) {
+ static const struct {
+ // The challenge string.
+ const char* challenge;
+ // Expected return value of ParseChallenge.
+ bool parsed_success;
+ // The expected values that were parsed.
+ const char* parsed_realm;
+ const char* parsed_nonce;
+ const char* parsed_domain;
+ const char* parsed_opaque;
+ bool parsed_stale;
+ int parsed_algorithm;
+ int parsed_qop;
+ } tests[] = {
+ {
+ "Digest nonce=\"xyz\", realm=\"Thunder Bluff\"",
+ true,
+ "Thunder Bluff",
+ "xyz",
+ "",
+ "",
+ false,
+ HttpAuthHandlerDigest::ALGORITHM_UNSPECIFIED,
+ HttpAuthHandlerDigest::QOP_UNSPECIFIED
+ },
+
+ {// Check that when algorithm has an unsupported value, parsing fails.
+ "Digest nonce=\"xyz\", algorithm=\"awezum\", realm=\"Thunder\"",
+ false,
+ // The remaining values don't matter (but some have been set already).
+ "",
+ "xyz",
+ "",
+ "",
+ false,
+ HttpAuthHandlerDigest::ALGORITHM_UNSPECIFIED,
+ HttpAuthHandlerDigest::QOP_UNSPECIFIED
+ },
+
+ { // Check that algorithm's value is case insensitive.
+ "Digest nonce=\"xyz\", algorithm=\"mD5\", realm=\"Oblivion\"",
+ true,
+ "Oblivion",
+ "xyz",
+ "",
+ "",
+ false,
+ HttpAuthHandlerDigest::ALGORITHM_MD5,
+ HttpAuthHandlerDigest::QOP_UNSPECIFIED
+ },
+
+ { // Check that md5-sess is recognized, as is single QOP
+ "Digest nonce=\"xyz\", algorithm=\"md5-sess\", "
+ "realm=\"Oblivion\", qop=\"auth\"",
+ true,
+ "Oblivion",
+ "xyz",
+ "",
+ "",
+ false,
+ HttpAuthHandlerDigest::ALGORITHM_MD5_SESS,
+ HttpAuthHandlerDigest::QOP_AUTH
+ },
+
+ { // The realm can't be missing.
+ "Digest nonce=\"xyz\"",
+ false, // FAILED parse.
+ "",
+ "xyz",
+ "",
+ "",
+ false,
+ HttpAuthHandlerDigest::ALGORITHM_UNSPECIFIED,
+ HttpAuthHandlerDigest::QOP_UNSPECIFIED
+ }
+ };
+
+ for (int i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ std::string challenge(tests[i].challenge);
+
+ HttpAuthHandlerDigest auth;
+ bool ok = auth.ParseChallenge(challenge.begin(), challenge.end());
+
+ EXPECT_EQ(tests[i].parsed_success, ok);
+ EXPECT_STREQ(tests[i].parsed_realm, auth.realm_.c_str());
+ EXPECT_STREQ(tests[i].parsed_nonce, auth.nonce_.c_str());
+ EXPECT_STREQ(tests[i].parsed_domain, auth.domain_.c_str());
+ EXPECT_STREQ(tests[i].parsed_opaque, auth.opaque_.c_str());
+ EXPECT_EQ(tests[i].parsed_stale, auth.stale_);
+ EXPECT_EQ(tests[i].parsed_algorithm, auth.algorithm_);
+ EXPECT_EQ(tests[i].parsed_qop, auth.qop_);
+ }
+}
+
+TEST(HttpAuthHandlerDigestTest, AssembleCredentials) {
+ static const struct {
+ const char* req_method;
+ const char* req_path;
+ const char* challenge;
+ const char* username;
+ const char* password;
+ const char* cnonce;
+ int nonce_count;
+ const char* expected_creds;
+ } tests[] = {
+ { // MD5 with username/password
+ "GET",
+ "/test/drealm1",
+
+ // Challenge
+ "Digest realm=\"DRealm1\", "
+ "nonce=\"claGgoRXBAA=7583377687842fdb7b56ba0555d175baa0b800e3\", "
+ "algorithm=MD5, qop=\"auth\"",
+
+ "foo", "bar", // username/password
+ "082c875dcb2ca740", // cnonce
+ 1, // nc
+
+ // Authorization
+ "Digest username=\"foo\", realm=\"DRealm1\", "
+ "nonce=\"claGgoRXBAA=7583377687842fdb7b56ba0555d175baa0b800e3\", "
+ "uri=\"/test/drealm1\", algorithm=MD5, "
+ "response=\"bcfaa62f1186a31ff1b474a19a17cf57\", "
+ "qop=auth, nc=00000001, cnonce=\"082c875dcb2ca740\""
+ },
+
+ { // MD5 with username but empty password. username has space in it.
+ "GET",
+ "/test/drealm1/",
+
+ // Challenge
+ "Digest realm=\"DRealm1\", "
+ "nonce=\"Ure30oRXBAA=7eca98bbf521ac6642820b11b86bd2d9ed7edc70\", "
+ "algorithm=MD5, qop=\"auth\"",
+
+ "foo bar", "", // Username/password
+ "082c875dcb2ca740", // cnonce
+ 1, // nc
+
+ // Authorization
+ "Digest username=\"foo bar\", realm=\"DRealm1\", "
+ "nonce=\"Ure30oRXBAA=7eca98bbf521ac6642820b11b86bd2d9ed7edc70\", "
+ "uri=\"/test/drealm1/\", algorithm=MD5, "
+ "response=\"93c9c6d5930af3b0eb26c745e02b04a0\", "
+ "qop=auth, nc=00000001, cnonce=\"082c875dcb2ca740\""
+ },
+
+ { // No algorithm, and no qop.
+ "GET",
+ "/",
+
+ // Challenge
+ "Digest realm=\"Oblivion\", nonce=\"nonce-value\"",
+
+ "FooBar", "pass", // Username/password
+ "", // cnonce
+ 1, // nc
+
+ // Authorization
+ "Digest username=\"FooBar\", realm=\"Oblivion\", "
+ "nonce=\"nonce-value\", uri=\"/\", "
+ "response=\"f72ff54ebde2f928860f806ec04acd1b\""
+ },
+
+ { // MD5-sess
+ "GET",
+ "/",
+
+ // Challenge
+ "Digest realm=\"Baztastic\", nonce=\"AAAAAAAA\", "
+ "algorithm=\"md5-sess\", qop=auth",
+
+ "USER", "123", // Username/password
+ "15c07961ed8575c4", // cnonce
+ 1, // nc
+
+ // Authorization
+ "Digest username=\"USER\", realm=\"Baztastic\", "
+ "nonce=\"AAAAAAAA\", uri=\"/\", algorithm=MD5-sess, "
+ "response=\"cbc1139821ee7192069580570c541a03\", "
+ "qop=auth, nc=00000001, cnonce=\"15c07961ed8575c4\""
+ }
+ };
+ for (int i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ HttpAuthHandlerDigest digest;
+ std::string challenge = tests[i].challenge;
+ EXPECT_TRUE(digest.InitFromChallenge(
+ challenge.begin(), challenge.end(), HttpAuth::AUTH_SERVER));
+
+ std::string creds = digest.AssembleCredentials(tests[i].req_method,
+ tests[i].req_path, tests[i].username, tests[i].password,
+ tests[i].cnonce, tests[i].nonce_count);
+
+ EXPECT_STREQ(tests[i].expected_creds, creds.c_str());
+ }
+}
+
+} // namespace net
diff --git a/net/http/http_auth_unittest.cc b/net/http/http_auth_unittest.cc
new file mode 100644
index 0000000..a82ffa1
--- /dev/null
+++ b/net/http/http_auth_unittest.cc
@@ -0,0 +1,185 @@
+// Copyright (c) 2006-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 "testing/gtest/include/gtest/gtest.h"
+
+#include "base/scoped_ptr.h"
+#include "net/http/http_auth.h"
+#include "net/http/http_auth_handler.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_util.h"
+
+namespace net {
+
+TEST(HttpAuthTest, ChooseBestChallenge) {
+ static const struct {
+ const char* headers;
+ const char* challenge_realm;
+ } tests[] = {
+ {
+ "Y: Digest realm=\"X\", nonce=\"aaaaaaaaaa\"\n"
+ "www-authenticate: Basic realm=\"BasicRealm\"\n",
+
+ // Basic is the only challenge type, pick it.
+ "BasicRealm",
+ },
+ {
+ "Y: Digest realm=\"FooBar\", nonce=\"aaaaaaaaaa\"\n"
+ "www-authenticate: Fake realm=\"FooBar\"\n",
+
+ // Fake is the only challenge type, but it is unsupported.
+ "",
+ },
+ {
+ "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",
+
+ // Pick Digset over Basic
+ "DigestRealm",
+ }
+ };
+
+ for (int i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ // Make a HttpResponseHeaders object.
+ std::string headers_with_status_line("HTTP/1.1 401 OK\n");
+ headers_with_status_line += tests[i].headers;
+ scoped_refptr<net::HttpResponseHeaders> headers(
+ new net::HttpResponseHeaders(
+ net::HttpUtil::AssembleRawHeaders(
+ headers_with_status_line.c_str(),
+ headers_with_status_line.length())));
+
+ scoped_ptr<HttpAuthHandler> handler(HttpAuth::ChooseBestChallenge(
+ headers.get(), HttpAuth::AUTH_SERVER));
+
+ if (handler.get()) {
+ EXPECT_STREQ(tests[i].challenge_realm, handler->realm().c_str());
+ } else {
+ EXPECT_STREQ("", tests[i].challenge_realm);
+ }
+ }
+}
+
+TEST(HttpAuthTest, ChallengeTokenizer) {
+ std::string challenge_str = "Basic realm=\"foobar\"";
+ HttpAuth::ChallengeTokenizer challenge(challenge_str.begin(),
+ challenge_str.end());
+ EXPECT_TRUE(challenge.valid());
+ EXPECT_EQ(std::string("Basic"), challenge.scheme());
+ EXPECT_TRUE(challenge.GetNext());
+ EXPECT_TRUE(challenge.valid());
+ EXPECT_EQ(std::string("realm"), challenge.name());
+ EXPECT_EQ(std::string("foobar"), challenge.unquoted_value());
+ EXPECT_EQ(std::string("\"foobar\""), challenge.value());
+ EXPECT_TRUE(challenge.value_is_quoted());
+ EXPECT_FALSE(challenge.GetNext());
+}
+
+// Use a name=value property with no quote marks.
+TEST(HttpAuthTest, ChallengeTokenizerNoQuotes) {
+ std::string challenge_str = "Basic realm=foobar@baz.com";
+ HttpAuth::ChallengeTokenizer challenge(challenge_str.begin(),
+ challenge_str.end());
+ EXPECT_TRUE(challenge.valid());
+ EXPECT_EQ(std::string("Basic"), challenge.scheme());
+ EXPECT_TRUE(challenge.GetNext());
+ EXPECT_TRUE(challenge.valid());
+ EXPECT_EQ(std::string("realm"), challenge.name());
+ EXPECT_EQ(std::string("foobar@baz.com"), challenge.value());
+ EXPECT_EQ(std::string("foobar@baz.com"), challenge.unquoted_value());
+ EXPECT_FALSE(challenge.value_is_quoted());
+ EXPECT_FALSE(challenge.GetNext());
+}
+
+// Use a name= property which has no value.
+TEST(HttpAuthTest, ChallengeTokenizerNoValue) {
+ std::string challenge_str = "Digest qop=";
+ HttpAuth::ChallengeTokenizer challenge(
+ challenge_str.begin(), challenge_str.end());
+ EXPECT_TRUE(challenge.valid());
+ EXPECT_EQ(std::string("Digest"), challenge.scheme());
+ EXPECT_TRUE(challenge.GetNext());
+ EXPECT_TRUE(challenge.valid());
+ EXPECT_EQ(std::string("qop"), challenge.name());
+ EXPECT_EQ(std::string(""), challenge.value());
+ EXPECT_FALSE(challenge.value_is_quoted());
+ EXPECT_FALSE(challenge.GetNext());
+}
+
+// Specify multiple properties, comma separated.
+TEST(HttpAuthTest, ChallengeTokenizerMultiple) {
+ std::string challenge_str =
+ "Digest algorithm=md5, realm=\"Oblivion\", qop=auth-int";
+ HttpAuth::ChallengeTokenizer challenge(challenge_str.begin(),
+ challenge_str.end());
+ EXPECT_TRUE(challenge.valid());
+ EXPECT_EQ(std::string("Digest"), challenge.scheme());
+ EXPECT_TRUE(challenge.GetNext());
+ EXPECT_TRUE(challenge.valid());
+ EXPECT_EQ(std::string("algorithm"), challenge.name());
+ EXPECT_EQ(std::string("md5"), challenge.value());
+ EXPECT_FALSE(challenge.value_is_quoted());
+ EXPECT_TRUE(challenge.GetNext());
+ EXPECT_TRUE(challenge.valid());
+ EXPECT_EQ(std::string("realm"), challenge.name());
+ EXPECT_EQ(std::string("Oblivion"), challenge.unquoted_value());
+ EXPECT_TRUE(challenge.value_is_quoted());
+ EXPECT_TRUE(challenge.GetNext());
+ EXPECT_TRUE(challenge.valid());
+ EXPECT_EQ(std::string("qop"), challenge.name());
+ EXPECT_EQ(std::string("auth-int"), challenge.value());
+ EXPECT_FALSE(challenge.value_is_quoted());
+ EXPECT_FALSE(challenge.GetNext());
+}
+
+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());
+}
+
+TEST(HttpAuthTest, CreateAuthHandler) {
+ {
+ scoped_ptr<HttpAuthHandler> handler(
+ HttpAuth::CreateAuthHandler("Basic realm=\"FooBar\"",
+ HttpAuth::AUTH_SERVER));
+ EXPECT_FALSE(handler.get() == NULL);
+ EXPECT_STREQ("basic", handler->scheme().c_str());
+ EXPECT_STREQ("FooBar", handler->realm().c_str());
+ EXPECT_EQ(HttpAuth::AUTH_SERVER, handler->target());
+ }
+ {
+ scoped_ptr<HttpAuthHandler> handler(
+ HttpAuth::CreateAuthHandler("UNSUPPORTED realm=\"FooBar\"",
+ HttpAuth::AUTH_SERVER));
+ EXPECT_TRUE(handler.get() == NULL);
+ }
+ {
+ scoped_ptr<HttpAuthHandler> handler(HttpAuth::CreateAuthHandler(
+ "Digest realm=\"FooBar\", nonce=\"xyz\"",
+ HttpAuth::AUTH_PROXY));
+ EXPECT_FALSE(handler.get() == NULL);
+ EXPECT_STREQ("digest", handler->scheme().c_str());
+ EXPECT_STREQ("FooBar", handler->realm().c_str());
+ EXPECT_EQ(HttpAuth::AUTH_PROXY, handler->target());
+ }
+}
+
+} // namespace net
diff --git a/net/http/http_network_transaction.cc b/net/http/http_network_transaction.cc
index ea0f3d7..c518f14 100644
--- a/net/http/http_network_transaction.cc
+++ b/net/http/http_network_transaction.cc
@@ -4,6 +4,7 @@
#include "net/http/http_network_transaction.h"
+#include "base/scoped_ptr.h"
#include "base/compiler_specific.h"
#include "base/string_util.h"
#include "base/trace_event.h"
@@ -11,10 +12,13 @@
#include "net/base/client_socket_factory.h"
#include "net/base/host_resolver.h"
#include "net/base/load_flags.h"
+#include "net/base/net_util.h"
#if defined(OS_WIN)
#include "net/base/ssl_client_socket.h"
#endif
#include "net/base/upload_data_stream.h"
+#include "net/http/http_auth.h"
+#include "net/http/http_auth_handler.h"
#include "net/http/http_chunked_decoder.h"
#include "net/http/http_network_session.h"
#include "net/http/http_request_info.h"
@@ -22,6 +26,8 @@
// TODO(darin):
// - authentication
+// + pre-emptive authorization
+// + use the username/password encoded in the URL.
// - proxies (need to call ReconsiderProxyAfterError and handle SSL tunnel)
// - ssl
@@ -86,7 +92,33 @@ int HttpNetworkTransaction::RestartWithAuth(
const std::wstring& username,
const std::wstring& password,
CompletionCallback* callback) {
- return ERR_FAILED; // TODO(darin): implement me!
+
+ DCHECK(NeedAuth(HttpAuth::AUTH_PROXY) ||
+ NeedAuth(HttpAuth::AUTH_SERVER));
+
+ // Figure out whether this username password is for proxy or server.
+ // Proxy gets set first, then server.
+ HttpAuth::Target target = NeedAuth(HttpAuth::AUTH_PROXY) ?
+ HttpAuth::AUTH_PROXY : HttpAuth::AUTH_SERVER;
+
+ // Update the username/password.
+ auth_data_[target]->state = AUTH_STATE_HAVE_AUTH;
+ auth_data_[target]->username = username;
+ auth_data_[target]->password = password;
+
+ next_state_ = STATE_INIT_CONNECTION;
+ connection_.set_socket(NULL);
+ connection_.Reset();
+
+ // Reset the other member variables.
+ ResetStateForRestart();
+
+ DCHECK(user_callback_ == NULL);
+ int rv = DoLoop(OK);
+ if (rv == ERR_IO_PENDING)
+ user_callback_ = callback;
+
+ return rv;
}
int HttpNetworkTransaction::Read(char* buf, int buf_len,
@@ -199,6 +231,8 @@ void HttpNetworkTransaction::BuildRequestHeaders() {
request_headers_ += "Cache-Control: max-age=0\r\n";
}
+ ApplyAuth();
+
// TODO(darin): Need to prune out duplicate headers.
request_headers_ += request_->extra_headers;
@@ -209,12 +243,7 @@ void HttpNetworkTransaction::BuildRequestHeaders() {
// in draft-luotonen-web-proxy-tunneling-01.txt and RFC 2817, Sections 5.2 and
// 5.3.
void HttpNetworkTransaction::BuildTunnelRequest() {
- std::string port;
- if (request_->url.has_port()) {
- port = request_->url.port();
- } else {
- port = "443";
- }
+ std::string port = GetImplicitPort(request_->url);
// RFC 2616 Section 9 says the Host request-header field MUST accompany all
// HTTP/1.1 requests.
@@ -227,8 +256,7 @@ void HttpNetworkTransaction::BuildTunnelRequest() {
if (!request_->user_agent.empty())
request_headers_ += "User-Agent: " + request_->user_agent + "\r\n";
- // TODO(wtc): Add the Proxy-Authorization header, if necessary, to handle
- // proxy authentication.
+ ApplyAuth();
request_headers_ += "\r\n";
}
@@ -765,10 +793,13 @@ int HttpNetworkTransaction::DidReadResponseHeaders() {
return OK;
}
- // TODO(wtc): Handle 401 server or 407 proxy authentication challenge.
response_.headers = headers;
response_.vary_data.Init(*request_, *response_.headers);
+ int rv = PopulateAuthChallenge();
+ if (rv != OK)
+ return rv;
+
// Figure how to determine EOF:
// For certain responses, we know the content length is always 0.
@@ -858,6 +889,21 @@ int HttpNetworkTransaction::HandleIOError(int error) {
return error;
}
+void HttpNetworkTransaction::ResetStateForRestart() {
+ header_buf_.reset();
+ header_buf_capacity_ = 0;
+ header_buf_len_ = 0;
+ header_buf_body_offset_ = -1;
+ header_buf_http_offset_ = -1;
+ content_length_ = -1;
+ content_read_ = 0;
+ read_buf_ = NULL;
+ read_buf_len_ = 0;
+ request_headers_.clear();
+ request_headers_bytes_sent_ = 0;
+ chunked_decoder_.reset();
+}
+
bool HttpNetworkTransaction::ShouldResendRequest() {
// NOTE: we resend a request only if we reused a keep-alive connection.
// This automatically prevents an infinite resend loop because we'll run
@@ -876,5 +922,95 @@ bool HttpNetworkTransaction::ShouldResendRequest() {
return true;
}
-} // namespace net
+void HttpNetworkTransaction::AddAuthorizationHeader(HttpAuth::Target target) {
+ DCHECK(HaveAuth(target));
+ DCHECK(!auth_cache_key_[target].empty());
+
+ // Add auth data to cache
+ session_->auth_cache()->Add(auth_cache_key_[target], auth_data_[target]);
+
+ // Add a Authorization/Proxy-Authorization header line.
+ std::string credentials = auth_handler_[target]->GenerateCredentials(
+ auth_data_[target]->username,
+ auth_data_[target]->password,
+ request_,
+ &proxy_info_);
+ request_headers_ += HttpAuth::GetAuthorizationHeaderName(target) +
+ ": " + credentials + "\r\n";
+}
+
+void HttpNetworkTransaction::ApplyAuth() {
+ // We expect using_proxy_ and using_tunnel_ to be mutually exclusive.
+ DCHECK(!using_proxy_ || !using_tunnel_);
+
+ // Don't send proxy auth after tunnel has been established.
+ bool should_apply_proxy_auth = using_proxy_ || establishing_tunnel_;
+
+ // Don't send origin server auth while establishing tunnel.
+ bool should_apply_server_auth = !establishing_tunnel_;
+
+ if (should_apply_proxy_auth && HaveAuth(HttpAuth::AUTH_PROXY))
+ AddAuthorizationHeader(HttpAuth::AUTH_PROXY);
+ if (should_apply_server_auth && HaveAuth(HttpAuth::AUTH_SERVER))
+ AddAuthorizationHeader(HttpAuth::AUTH_SERVER);
+}
+
+// Populates response_.auth_challenge with the authentication challenge info.
+// This info is consumed by URLRequestHttpJob::GetAuthChallengeInfo().
+int HttpNetworkTransaction::PopulateAuthChallenge() {
+ DCHECK(response_.headers);
+
+ int status = response_.headers->response_code();
+ if (status != 401 && status != 407)
+ return OK;
+ HttpAuth::Target target = status == 407 ?
+ HttpAuth::AUTH_PROXY : HttpAuth::AUTH_SERVER;
+
+ if (target == HttpAuth::AUTH_PROXY && proxy_info_.is_direct()) {
+ // TODO(eroman): Add a unique error code.
+ return ERR_INVALID_RESPONSE;
+ }
+
+ // Find the best authentication challenge that we support.
+ scoped_ptr<HttpAuthHandler> auth_handler(
+ HttpAuth::ChooseBestChallenge(response_.headers.get(), target));
+ // We found no supported challenge -- let the transaction continue
+ // so we end up displaying the error page.
+ if (!auth_handler.get())
+ return OK;
+
+ // Construct an AuthChallengeInfo.
+ scoped_refptr<AuthChallengeInfo> auth_info = new AuthChallengeInfo;
+ auth_info->is_proxy = target == HttpAuth::AUTH_PROXY;
+ auth_info->scheme = ASCIIToWide(auth_handler->scheme());
+ // TODO(eroman): decode realm according to RFC 2047.
+ auth_info->realm = ASCIIToWide(auth_handler->realm());
+ if (target == HttpAuth::AUTH_PROXY) {
+ auth_info->host = ASCIIToWide(proxy_info_.proxy_server());
+ } else {
+ DCHECK(target == HttpAuth::AUTH_SERVER);
+ auth_info->host = ASCIIToWide(request_->url.host());
+ }
+
+ // Update the auth cache key and remove any data in the auth cache.
+ if (!auth_data_[target])
+ auth_data_[target] = new AuthData;
+ auth_cache_key_[target] = AuthCache::HttpKey(request_->url, *auth_info);
+ DCHECK(!auth_cache_key_[target].empty());
+ auth_data_[target]->scheme = auth_info->scheme;
+ if (auth_data_[target]->state == AUTH_STATE_HAVE_AUTH) {
+ // The cached identity probably isn't valid so remove it.
+ // The assumption here is that the cached auth data is what we
+ // just used.
+ session_->auth_cache()->Remove(auth_cache_key_[target]);
+ auth_data_[target]->state = AUTH_STATE_NEED_AUTH;
+ }
+
+ response_.auth_challenge.swap(auth_info);
+ auth_handler_[target].reset(auth_handler.release());
+
+ return OK;
+}
+
+} // namespace net
diff --git a/net/http/http_network_transaction.h b/net/http/http_network_transaction.h
index 5334e43..533d4a2 100644
--- a/net/http/http_network_transaction.h
+++ b/net/http/http_network_transaction.h
@@ -11,6 +11,8 @@
#include "net/base/address_list.h"
#include "net/base/client_socket_handle.h"
#include "net/base/host_resolver.h"
+#include "net/http/http_auth.h"
+#include "net/http/http_auth_handler.h"
#include "net/http/http_response_info.h"
#include "net/http/http_transaction.h"
#include "net/proxy/proxy_service.h"
@@ -99,6 +101,49 @@ class HttpNetworkTransaction : public HttpTransaction {
return header_buf_http_offset_ != -1;
}
+ // Resets the members of the transaction, to rewinding next_state_.
+ void ResetStateForRestart();
+
+ // Attach any credentials needed for the proxy server or origin server.
+ void ApplyAuth();
+
+ // Helper used by ApplyAuth(). Adds either the proxy auth header, or the
+ // origin server auth header, as specified by |target|
+ void AddAuthorizationHeader(HttpAuth::Target target);
+
+ // Handles HTTP status code 401 or 407. Populates response_.auth_challenge
+ // with the required information so that URLRequestHttpJob can prompt
+ // for a username/password.
+ int PopulateAuthChallenge();
+
+ bool NeedAuth(HttpAuth::Target target) const {
+ return auth_data_[target] &&
+ auth_data_[target]->state == AUTH_STATE_NEED_AUTH;
+ }
+
+ bool HaveAuth(HttpAuth::Target target) const {
+ return auth_data_[target] &&
+ auth_data_[target]->state == AUTH_STATE_HAVE_AUTH;
+ }
+
+ // The following three auth members are arrays of size two -- index 0 is
+ // for the proxy server, and index 1 is for the origin server.
+ // Use the enum HttpAuth::Target to index into them.
+
+ // auth_handler encapsulates the logic for the particular auth-scheme.
+ // This includes the challenge's parameters. If NULL, then there is no
+ // associated auth handler.
+ scoped_ptr<HttpAuthHandler> auth_handler_[2];
+
+ // auth_data tracks the identity (username/password) that is to be
+ // applied to the proxy/origin server. The identify may have come
+ // from a login prompt, or from the auth cache. It is fed to us
+ // by URLRequestHttpJob, via RestartWithAuth().
+ scoped_refptr<AuthData> auth_data_[2];
+
+ // The key in the auth cache, for auth_data_.
+ std::string auth_cache_key_[2];
+
CompletionCallbackImpl<HttpNetworkTransaction> io_callback_;
CompletionCallback* user_callback_;
diff --git a/net/http/http_response_headers_unittest.cc b/net/http/http_response_headers_unittest.cc
index 6159e93..df3add4 100644
--- a/net/http/http_response_headers_unittest.cc
+++ b/net/http/http_response_headers_unittest.cc
@@ -436,6 +436,26 @@ TEST(HttpResponseHeadersTest, EnumerateHeader_Coalesced) {
EXPECT_EQ("no-cache=\"set-cookie,server\"", value);
EXPECT_TRUE(parsed->EnumerateHeader(&iter, "cache-control", &value));
EXPECT_EQ("no-store", value);
+ EXPECT_FALSE(parsed->EnumerateHeader(&iter, "cache-control", &value));
+}
+
+TEST(HttpResponseHeadersTest, EnumerateHeader_Challenge) {
+ // Even though WWW-Authenticate has commas, it should not be treated as
+ // coalesced values.
+ std::string headers =
+ "HTTP/1.1 401 OK\n"
+ "WWW-Authenticate:Digest realm=foobar, nonce=x, domain=y\n"
+ "WWW-Authenticate:Basic realm=quatar\n";
+ HeadersToRaw(&headers);
+ scoped_refptr<HttpResponseHeaders> parsed = new HttpResponseHeaders(headers);
+
+ void* iter = NULL;
+ std::string value;
+ EXPECT_TRUE(parsed->EnumerateHeader(&iter, "WWW-Authenticate", &value));
+ EXPECT_EQ("Digest realm=foobar, nonce=x, domain=y", value);
+ EXPECT_TRUE(parsed->EnumerateHeader(&iter, "WWW-Authenticate", &value));
+ EXPECT_EQ("Basic realm=quatar", value);
+ EXPECT_FALSE(parsed->EnumerateHeader(&iter, "WWW-Authenticate", &value));
}
TEST(HttpResponseHeadersTest, EnumerateHeader_DateValued) {
diff --git a/net/http/http_util.cc b/net/http/http_util.cc
index e204e67..d69012c 100644
--- a/net/http/http_util.cc
+++ b/net/http/http_util.cc
@@ -228,7 +228,11 @@ bool HttpUtil::IsNonCoalescingHeader(string::const_iterator name_begin,
"last-modified",
"location", // See bug 1050541 for details
"retry-after",
- "set-cookie"
+ "set-cookie",
+ // The format of auth-challenges mixes both space separated tokens and
+ // comma separated properties, so coalescing on comma won't work.
+ "www-authenticate",
+ "proxy-authenticate"
};
for (size_t i = 0; i < arraysize(kNonCoalescingHeaders); ++i) {
if (LowerCaseEqualsASCII(name_begin, name_end, kNonCoalescingHeaders[i]))
@@ -252,6 +256,73 @@ void HttpUtil::TrimLWS(string::const_iterator* begin,
--(*end);
}
+// static
+bool HttpUtil::IsQuote(char c) {
+ // Single quote mark isn't actually part of quoted-text production,
+ // but apparently some servers rely on this.
+ return c == '"' || c == '\'';
+}
+
+// static
+std::string HttpUtil::Unquote(std::string::const_iterator begin,
+ std::string::const_iterator end) {
+ // Empty string
+ if (begin == end)
+ return std::string();
+
+ // Nothing to unquote.
+ if (!IsQuote(*begin))
+ return std::string(begin, end);
+
+ // No terminal quote mark.
+ if (end - begin < 2 || *begin != *(end - 1))
+ return std::string(begin, end);
+
+ // Strip quotemarks
+ ++begin;
+ --end;
+
+ // Unescape quoted-pair (defined in RFC 2616 section 2.2)
+ std::string unescaped;
+ bool prev_escape = false;
+ for (; begin != end; ++begin) {
+ char c = *begin;
+ if (c == '\\' && !prev_escape) {
+ prev_escape = true;
+ continue;
+ }
+ prev_escape = false;
+ unescaped.push_back(c);
+ }
+ return unescaped;
+}
+
+// static
+std::string HttpUtil::Unquote(const std::string& str) {
+ return Unquote(str.begin(), str.end());
+}
+
+// static
+std::string HttpUtil::Quote(const std::string& str) {
+ std::string escaped;
+ escaped.reserve(2 + str.size());
+
+ std::string::const_iterator begin = str.begin();
+ std::string::const_iterator end = str.end();
+
+ // Esape any backslashes or quotemarks within the string, and
+ // then surround with quotes.
+ escaped.push_back('"');
+ for (; begin != end; ++begin) {
+ char c = *begin;
+ if (c == '"' || c == '\\')
+ escaped.push_back('\\');
+ escaped.push_back(c);
+ }
+ escaped.push_back('"');
+ return escaped;
+}
+
// Find the "http" substring in a status line. This allows for
// some slop at the start. If the "http" string could not be found
// then returns -1.
diff --git a/net/http/http_util.h b/net/http/http_util.h
index 5262070..d46ace4 100644
--- a/net/http/http_util.h
+++ b/net/http/http_util.h
@@ -65,6 +65,23 @@ class HttpUtil {
static void TrimLWS(std::string::const_iterator* begin,
std::string::const_iterator* end);
+ // Whether the character is the start of a quotation mark.
+ static bool IsQuote(char c);
+
+ // RFC 2616 Sec 2.2:
+ // quoted-string = ( <"> *(qdtext | quoted-pair ) <"> )
+ // Unquote() strips the surrounding quotemarks off a string, and unescapes
+ // any quoted-pair to obtain the value contained by the quoted-string.
+ // If the input is not quoted, then it works like the identity function.
+ static std::string Unquote(std::string::const_iterator begin,
+ std::string::const_iterator end);
+
+ // Same as above.
+ static std::string Unquote(const std::string& str);
+
+ // The reverse of Unquote() -- escapes and surrounds with "
+ static std::string Quote(const std::string& str);
+
// Returns the start of the status line, or -1 if no status line was found.
// This allows for 4 bytes of junk to precede the status line (which is what
// mozilla does too).
diff --git a/net/http/http_util_unittest.cc b/net/http/http_util_unittest.cc
index 8cccf31..4b27a0a 100644
--- a/net/http/http_util_unittest.cc
+++ b/net/http/http_util_unittest.cc
@@ -95,6 +95,37 @@ TEST(HttpUtilTest, ValuesIterator_Blanks) {
EXPECT_FALSE(it.GetNext());
}
+TEST(HttpUtilTest, Unquote) {
+ // Replace <backslash> " with ".
+ EXPECT_STREQ("xyz\"abc", HttpUtil::Unquote("\"xyz\\\"abc\"").c_str());
+
+ // Replace <backslash> <backslash> with <backslash>
+ EXPECT_STREQ("xyz\\abc", HttpUtil::Unquote("\"xyz\\\\abc\"").c_str());
+ EXPECT_STREQ("xyz\\\\\\abc",
+ HttpUtil::Unquote("\"xyz\\\\\\\\\\\\abc\"").c_str());
+
+ // Replace <backslash> X with X
+ EXPECT_STREQ("xyzXabc", HttpUtil::Unquote("\"xyz\\Xabc\"").c_str());
+
+ // Act as identity function on unquoted inputs.
+ EXPECT_STREQ("X", HttpUtil::Unquote("X").c_str());
+ EXPECT_STREQ("\"", HttpUtil::Unquote("\"").c_str());
+
+ // Allow single quotes to act as quote marks.
+ // Not part of RFC 2616.
+ EXPECT_STREQ("x\"", HttpUtil::Unquote("'x\"'").c_str());
+}
+
+TEST(HttpUtilTest, Quote) {
+ EXPECT_STREQ("\"xyz\\\"abc\"", HttpUtil::Quote("xyz\"abc").c_str());
+
+ // Replace <backslash> <backslash> with <backslash>
+ EXPECT_STREQ("\"xyz\\\\abc\"", HttpUtil::Quote("xyz\\abc").c_str());
+
+ // Replace <backslash> X with X
+ EXPECT_STREQ("\"xyzXabc\"", HttpUtil::Quote("xyzXabc").c_str());
+}
+
TEST(HttpUtilTest, LocateEndOfHeaders) {
struct {
const char* input;
diff --git a/net/net.xcodeproj/project.pbxproj b/net/net.xcodeproj/project.pbxproj
index 12bd44b..56462c0 100644
--- a/net/net.xcodeproj/project.pbxproj
+++ b/net/net.xcodeproj/project.pbxproj
@@ -34,6 +34,10 @@
/* End PBXAggregateTarget section */
/* Begin PBXBuildFile section */
+ 0435A4660E8DD69C00E4DF08 /* http_auth.cc in Sources */ = {isa = PBXBuildFile; fileRef = 0435A4650E8DD69C00E4DF08 /* http_auth.cc */; };
+ 0435A47A0E8DD6F300E4DF08 /* http_auth_handler.cc in Sources */ = {isa = PBXBuildFile; fileRef = 0435A4790E8DD6F300E4DF08 /* http_auth_handler.cc */; };
+ 0435A4800E8DD73600E4DF08 /* http_auth_handler_basic.cc in Sources */ = {isa = PBXBuildFile; fileRef = 0435A47F0E8DD73600E4DF08 /* http_auth_handler_basic.cc */; };
+ 0435A48F0E8DD74B00E4DF08 /* http_auth_handler_digest.cc in Sources */ = {isa = PBXBuildFile; fileRef = 0435A48E0E8DD74B00E4DF08 /* http_auth_handler_digest.cc */; };
048268070E5B3B1000A30786 /* mime_util.cc in Sources */ = {isa = PBXBuildFile; fileRef = 7BED32AA0E5A181C00A747DB /* mime_util.cc */; };
048268080E5B3B3200A30786 /* http_chunked_decoder_unittest.cc in Sources */ = {isa = PBXBuildFile; fileRef = 7BED33570E5A194700A747DB /* http_chunked_decoder_unittest.cc */; };
048268090E5B3B4800A30786 /* mime_util_unittest.cc in Sources */ = {isa = PBXBuildFile; fileRef = 7BED325E0E5A181C00A747DB /* mime_util_unittest.cc */; };
@@ -391,6 +395,14 @@
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
+ 0435A4650E8DD69C00E4DF08 /* http_auth.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = http_auth.cc; sourceTree = "<group>"; };
+ 0435A4670E8DD6B300E4DF08 /* http_auth.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = http_auth.h; sourceTree = "<group>"; };
+ 0435A4790E8DD6F300E4DF08 /* http_auth_handler.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = http_auth_handler.cc; sourceTree = "<group>"; };
+ 0435A47B0E8DD6FA00E4DF08 /* http_auth_handler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = http_auth_handler.h; sourceTree = "<group>"; };
+ 0435A47F0E8DD73600E4DF08 /* http_auth_handler_basic.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = http_auth_handler_basic.cc; sourceTree = "<group>"; };
+ 0435A48D0E8DD74300E4DF08 /* http_auth_handler_basic.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = http_auth_handler_basic.h; sourceTree = "<group>"; };
+ 0435A48E0E8DD74B00E4DF08 /* http_auth_handler_digest.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = http_auth_handler_digest.cc; sourceTree = "<group>"; };
+ 0435A4900E8DD75200E4DF08 /* http_auth_handler_digest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = http_auth_handler_digest.h; sourceTree = "<group>"; };
533102E60E5E3EBF00FF8E32 /* net_util_posix.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = net_util_posix.cc; sourceTree = "<group>"; };
7B2630600E82F282001CE27F /* libevent.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = libevent.xcodeproj; path = third_party/libevent/libevent.xcodeproj; sourceTree = "<group>"; };
7B466C460E5E732900C91F63 /* platform_test_mac.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = platform_test_mac.mm; sourceTree = "<group>"; };
@@ -1013,6 +1025,14 @@
7BED33610E5A194700A747DB /* cert_status_cache.cc */,
7BED335A0E5A194700A747DB /* cert_status_cache.h */,
7BED33600E5A194700A747DB /* http_atom_list.h */,
+ 0435A4650E8DD69C00E4DF08 /* http_auth.cc */,
+ 0435A4670E8DD6B300E4DF08 /* http_auth.h */,
+ 0435A4790E8DD6F300E4DF08 /* http_auth_handler.cc */,
+ 0435A47B0E8DD6FA00E4DF08 /* http_auth_handler.h */,
+ 0435A47F0E8DD73600E4DF08 /* http_auth_handler_basic.cc */,
+ 0435A48D0E8DD74300E4DF08 /* http_auth_handler_basic.h */,
+ 0435A48E0E8DD74B00E4DF08 /* http_auth_handler_digest.cc */,
+ 0435A4900E8DD75200E4DF08 /* http_auth_handler_digest.h */,
7BED334C0E5A194700A747DB /* http_cache.cc */,
7BED33660E5A194700A747DB /* http_cache.h */,
7BED33550E5A194700A747DB /* http_cache_unittest.cc */,
@@ -1399,6 +1419,7 @@
7BA016A30E5A1EE900044150 /* gzip_header.cc in Sources */,
7B8504280E5B2E2A00730B43 /* hash.cc in Sources */,
7B82FF430E763602008F45CF /* host_resolver.cc in Sources */,
+ 0435A4660E8DD69C00E4DF08 /* http_auth.cc in Sources */,
0482692A0E5B624D00A30786 /* http_cache.cc in Sources */,
7B85042B0E5B2E2A00730B43 /* http_chunked_decoder.cc in Sources */,
E49DD3290E893336003C7A87 /* http_network_layer.cc in Sources */,
@@ -1444,6 +1465,9 @@
821F20A50E5CD414003C7E38 /* url_request_view_cache_job.cc in Sources */,
82113BBD0E892E5800E3848F /* x509_certificate.cc in Sources */,
827E139D0E81611D00183614 /* x509_certificate_mac.cc in Sources */,
+ 0435A47A0E8DD6F300E4DF08 /* http_auth_handler.cc in Sources */,
+ 0435A4800E8DD73600E4DF08 /* http_auth_handler_basic.cc in Sources */,
+ 0435A48F0E8DD74B00E4DF08 /* http_auth_handler_digest.cc in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};