summaryrefslogtreecommitdiffstats
path: root/net/http
diff options
context:
space:
mode:
Diffstat (limited to 'net/http')
-rw-r--r--net/http/http_auth.cc79
-rw-r--r--net/http/http_auth.h75
-rw-r--r--net/http/http_auth_gssapi_posix.cc9
-rw-r--r--net/http/http_auth_handler_basic.cc13
-rw-r--r--net/http/http_auth_handler_digest.cc25
-rw-r--r--net/http/http_auth_handler_factory.cc5
-rw-r--r--net/http/http_auth_handler_mock.cc3
-rw-r--r--net/http/http_auth_handler_ntlm.cc10
-rw-r--r--net/http/http_auth_sspi_win.cc8
-rw-r--r--net/http/http_auth_unittest.cc193
-rw-r--r--net/http/http_util.cc73
-rw-r--r--net/http/http_util.h55
-rw-r--r--net/http/http_util_unittest.cc133
13 files changed, 434 insertions, 247 deletions
diff --git a/net/http/http_auth.cc b/net/http/http_auth.cc
index debcca7..fe7005a 100644
--- a/net/http/http_auth.cc
+++ b/net/http/http_auth.cc
@@ -88,7 +88,7 @@ void HttpAuth::ChallengeTokenizer::Init(std::string::const_iterator begin,
// is separated by 1*SP.
StringTokenizer tok(begin, end, HTTP_LWS);
if (!tok.GetNext()) {
- valid_ = false;
+ // Default param and scheme iterators provide empty strings
return;
}
@@ -96,70 +96,27 @@ void HttpAuth::ChallengeTokenizer::Init(std::string::const_iterator begin,
scheme_begin_ = tok.token_begin();
scheme_end_ = tok.token_end();
- // Everything past scheme_end_ is a (comma separated) value list.
- props_ = HttpUtil::ValuesIterator(scheme_end_, end, ',');
+ params_begin_ = scheme_end_;
+ params_end_ = end;
+ HttpUtil::TrimLWS(&params_begin_, &params_end_);
}
-// We expect properties to be formatted as one of:
-// name="value"
-// name=value
-// name=
-// Due to buggy implementations found in some embedded devices, we also
-// accept values with missing close quotemark (http://crbug.com/39836):
-// name="value
-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_;
-
- if (expect_base64_token_) {
- expect_base64_token_ = false;
- // Strip off any padding.
- // (See https://bugzilla.mozilla.org/show_bug.cgi?id=230351.)
- //
- // Our base64 decoder requires that the length be a multiple of 4.
- int encoded_length = value_end_ - value_begin_;
- while (encoded_length > 0 && encoded_length % 4 != 0 &&
- value_begin_[encoded_length - 1] == '=') {
- --encoded_length;
- --value_end_;
- }
- return true;
- }
-
- // 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;
-
- value_is_quoted_ = false;
- if (value_begin_ != value_end_ && HttpUtil::IsQuote(*value_begin_)) {
- // Trim surrounding quotemarks off the value
- if (*value_begin_ != *(value_end_ - 1) || value_begin_ + 1 == value_end_)
- value_begin_ = equals + 2; // Gracefully recover from mismatching quotes.
- else
- value_is_quoted_ = true;
- }
- return true;
+HttpUtil::NameValuePairsIterator HttpAuth::ChallengeTokenizer::param_pairs()
+ const {
+ return HttpUtil::NameValuePairsIterator(params_begin_, params_end_, ',');
}
-// If value() has quotemarks, unquote it.
-std::string HttpAuth::ChallengeTokenizer::unquoted_value() const {
- return HttpUtil::Unquote(value_begin_, value_end_);
+std::string HttpAuth::ChallengeTokenizer::base64_param() const {
+ // Strip off any padding.
+ // (See https://bugzilla.mozilla.org/show_bug.cgi?id=230351.)
+ //
+ // Our base64 decoder requires that the length be a multiple of 4.
+ int encoded_length = params_end_ - params_begin_;
+ while (encoded_length > 0 && encoded_length % 4 != 0 &&
+ params_begin_[encoded_length - 1] == '=') {
+ --encoded_length;
+ }
+ return std::string(params_begin_, params_begin_ + encoded_length);
}
// static
diff --git a/net/http/http_auth.h b/net/http/http_auth.h
index dc78f0c..a996304 100644
--- a/net/http/http_auth.h
+++ b/net/http/http_auth.h
@@ -129,23 +129,24 @@ class HttpAuth {
const std::set<std::string>& disabled_schemes,
std::string* challenge_used);
- // ChallengeTokenizer breaks up a challenge string into the the auth scheme
- // and parameter list, according to RFC 2617 Sec 1.2:
+ // 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.
+ // Depending on the challenge scheme, it may be appropriate to interpret the
+ // parameters as either a base-64 encoded string or a comma-delimited list
+ // of name-value pairs. param_pairs() and base64_param() methods are provided
+ // to support either usage.
class ChallengeTokenizer {
public:
ChallengeTokenizer(std::string::const_iterator begin,
std::string::const_iterator end)
- : props_(begin, end, ','),
- valid_(true),
- begin_(begin),
+ : begin_(begin),
end_(end),
- value_is_quoted_(false),
- expect_base64_token_(false) {
+ scheme_begin_(begin),
+ scheme_end_(begin),
+ params_begin_(end),
+ params_end_(end) {
Init(begin, end);
}
@@ -161,67 +162,21 @@ class HttpAuth {
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();
-
- // Inform the tokenizer whether the next token should be treated as a base64
- // encoded value. If |expect_base64_token| is true, |GetNext| will treat the
- // next token as a base64 encoded value, and will include the trailing '='
- // padding rather than attempt to split the token into a name/value pair.
- // In this case, |name| will be empty, and |value| will contain the token.
- // Subsequent calls to |GetNext()| will not treat the token like a base64
- // encoded token unless the caller again calls |set_expect_base64_token|.
- void set_expect_base64_token(bool expect_base64_token) {
- expect_base64_token_ = expect_base64_token;
- }
-
- // 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_; }
+ HttpUtil::NameValuePairsIterator param_pairs() const;
+ std::string base64_param() const;
private:
void Init(std::string::const_iterator begin,
std::string::const_iterator end);
- HttpUtil::ValuesIterator props_;
- bool valid_;
-
std::string::const_iterator begin_;
std::string::const_iterator end_;
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_;
- bool expect_base64_token_;
+ std::string::const_iterator params_begin_;
+ std::string::const_iterator params_end_;
};
};
diff --git a/net/http/http_auth_gssapi_posix.cc b/net/http/http_auth_gssapi_posix.cc
index fdbef63..674549a 100644
--- a/net/http/http_auth_gssapi_posix.cc
+++ b/net/http/http_auth_gssapi_posix.cc
@@ -668,12 +668,12 @@ void HttpAuthGSSAPI::Delegate() {
HttpAuth::AuthorizationResult HttpAuthGSSAPI::ParseChallenge(
HttpAuth::ChallengeTokenizer* tok) {
// Verify the challenge's auth-scheme.
- if (!tok->valid() ||
- !LowerCaseEqualsASCII(tok->scheme(), StringToLowerASCII(scheme_).c_str()))
+ if (!LowerCaseEqualsASCII(tok->scheme(), StringToLowerASCII(scheme_).c_str()))
return HttpAuth::AUTHORIZATION_RESULT_INVALID;
- tok->set_expect_base64_token(true);
- if (!tok->GetNext()) {
+ std::string encoded_auth_token = tok->base64_param();
+
+ if (encoded_auth_token.empty()) {
// If a context has already been established, an empty Negotiate challenge
// should be treated as a rejection of the current attempt.
if (scoped_sec_context_.get() != GSS_C_NO_CONTEXT)
@@ -688,7 +688,6 @@ HttpAuth::AuthorizationResult HttpAuthGSSAPI::ParseChallenge(
}
// Make sure the additional token is base64 encoded.
- std::string encoded_auth_token = tok->value();
std::string decoded_auth_token;
bool base64_rv = base::Base64Decode(encoded_auth_token, &decoded_auth_token);
if (!base64_rv)
diff --git a/net/http/http_auth_handler_basic.cc b/net/http/http_auth_handler_basic.cc
index 355ce8b..054357a 100644
--- a/net/http/http_auth_handler_basic.cc
+++ b/net/http/http_auth_handler_basic.cc
@@ -32,18 +32,19 @@ bool HttpAuthHandlerBasic::Init(HttpAuth::ChallengeTokenizer* challenge) {
bool HttpAuthHandlerBasic::ParseChallenge(
HttpAuth::ChallengeTokenizer* challenge) {
// Verify the challenge's auth-scheme.
- if (!challenge->valid() ||
- !LowerCaseEqualsASCII(challenge->scheme(), "basic"))
+ if (!LowerCaseEqualsASCII(challenge->scheme(), "basic"))
return false;
+ HttpUtil::NameValuePairsIterator parameters = challenge->param_pairs();
+
// Extract the realm (may be missing).
std::string realm;
- while (challenge->GetNext()) {
- if (LowerCaseEqualsASCII(challenge->name(), "realm"))
- realm = challenge->unquoted_value();
+ while (parameters.GetNext()) {
+ if (LowerCaseEqualsASCII(parameters.name(), "realm"))
+ realm = parameters.unquoted_value();
}
- if (!challenge->valid())
+ if (!parameters.valid())
return false;
realm_ = realm;
diff --git a/net/http/http_auth_handler_digest.cc b/net/http/http_auth_handler_digest.cc
index 7c14a47..a802213 100644
--- a/net/http/http_auth_handler_digest.cc
+++ b/net/http/http_auth_handler_digest.cc
@@ -212,15 +212,16 @@ HttpAuth::AuthorizationResult HttpAuthHandlerDigest::HandleAnotherChallenge(
// to differentiate between stale and rejected responses.
// Note that the state of the current handler is not mutated - this way if
// there is a rejection the realm hasn't changed.
- if (!challenge->valid() ||
- !LowerCaseEqualsASCII(challenge->scheme(), "digest"))
+ if (!LowerCaseEqualsASCII(challenge->scheme(), "digest"))
return HttpAuth::AUTHORIZATION_RESULT_INVALID;
+ HttpUtil::NameValuePairsIterator parameters = challenge->param_pairs();
+
// Try to find the "stale" value.
- while (challenge->GetNext()) {
- if (!LowerCaseEqualsASCII(challenge->name(), "stale"))
+ while (parameters.GetNext()) {
+ if (!LowerCaseEqualsASCII(parameters.name(), "stale"))
continue;
- if (LowerCaseEqualsASCII(challenge->unquoted_value(), "true"))
+ if (LowerCaseEqualsASCII(parameters.unquoted_value(), "true"))
return HttpAuth::AUTHORIZATION_RESULT_STALE;
}
@@ -258,24 +259,26 @@ bool HttpAuthHandlerDigest::ParseChallenge(
realm_ = nonce_ = domain_ = opaque_ = std::string();
// FAIL -- Couldn't match auth-scheme.
- if (!challenge->valid() ||
- !LowerCaseEqualsASCII(challenge->scheme(), "digest"))
+ if (!LowerCaseEqualsASCII(challenge->scheme(), "digest"))
return false;
+ HttpUtil::NameValuePairsIterator parameters = challenge->param_pairs();
+
// Loop through all the properties.
- while (challenge->GetNext()) {
- if (challenge->value().empty()) {
+ while (parameters.GetNext()) {
+ if (parameters.value().empty()) {
DLOG(INFO) << "Invalid digest property";
return false;
}
// FAIL -- couldn't parse a property.
- if (!ParseChallengeProperty(challenge->name(), challenge->unquoted_value()))
+ if (!ParseChallengeProperty(parameters.name(),
+ parameters.unquoted_value()))
return false;
}
// Check if tokenizer failed.
- if (!challenge->valid())
+ if (!parameters.valid())
return false;
// Check that a minimum set of properties were provided.
diff --git a/net/http/http_auth_handler_factory.cc b/net/http/http_auth_handler_factory.cc
index 4ad7fe8..e2e3e6a 100644
--- a/net/http/http_auth_handler_factory.cc
+++ b/net/http/http_auth_handler_factory.cc
@@ -141,11 +141,12 @@ int HttpAuthHandlerRegistryFactory::CreateAuthHandler(
int digest_nonce_count,
const BoundNetLog& net_log,
scoped_ptr<HttpAuthHandler>* handler) {
- if (!challenge->valid()) {
+ std::string scheme = challenge->scheme();
+ if (scheme.empty()) {
handler->reset();
return ERR_INVALID_RESPONSE;
}
- std::string lower_scheme = StringToLowerASCII(challenge->scheme());
+ std::string lower_scheme = StringToLowerASCII(scheme);
FactoryMap::iterator it = factory_map_.find(lower_scheme);
if (it == factory_map_.end()) {
handler->reset();
diff --git a/net/http/http_auth_handler_mock.cc b/net/http/http_auth_handler_mock.cc
index 90602d0..d1e8bb2 100644
--- a/net/http/http_auth_handler_mock.cc
+++ b/net/http/http_auth_handler_mock.cc
@@ -82,8 +82,7 @@ HttpAuth::AuthorizationResult HttpAuthHandlerMock::HandleAnotherChallenge(
HttpAuth::ChallengeTokenizer* challenge) {
if (!is_connection_based())
return HttpAuth::AUTHORIZATION_RESULT_REJECT;
- if (!challenge->valid() ||
- !LowerCaseEqualsASCII(challenge->scheme(), "mock"))
+ if (!LowerCaseEqualsASCII(challenge->scheme(), "mock"))
return HttpAuth::AUTHORIZATION_RESULT_INVALID;
return HttpAuth::AUTHORIZATION_RESULT_ACCEPT;
}
diff --git a/net/http/http_auth_handler_ntlm.cc b/net/http/http_auth_handler_ntlm.cc
index dddddb4..352b2ed 100644
--- a/net/http/http_auth_handler_ntlm.cc
+++ b/net/http/http_auth_handler_ntlm.cc
@@ -116,14 +116,16 @@ HttpAuth::AuthorizationResult HttpAuthHandlerNTLM::ParseChallenge(
// TODO(cbentzel): Most of the logic between SSPI, GSSAPI, and portable NTLM
// authentication parsing could probably be shared - just need to know if
// there was previously a challenge round.
+ // TODO(cbentzel): Write a test case to validate that auth_data_ is left empty
+ // in all failure conditions.
auth_data_.clear();
// Verify the challenge's auth-scheme.
- if (!tok->valid() || !LowerCaseEqualsASCII(tok->scheme(), "ntlm"))
+ if (!LowerCaseEqualsASCII(tok->scheme(), "ntlm"))
return HttpAuth::AUTHORIZATION_RESULT_INVALID;
- tok->set_expect_base64_token(true);
- if (!tok->GetNext()) {
+ std::string base64_param = tok->base64_param();
+ if (base64_param.empty()) {
if (!initial_challenge)
return HttpAuth::AUTHORIZATION_RESULT_REJECT;
return HttpAuth::AUTHORIZATION_RESULT_ACCEPT;
@@ -132,7 +134,7 @@ HttpAuth::AuthorizationResult HttpAuthHandlerNTLM::ParseChallenge(
return HttpAuth::AUTHORIZATION_RESULT_INVALID;
}
- auth_data_.assign(tok->value_begin(), tok->value_end());
+ auth_data_ = base64_param;
return HttpAuth::AUTHORIZATION_RESULT_ACCEPT;
#endif // defined(NTLM_SSPI)
}
diff --git a/net/http/http_auth_sspi_win.cc b/net/http/http_auth_sspi_win.cc
index 276eea4..65281e0 100644
--- a/net/http/http_auth_sspi_win.cc
+++ b/net/http/http_auth_sspi_win.cc
@@ -222,12 +222,11 @@ void HttpAuthSSPI::ResetSecurityContext() {
HttpAuth::AuthorizationResult HttpAuthSSPI::ParseChallenge(
HttpAuth::ChallengeTokenizer* tok) {
// Verify the challenge's auth-scheme.
- if (!tok->valid() ||
- !LowerCaseEqualsASCII(tok->scheme(), StringToLowerASCII(scheme_).c_str()))
+ if (!LowerCaseEqualsASCII(tok->scheme(), StringToLowerASCII(scheme_).c_str()))
return HttpAuth::AUTHORIZATION_RESULT_INVALID;
- tok->set_expect_base64_token(true);
- if (!tok->GetNext()) {
+ std::string encoded_auth_token = tok->base64_param();
+ if (encoded_auth_token.empty()) {
// If a context has already been established, an empty challenge
// should be treated as a rejection of the current attempt.
if (SecIsValidHandle(&ctxt_))
@@ -241,7 +240,6 @@ HttpAuth::AuthorizationResult HttpAuthSSPI::ParseChallenge(
return HttpAuth::AUTHORIZATION_RESULT_INVALID;
}
- std::string encoded_auth_token = tok->value();
std::string decoded_auth_token;
bool base64_rv = base::Base64Decode(encoded_auth_token, &decoded_auth_token);
if (!base64_rv)
diff --git a/net/http/http_auth_unittest.cc b/net/http/http_auth_unittest.cc
index cc62214..913e1cc 100644
--- a/net/http/http_auth_unittest.cc
+++ b/net/http/http_auth_unittest.cc
@@ -188,15 +188,17 @@ TEST(HttpAuthTest, ChallengeTokenizer) {
std::string challenge_str = "Basic realm=\"foobar\"";
HttpAuth::ChallengeTokenizer challenge(challenge_str.begin(),
challenge_str.end());
- EXPECT_TRUE(challenge.valid());
+ HttpUtil::NameValuePairsIterator parameters = challenge.param_pairs();
+
+ EXPECT_TRUE(parameters.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());
+ EXPECT_TRUE(parameters.GetNext());
+ EXPECT_TRUE(parameters.valid());
+ EXPECT_EQ(std::string("realm"), parameters.name());
+ EXPECT_EQ(std::string("foobar"), parameters.unquoted_value());
+ EXPECT_EQ(std::string("\"foobar\""), parameters.value());
+ EXPECT_TRUE(parameters.value_is_quoted());
+ EXPECT_FALSE(parameters.GetNext());
}
// Use a name=value property with no quote marks.
@@ -204,15 +206,17 @@ 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());
+ HttpUtil::NameValuePairsIterator parameters = challenge.param_pairs();
+
+ EXPECT_TRUE(parameters.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());
+ EXPECT_TRUE(parameters.GetNext());
+ EXPECT_TRUE(parameters.valid());
+ EXPECT_EQ(std::string("realm"), parameters.name());
+ EXPECT_EQ(std::string("foobar@baz.com"), parameters.value());
+ EXPECT_EQ(std::string("foobar@baz.com"), parameters.unquoted_value());
+ EXPECT_FALSE(parameters.value_is_quoted());
+ EXPECT_FALSE(parameters.GetNext());
}
// Use a name=value property with mismatching quote marks.
@@ -220,15 +224,17 @@ TEST(HttpAuthTest, ChallengeTokenizerMismatchedQuotes) {
std::string challenge_str = "Basic realm=\"foobar@baz.com";
HttpAuth::ChallengeTokenizer challenge(challenge_str.begin(),
challenge_str.end());
- EXPECT_TRUE(challenge.valid());
+ HttpUtil::NameValuePairsIterator parameters = challenge.param_pairs();
+
+ EXPECT_TRUE(parameters.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());
+ EXPECT_TRUE(parameters.GetNext());
+ EXPECT_TRUE(parameters.valid());
+ EXPECT_EQ(std::string("realm"), parameters.name());
+ EXPECT_EQ(std::string("foobar@baz.com"), parameters.value());
+ EXPECT_EQ(std::string("foobar@baz.com"), parameters.unquoted_value());
+ EXPECT_FALSE(parameters.value_is_quoted());
+ EXPECT_FALSE(parameters.GetNext());
}
// Use a name= property without a value and with mismatching quote marks.
@@ -236,14 +242,16 @@ TEST(HttpAuthTest, ChallengeTokenizerMismatchedQuotesNoValue) {
std::string challenge_str = "Basic realm=\"";
HttpAuth::ChallengeTokenizer challenge(challenge_str.begin(),
challenge_str.end());
- EXPECT_TRUE(challenge.valid());
+ HttpUtil::NameValuePairsIterator parameters = challenge.param_pairs();
+
+ EXPECT_TRUE(parameters.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(""), challenge.value());
- EXPECT_FALSE(challenge.value_is_quoted());
- EXPECT_FALSE(challenge.GetNext());
+ EXPECT_TRUE(parameters.GetNext());
+ EXPECT_TRUE(parameters.valid());
+ EXPECT_EQ(std::string("realm"), parameters.name());
+ EXPECT_EQ(std::string(""), parameters.value());
+ EXPECT_FALSE(parameters.value_is_quoted());
+ EXPECT_FALSE(parameters.GetNext());
}
// Use a name=value property with mismatching quote marks and spaces in the
@@ -252,15 +260,17 @@ TEST(HttpAuthTest, ChallengeTokenizerMismatchedQuotesSpaces) {
std::string challenge_str = "Basic realm=\"foo bar";
HttpAuth::ChallengeTokenizer challenge(challenge_str.begin(),
challenge_str.end());
- EXPECT_TRUE(challenge.valid());
+ HttpUtil::NameValuePairsIterator parameters = challenge.param_pairs();
+
+ EXPECT_TRUE(parameters.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("foo bar"), challenge.value());
- EXPECT_EQ(std::string("foo bar"), challenge.unquoted_value());
- EXPECT_FALSE(challenge.value_is_quoted());
- EXPECT_FALSE(challenge.GetNext());
+ EXPECT_TRUE(parameters.GetNext());
+ EXPECT_TRUE(parameters.valid());
+ EXPECT_EQ(std::string("realm"), parameters.name());
+ EXPECT_EQ(std::string("foo bar"), parameters.value());
+ EXPECT_EQ(std::string("foo bar"), parameters.unquoted_value());
+ EXPECT_FALSE(parameters.value_is_quoted());
+ EXPECT_FALSE(parameters.GetNext());
}
// Use multiple name=value properties with mismatching quote marks in the last
@@ -269,26 +279,28 @@ TEST(HttpAuthTest, ChallengeTokenizerMismatchedQuotesMultiple) {
std::string challenge_str = "Digest qop=, algorithm=md5, realm=\"foo";
HttpAuth::ChallengeTokenizer challenge(challenge_str.begin(),
challenge_str.end());
- EXPECT_TRUE(challenge.valid());
+ HttpUtil::NameValuePairsIterator parameters = challenge.param_pairs();
+
+ EXPECT_TRUE(parameters.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_TRUE(challenge.GetNext());
- EXPECT_TRUE(challenge.valid());
- EXPECT_EQ(std::string("algorithm"), challenge.name());
- EXPECT_EQ(std::string("md5"), challenge.value());
- EXPECT_EQ(std::string("md5"), challenge.unquoted_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("foo"), challenge.value());
- EXPECT_EQ(std::string("foo"), challenge.unquoted_value());
- EXPECT_FALSE(challenge.value_is_quoted());
- EXPECT_FALSE(challenge.GetNext());
+ EXPECT_TRUE(parameters.GetNext());
+ EXPECT_TRUE(parameters.valid());
+ EXPECT_EQ(std::string("qop"), parameters.name());
+ EXPECT_EQ(std::string(""), parameters.value());
+ EXPECT_FALSE(parameters.value_is_quoted());
+ EXPECT_TRUE(parameters.GetNext());
+ EXPECT_TRUE(parameters.valid());
+ EXPECT_EQ(std::string("algorithm"), parameters.name());
+ EXPECT_EQ(std::string("md5"), parameters.value());
+ EXPECT_EQ(std::string("md5"), parameters.unquoted_value());
+ EXPECT_FALSE(parameters.value_is_quoted());
+ EXPECT_TRUE(parameters.GetNext());
+ EXPECT_TRUE(parameters.valid());
+ EXPECT_EQ(std::string("realm"), parameters.name());
+ EXPECT_EQ(std::string("foo"), parameters.value());
+ EXPECT_EQ(std::string("foo"), parameters.unquoted_value());
+ EXPECT_FALSE(parameters.value_is_quoted());
+ EXPECT_FALSE(parameters.GetNext());
}
// Use a name= property which has no value.
@@ -296,14 +308,16 @@ TEST(HttpAuthTest, ChallengeTokenizerNoValue) {
std::string challenge_str = "Digest qop=";
HttpAuth::ChallengeTokenizer challenge(
challenge_str.begin(), challenge_str.end());
- EXPECT_TRUE(challenge.valid());
+ HttpUtil::NameValuePairsIterator parameters = challenge.param_pairs();
+
+ EXPECT_TRUE(parameters.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());
+ EXPECT_TRUE(parameters.GetNext());
+ EXPECT_TRUE(parameters.valid());
+ EXPECT_EQ(std::string("qop"), parameters.name());
+ EXPECT_EQ(std::string(""), parameters.value());
+ EXPECT_FALSE(parameters.value_is_quoted());
+ EXPECT_FALSE(parameters.GetNext());
}
// Specify multiple properties, comma separated.
@@ -312,24 +326,26 @@ TEST(HttpAuthTest, ChallengeTokenizerMultiple) {
"Digest algorithm=md5, realm=\"Oblivion\", qop=auth-int";
HttpAuth::ChallengeTokenizer challenge(challenge_str.begin(),
challenge_str.end());
- EXPECT_TRUE(challenge.valid());
+ HttpUtil::NameValuePairsIterator parameters = challenge.param_pairs();
+
+ EXPECT_TRUE(parameters.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());
+ EXPECT_TRUE(parameters.GetNext());
+ EXPECT_TRUE(parameters.valid());
+ EXPECT_EQ(std::string("algorithm"), parameters.name());
+ EXPECT_EQ(std::string("md5"), parameters.value());
+ EXPECT_FALSE(parameters.value_is_quoted());
+ EXPECT_TRUE(parameters.GetNext());
+ EXPECT_TRUE(parameters.valid());
+ EXPECT_EQ(std::string("realm"), parameters.name());
+ EXPECT_EQ(std::string("Oblivion"), parameters.unquoted_value());
+ EXPECT_TRUE(parameters.value_is_quoted());
+ EXPECT_TRUE(parameters.GetNext());
+ EXPECT_TRUE(parameters.valid());
+ EXPECT_EQ(std::string("qop"), parameters.name());
+ EXPECT_EQ(std::string("auth-int"), parameters.value());
+ EXPECT_FALSE(parameters.value_is_quoted());
+ EXPECT_FALSE(parameters.GetNext());
}
// Use a challenge which has no property.
@@ -337,9 +353,11 @@ TEST(HttpAuthTest, ChallengeTokenizerNoProperty) {
std::string challenge_str = "NTLM";
HttpAuth::ChallengeTokenizer challenge(
challenge_str.begin(), challenge_str.end());
- EXPECT_TRUE(challenge.valid());
+ HttpUtil::NameValuePairsIterator parameters = challenge.param_pairs();
+
+ EXPECT_TRUE(parameters.valid());
EXPECT_EQ(std::string("NTLM"), challenge.scheme());
- EXPECT_FALSE(challenge.GetNext());
+ EXPECT_FALSE(parameters.GetNext());
}
// Use a challenge with Base64 encoded token.
@@ -347,13 +365,10 @@ TEST(HttpAuthTest, ChallengeTokenizerBase64) {
std::string challenge_str = "NTLM SGVsbG8sIFdvcmxkCg===";
HttpAuth::ChallengeTokenizer challenge(challenge_str.begin(),
challenge_str.end());
- EXPECT_TRUE(challenge.valid());
+
EXPECT_EQ(std::string("NTLM"), challenge.scheme());
- challenge.set_expect_base64_token(true);
- EXPECT_TRUE(challenge.GetNext());
// Notice the two equal statements below due to padding removal.
- EXPECT_EQ(std::string("SGVsbG8sIFdvcmxkCg=="), challenge.value());
- EXPECT_FALSE(challenge.GetNext());
+ EXPECT_EQ(std::string("SGVsbG8sIFdvcmxkCg=="), challenge.base64_param());
}
TEST(HttpAuthTest, GetChallengeHeaderName) {
diff --git a/net/http/http_util.cc b/net/http/http_util.cc
index 1098a06..0f45ccc 100644
--- a/net/http/http_util.cc
+++ b/net/http/http_util.cc
@@ -702,4 +702,77 @@ bool HttpUtil::ValuesIterator::GetNext() {
return false;
}
+HttpUtil::NameValuePairsIterator::NameValuePairsIterator(
+ string::const_iterator begin,
+ string::const_iterator end,
+ char delimiter)
+ : props_(begin, end, delimiter),
+ valid_(true),
+ begin_(begin),
+ end_(end),
+ name_begin_(end),
+ name_end_(end),
+ value_begin_(end),
+ value_end_(end),
+ value_is_quoted_(false) {
+}
+
+// We expect properties to be formatted as one of:
+// name="value"
+// name='value'
+// name='\'value\''
+// name=value
+// name = value
+// name=
+// Due to buggy implementations found in some embedded devices, we also
+// accept values with missing close quotemark (http://crbug.com/39836):
+// name="value
+bool HttpUtil::NameValuePairsIterator::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;
+
+ TrimLWS(&name_begin_, &name_end_);
+ TrimLWS(&value_begin_, &value_end_);
+ value_is_quoted_ = false;
+ if (value_begin_ != value_end_ && HttpUtil::IsQuote(*value_begin_)) {
+ // Trim surrounding quotemarks off the value
+ if (*value_begin_ != *(value_end_ - 1) || value_begin_ + 1 == value_end_)
+ // NOTE: This is not as graceful as it sounds:
+ // * quoted-pairs will no longer be unquoted
+ // (["\"hello] should give ["hello]).
+ // * Does not detect when the final quote is escaped
+ // (["value\"] should give [value"])
+ ++value_begin_; // Gracefully recover from mismatching quotes.
+ else
+ value_is_quoted_ = true;
+ }
+
+ return true;
+}
+
+// If value() has quotemarks, unquote it.
+std::string HttpUtil::NameValuePairsIterator::unquoted_value() const {
+ return HttpUtil::Unquote(value_begin_, value_end_);
+}
+
} // namespace net
diff --git a/net/http/http_util.h b/net/http/http_util.h
index 776b876..631790b 100644
--- a/net/http/http_util.h
+++ b/net/http/http_util.h
@@ -6,6 +6,7 @@
#define NET_HTTP_HTTP_UTIL_H_
#pragma once
+#include <string>
#include <vector>
#include "base/string_tokenizer.h"
@@ -214,10 +215,10 @@ class HttpUtil {
std::string::const_iterator values_end_;
};
- // Used to iterate over deliminated values in a HTTP header. HTTP LWS is
+ // Iterates over delimited values in an HTTP header. HTTP LWS is
// automatically trimmed from the resulting values.
//
- // When using this class to iterate over response header values, beware that
+ // When using this class to iterate over response header values, be aware that
// for some headers (e.g., Last-Modified), commas are not used as delimiters.
// This iterator should be avoided for headers like that which are considered
// non-coalescing (see IsNonCoalescingHeader).
@@ -251,6 +252,56 @@ class HttpUtil {
std::string::const_iterator value_begin_;
std::string::const_iterator value_end_;
};
+
+ // Iterates over a delimited sequence of name-value pairs in an HTTP header.
+ // Each pair consists of a token (the name), an equals sign, and either a
+ // token or quoted-string (the value). Arbitrary HTTP LWS is permitted outside
+ // of and between names, values, and delimiters.
+ class NameValuePairsIterator {
+ public:
+ NameValuePairsIterator(std::string::const_iterator begin,
+ std::string::const_iterator end,
+ char delimiter);
+
+ // Advances the iterator to the next pair, if any. Returns true if there
+ // is a next pair. Use name* and value* methods to access the resultant
+ // value.
+ bool GetNext();
+
+ // Returns false if there was a parse error.
+ bool valid() const { return valid_; }
+
+ // 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:
+ HttpUtil::ValuesIterator props_;
+ bool valid_;
+
+ std::string::const_iterator begin_;
+ std::string::const_iterator 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
diff --git a/net/http/http_util_unittest.cc b/net/http/http_util_unittest.cc
index 0c018a2..4ebbd2e 100644
--- a/net/http/http_util_unittest.cc
+++ b/net/http/http_util_unittest.cc
@@ -671,3 +671,136 @@ TEST(HttpUtilTest, ParseRanges) {
}
}
}
+
+namespace {
+
+void CheckNameValuePair(HttpUtil::NameValuePairsIterator* parser,
+ bool expect_next,
+ bool expect_valid,
+ std::string expected_name,
+ std::string expected_value,
+ std::string expected_unquoted_value) {
+ ASSERT_EQ(expect_next, parser->GetNext());
+ ASSERT_EQ(expect_valid, parser->valid());
+ if (!expect_next || !expect_valid) {
+ return;
+ }
+ ASSERT_EQ(expected_name, std::string(parser->name_begin(),
+ parser->name_end()));
+ ASSERT_EQ(expected_name, parser->name());
+ ASSERT_EQ(expected_value, std::string(parser->value_begin(),
+ parser->value_end()));
+ ASSERT_EQ(expected_value, parser->value());
+ ASSERT_EQ(expected_unquoted_value, parser->unquoted_value());
+ ASSERT_EQ(expected_value != expected_unquoted_value,
+ parser->value_is_quoted());
+}
+
+void CheckInvalidNameValuePair(std::string valid_part,
+ std::string invalid_part) {
+ std::string whole_string = valid_part + invalid_part;
+
+ HttpUtil::NameValuePairsIterator valid_parser(valid_part.begin(),
+ valid_part.end(),
+ ';');
+ HttpUtil::NameValuePairsIterator invalid_parser(whole_string.begin(),
+ whole_string.end(),
+ ';');
+
+ ASSERT_TRUE(valid_parser.valid());
+ ASSERT_TRUE(invalid_parser.valid());
+
+ // Both parsers should return all the same values until "valid_parser" is
+ // exhausted.
+ while (valid_parser.GetNext()) {
+ ASSERT_TRUE(invalid_parser.GetNext());
+ ASSERT_TRUE(valid_parser.valid());
+ ASSERT_TRUE(invalid_parser.valid());
+ ASSERT_EQ(valid_parser.name(), invalid_parser.name());
+ ASSERT_EQ(valid_parser.value(), invalid_parser.value());
+ }
+
+ // valid_parser is exhausted and remains 'valid'
+ ASSERT_TRUE(valid_parser.valid());
+
+ // invalid_parser's corresponding call to GetNext also returns false...
+ ASSERT_FALSE(invalid_parser.GetNext());
+ // ...but the parser is in an invalid state.
+ ASSERT_FALSE(invalid_parser.valid());
+}
+
+} // anonymous namespace
+
+TEST(HttpUtilTest, NameValuePairsIteratorEmptyInput) {
+ std::string data = "";
+ HttpUtil::NameValuePairsIterator parser(data.begin(), data.end(), ';');
+
+ EXPECT_TRUE(parser.valid());
+ ASSERT_NO_FATAL_FAILURE(
+ CheckNameValuePair(&parser, false, true, "", "", ""));
+}
+
+TEST(HttpUtilTest, NameValuePairsIterator) {
+ std::string data = "alpha=1; beta= 2 ;cappa =' 3 ';"
+ "delta= \" 4 \"; e= \" '5'\"; e=6";
+ HttpUtil::NameValuePairsIterator parser(data.begin(), data.end(), ';');
+ EXPECT_TRUE(parser.valid());
+
+ ASSERT_NO_FATAL_FAILURE(
+ CheckNameValuePair(&parser, true, true, "alpha", "1", "1"));
+ ASSERT_NO_FATAL_FAILURE(
+ CheckNameValuePair(&parser, true, true, "beta", "2", "2"));
+ ASSERT_NO_FATAL_FAILURE(
+ CheckNameValuePair(&parser, true, true, "cappa", "' 3 '", " 3 "));
+ ASSERT_NO_FATAL_FAILURE(
+ CheckNameValuePair(&parser, true, true, "delta", "\" 4 \"", " 4 "));
+ ASSERT_NO_FATAL_FAILURE(
+ CheckNameValuePair(&parser, true, true, "e", "\" '5'\"", " '5'"));
+ ASSERT_NO_FATAL_FAILURE(
+ CheckNameValuePair(&parser, true, true, "e", "6", "6"));
+ ASSERT_NO_FATAL_FAILURE(
+ CheckNameValuePair(&parser, false, true, "", "", ""));
+}
+
+TEST(HttpUtilTest, NameValuePairsIteratorIllegalInputs) {
+ ASSERT_NO_FATAL_FAILURE(CheckInvalidNameValuePair("alpha=1", "; beta"));
+ ASSERT_NO_FATAL_FAILURE(CheckInvalidNameValuePair("", "beta"));
+
+ ASSERT_NO_FATAL_FAILURE(CheckInvalidNameValuePair("alpha=1", "; 'beta'=2"));
+ ASSERT_NO_FATAL_FAILURE(CheckInvalidNameValuePair("", "'beta'=2"));
+
+ // According to the spec this is an error, but it doesn't seem appropriate to
+ // change our behaviour to be less permissive at this time.
+ // See NameValuePairsIteratorExtraSeparators test
+ // ASSERT_NO_FATAL_FAILURE(CheckInvalidNameValuePair("alpha=1", ";; beta=2"));
+}
+
+// If we are going to support extra separators against the spec, let's just make
+// sure they work rationally.
+TEST(HttpUtilTest, NameValuePairsIteratorExtraSeparators) {
+ std::string data = " ; ;;alpha=1; ;; ; beta= 2;cappa=3;;; ; ";
+ HttpUtil::NameValuePairsIterator parser(data.begin(), data.end(), ';');
+ EXPECT_TRUE(parser.valid());
+
+ ASSERT_NO_FATAL_FAILURE(
+ CheckNameValuePair(&parser, true, true, "alpha", "1", "1"));
+ ASSERT_NO_FATAL_FAILURE(
+ CheckNameValuePair(&parser, true, true, "beta", "2", "2"));
+ ASSERT_NO_FATAL_FAILURE(
+ CheckNameValuePair(&parser, true, true, "cappa", "3", "3"));
+ ASSERT_NO_FATAL_FAILURE(
+ CheckNameValuePair(&parser, false, true, "", "", ""));
+}
+
+// See comments on the implementation of NameValuePairsIterator::GetNext
+// regarding this derogation from the spec.
+TEST(HttpUtilTest, NameValuePairsIteratorMissingEndQuote) {
+ std::string data = "name='value";
+ HttpUtil::NameValuePairsIterator parser(data.begin(), data.end(), ';');
+ EXPECT_TRUE(parser.valid());
+
+ ASSERT_NO_FATAL_FAILURE(
+ CheckNameValuePair(&parser, true, true, "name", "value", "value"));
+ ASSERT_NO_FATAL_FAILURE(
+ CheckNameValuePair(&parser, false, true, "", "", ""));
+}