Commit 7a94ffd6 authored by Yutaka Hirano's avatar Yutaka Hirano Committed by Commit Bot

Refactor ParsedContentHeaderFieldParameters and related classes

This is a preliminary change for implementing charset replacement in
XHR on top of ParsedContentType.

This CL
 - Adds iterator getters for ParsedContentHeaderFieldParameters.
 - Makes ParsedContentHeaderFieldParameters a list of parameters
   instead of a dictionary.
 - Holds unmodified header names instead of lowered names.
 - Removes is_valid_ from ParsedContentHeaderFieldParameters, and adds
   the boolean (as part of Optional) to its holders.
 - Removed Mode::kStrict which can be checked after the parameters are
   parsed.

Bug: 651750
Change-Id: I35db41bee74de8ef33b0b95ae56d039599c5b556
Reviewed-on: https://chromium-review.googlesource.com/781208Reviewed-by: default avatarKinuko Yasuda <kinuko@chromium.org>
Reviewed-by: default avatarMounir Lamouri <mlamouri@chromium.org>
Reviewed-by: default avatarJohn Rummell <jrummell@chromium.org>
Reviewed-by: default avatarTakeshi Yoshino <tyoshino@chromium.org>
Commit-Queue: Yutaka Hirano <yhirano@chromium.org>
Cr-Commit-Position: refs/heads/master@{#519944}
parent 9cf57f49
......@@ -57,8 +57,8 @@ static WebVector<WebMediaKeySystemMediaCapability> ConvertCapabilities(
for (size_t i = 0; i < capabilities.size(); ++i) {
const WebString& content_type = capabilities[i].contentType();
result[i].content_type = content_type;
ParsedContentType type(content_type, ParsedContentType::Mode::kStrict);
if (type.IsValid()) {
ParsedContentType type(content_type);
if (type.IsValid() && !type.GetParameters().HasDuplicatedNames()) {
// From
// http://w3c.github.io/encrypted-media/#get-supported-capabilities-for-audio-video-type
// "If the user agent does not recognize one or more parameters,
......@@ -67,7 +67,7 @@ static WebVector<WebMediaKeySystemMediaCapability> ConvertCapabilities(
// present. Chromium expects "codecs" to be provided, so this capability
// will be skipped if codecs is not the only parameter specified.
result[i].mime_type = type.MimeType();
if (type.ParameterCount() == 1u)
if (type.GetParameters().ParameterCount() == 1u)
result[i].codecs = type.ParameterValueForName("codecs");
}
result[i].robustness = capabilities[i].robustness();
......
......@@ -59,8 +59,8 @@ double ComputeFrameRate(const String& fps_str) {
}
bool IsValidMimeType(const String& content_type, const String& prefix) {
ParsedContentType parsed_content_type(content_type,
ParsedContentType::Mode::kStrict);
ParsedContentType parsed_content_type(content_type);
if (!parsed_content_type.IsValid())
return false;
......@@ -68,17 +68,15 @@ bool IsValidMimeType(const String& content_type, const String& prefix) {
!parsed_content_type.MimeType().StartsWith(kApplicationMimeTypePrefix)) {
return false;
}
const auto& parameters = parsed_content_type.GetParameters();
if (parsed_content_type.ParameterCount() > 1)
if (parameters.ParameterCount() > 1)
return false;
if (parsed_content_type.ParameterCount() == 1 &&
parsed_content_type.ParameterValueForName(kCodecsMimeTypeParam)
.IsNull()) {
return false;
}
if (parameters.ParameterCount() == 0)
return true;
return true;
return parameters.begin()->name.LowerASCII() == kCodecsMimeTypeParam;
}
bool IsValidMediaConfiguration(const MediaConfiguration& configuration) {
......@@ -113,9 +111,9 @@ WebAudioConfiguration ToWebAudioConfiguration(
// |contentType| is mandatory.
DCHECK(configuration.hasContentType());
ParsedContentType parsed_content_type(configuration.contentType(),
ParsedContentType::Mode::kStrict);
ParsedContentType parsed_content_type(configuration.contentType());
DCHECK(parsed_content_type.IsValid());
DCHECK(!parsed_content_type.GetParameters().HasDuplicatedNames());
DEFINE_STATIC_LOCAL(const String, codecs, ("codecs"));
web_configuration.mime_type = parsed_content_type.MimeType().LowerASCII();
......@@ -141,9 +139,9 @@ WebVideoConfiguration ToWebVideoConfiguration(
// All the properties are mandatory.
DCHECK(configuration.hasContentType());
ParsedContentType parsed_content_type(configuration.contentType(),
ParsedContentType::Mode::kStrict);
ParsedContentType parsed_content_type(configuration.contentType());
DCHECK(parsed_content_type.IsValid());
DCHECK(!parsed_content_type.GetParameters().HasDuplicatedNames());
DEFINE_STATIC_LOCAL(const String, codecs, ("codecs"));
web_configuration.mime_type = parsed_content_type.MimeType().LowerASCII();
......
......@@ -5,6 +5,7 @@
#ifndef HeaderFieldTokenizer_h
#define HeaderFieldTokenizer_h
#include "platform/PlatformExport.h"
#include "platform/network/ParsedContentHeaderFieldParameters.h"
#include "platform/wtf/text/WTFString.h"
......@@ -12,7 +13,7 @@ namespace blink {
// Parses header fields into tokens, quoted strings and separators.
// Commonly used by ParsedContent* classes.
class HeaderFieldTokenizer final {
class PLATFORM_EXPORT HeaderFieldTokenizer final {
STACK_ALLOCATED();
public:
......
......@@ -20,7 +20,8 @@ ParsedContentDisposition::ParsedContentDisposition(
}
type_ = type.ToString();
parameters_.ParseParameters(std::move(tokenizer), mode);
parameters_ =
ParsedContentHeaderFieldParameters::Parse(std::move(tokenizer), mode);
}
String ParsedContentDisposition::Filename() const {
......
......@@ -9,7 +9,8 @@
#include "platform/network/ParsedContentHeaderFieldParameters.h"
#include "platform/wtf/Allocator.h"
#include "platform/wtf/HashMap.h"
#include "platform/wtf/text/StringHash.h"
#include "platform/wtf/Optional.h"
#include "platform/wtf/text/WTFString.h"
namespace blink {
......@@ -29,15 +30,13 @@ class PLATFORM_EXPORT ParsedContentDisposition final {
// Note that in the case of multiple values for the same name, the last value
// is returned.
String ParameterValueForName(const String& name) const {
return parameters_.ParameterValueForName(name);
return IsValid() ? parameters_->ParameterValueForName(name) : String();
}
size_t ParameterCount() const { return parameters_.ParameterCount(); }
bool IsValid() const { return parameters_.IsValid(); }
bool IsValid() const { return !!parameters_; }
private:
String type_;
ParsedContentHeaderFieldParameters parameters_;
WTF::Optional<ParsedContentHeaderFieldParameters> parameters_;
};
} // namespace blink
......
......@@ -5,34 +5,13 @@
#include "platform/network/ParsedContentHeaderFieldParameters.h"
#include "platform/network/HeaderFieldTokenizer.h"
#include "platform/wtf/HashSet.h"
#include "platform/wtf/text/StringBuilder.h"
#include "platform/wtf/text/StringHash.h"
#include "platform/wtf/text/StringView.h"
namespace blink {
ParsedContentHeaderFieldParameters::ParsedContentHeaderFieldParameters()
: is_valid_(false) {}
// static
ParsedContentHeaderFieldParameters
ParsedContentHeaderFieldParameters::CreateForTesting(const String& input,
Mode mode) {
ParsedContentHeaderFieldParameters parameters;
parameters.ParseParameters(HeaderFieldTokenizer(input), mode);
return parameters;
}
String ParsedContentHeaderFieldParameters::ParameterValueForName(
const String& name) const {
if (!name.ContainsOnlyASCII())
return String();
return parameters_.at(name.LowerASCII());
}
size_t ParsedContentHeaderFieldParameters::ParameterCount() const {
return parameters_.size();
}
// parameters := *(";" parameter)
//
// From http://tools.ietf.org/html/rfc2045#section-5.1:
......@@ -53,17 +32,14 @@ size_t ParsedContentHeaderFieldParameters::ParameterCount() const {
// "/" / "[" / "]" / "?" / "="
// ; Must be in quoted-string,
// ; to use within parameter values
void ParsedContentHeaderFieldParameters::ParseParameters(
HeaderFieldTokenizer tokenizer,
Mode mode) {
DCHECK(!is_valid_);
DCHECK(parameters_.IsEmpty());
KeyValuePairs map;
WTF::Optional<ParsedContentHeaderFieldParameters>
ParsedContentHeaderFieldParameters::Parse(HeaderFieldTokenizer tokenizer,
Mode mode) {
NameValuePairs parameters;
while (!tokenizer.IsConsumed()) {
if (!tokenizer.Consume(';')) {
DVLOG(1) << "Failed to find ';'";
return;
return WTF::nullopt;
}
StringView key;
......@@ -71,29 +47,50 @@ void ParsedContentHeaderFieldParameters::ParseParameters(
if (!tokenizer.ConsumeToken(Mode::kNormal, key)) {
DVLOG(1) << "Invalid content parameter name. (at " << tokenizer.Index()
<< ")";
return;
return WTF::nullopt;
}
if (!tokenizer.Consume('=')) {
DVLOG(1) << "Failed to find '='";
return;
return WTF::nullopt;
}
if (!tokenizer.ConsumeTokenOrQuotedString(mode, value)) {
DVLOG(1) << "Invalid content parameter value (at " << tokenizer.Index()
<< ", for '" << key.ToString() << "').";
return;
return WTF::nullopt;
}
// As |key| is parsed as a token, it consists of ascii characters
// and hence we don't need to care about non-ascii lowercasing.
DCHECK(key.ToString().ContainsOnlyASCII());
String key_string = key.ToString().LowerASCII();
if (mode == Mode::kStrict && map.find(key_string) != map.end()) {
DVLOG(1) << "Parameter " << key_string << " is defined multiple times.";
return;
}
map.Set(key_string, value);
parameters.emplace_back(key.ToString(), value);
}
return ParsedContentHeaderFieldParameters(std::move(parameters));
}
String ParsedContentHeaderFieldParameters::ParameterValueForName(
const String& name) const {
if (!name.ContainsOnlyASCII())
return String();
String lower_name = name.LowerASCII();
for (auto i = rbegin(); i != rend(); ++i) {
if (i->name.LowerASCII() == lower_name)
return i->value;
}
return String();
}
size_t ParsedContentHeaderFieldParameters::ParameterCount() const {
return parameters_.size();
}
bool ParsedContentHeaderFieldParameters::HasDuplicatedNames() const {
HashSet<String> names;
for (const auto& parameter : parameters_) {
const String lowered_name = parameter.name.LowerASCII();
if (names.find(lowered_name) != names.end())
return true;
names.insert(lowered_name);
}
parameters_ = std::move(map);
is_valid_ = true;
return false;
}
} // namespace blink
......@@ -7,8 +7,9 @@
#include "platform/PlatformExport.h"
#include "platform/wtf/Allocator.h"
#include "platform/wtf/HashMap.h"
#include "platform/wtf/text/StringHash.h"
#include "platform/wtf/Optional.h"
#include "platform/wtf/Vector.h"
#include "platform/wtf/text/WTFString.h"
namespace blink {
......@@ -18,40 +19,52 @@ class HeaderFieldTokenizer;
// them. It is used internally by ParsedContent* classes.
// FIXME: add support for comments.
class PLATFORM_EXPORT ParsedContentHeaderFieldParameters final {
STACK_ALLOCATED();
DISALLOW_NEW_EXCEPT_PLACEMENT_NEW();
public:
struct NameValue {
NameValue(String name, String value) : name(name), value(value) {}
String name;
String value;
};
using NameValuePairs = Vector<NameValue>;
using const_iterator = NameValuePairs::const_iterator;
using reverse_const_iterator = NameValuePairs::const_reverse_iterator;
// When |Relaxed| is specified, the parser parses parameter values in a sloppy
// manner, i.e., only ';' and '"' are treated as special characters.
// See https://chromiumcodereview.appspot.com/23043002.
// When |Strict| is specified, the parser does not allow multiple values
// for the same parameter. Some RFCs based on RFC2045 (e.g. RFC6838) note that
// "It is an error for a specific parameter to be specified more than once."
enum class Mode {
kNormal,
kRelaxed,
kStrict,
};
ParsedContentHeaderFieldParameters();
// We use base::Optional instead of WTF::Optional which requires its content
// type to be fully defined. They are essentially same, so uses of this class
// can (and should) use WTF::Optional to store the returned value.
static base::Optional<ParsedContentHeaderFieldParameters> Parse(
HeaderFieldTokenizer,
Mode);
// Note that in the case of multiple values for the same name, the last value
// is returned.
String ParameterValueForName(const String&) const;
size_t ParameterCount() const;
bool HasDuplicatedNames() const;
bool IsValid() const { return is_valid_; }
void ParseParameters(HeaderFieldTokenizer, Mode);
const_iterator begin() const { return parameters_.begin(); }
const_iterator end() const { return parameters_.end(); }
static ParsedContentHeaderFieldParameters CreateForTesting(
const String&,
Mode = Mode::kNormal);
reverse_const_iterator rbegin() const { return parameters_.rbegin(); }
reverse_const_iterator rend() const { return parameters_.rend(); }
private:
typedef HashMap<String, String> KeyValuePairs;
bool is_valid_;
KeyValuePairs parameters_;
explicit ParsedContentHeaderFieldParameters(NameValuePairs parameters)
: parameters_(std::move(parameters)) {}
NameValuePairs parameters_;
};
} // namespace blink
......
......@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "platform/network/HeaderFieldTokenizer.h"
#include "platform/network/ParsedContentDisposition.h"
#include "platform/network/ParsedContentHeaderFieldParameters.h"
#include "platform/network/ParsedContentType.h"
......@@ -17,9 +18,8 @@ using Mode = ParsedContentHeaderFieldParameters::Mode;
void CheckValidity(bool expected,
const String& input,
Mode mode = Mode::kNormal) {
EXPECT_EQ(expected,
ParsedContentHeaderFieldParameters::CreateForTesting(input, mode)
.IsValid())
EXPECT_EQ(expected, !!ParsedContentHeaderFieldParameters::Parse(
HeaderFieldTokenizer(input), mode))
<< input;
const String disposition_input = "attachment" + input;
......@@ -68,22 +68,24 @@ TEST(ParsedContentHeaderFieldParametersTest, ParameterName) {
CheckValidity(true, input);
ParsedContentHeaderFieldParameters t =
ParsedContentHeaderFieldParameters::CreateForTesting(input,
Mode::kNormal);
EXPECT_TRUE(t.IsValid());
EXPECT_EQ(4u, t.ParameterCount());
EXPECT_EQ(String(), t.ParameterValueForName("a"));
EXPECT_EQ(String(), t.ParameterValueForName("x"));
EXPECT_EQ("u", t.ParameterValueForName("y"));
EXPECT_EQ("S", t.ParameterValueForName("t"));
EXPECT_EQ("t u\"x", t.ParameterValueForName("k"));
EXPECT_EQ("U", t.ParameterValueForName("Q"));
EXPECT_EQ("S", t.ParameterValueForName("T"));
WTF::Optional<ParsedContentHeaderFieldParameters> t =
ParsedContentHeaderFieldParameters::Parse(HeaderFieldTokenizer(input),
Mode::kNormal);
ASSERT_TRUE(t);
EXPECT_EQ(6u, t->ParameterCount());
EXPECT_TRUE(t->HasDuplicatedNames());
EXPECT_EQ(String(), t->ParameterValueForName("a"));
EXPECT_EQ(String(), t->ParameterValueForName("x"));
EXPECT_EQ("u", t->ParameterValueForName("y"));
EXPECT_EQ("S", t->ParameterValueForName("t"));
EXPECT_EQ("t u\"x", t->ParameterValueForName("k"));
EXPECT_EQ("U", t->ParameterValueForName("Q"));
EXPECT_EQ("S", t->ParameterValueForName("T"));
String kelvin = String::FromUTF8("\xe2\x84\xaa");
DCHECK_EQ(kelvin.LowerUnicode(AtomicString()), "k");
EXPECT_EQ(String(), t.ParameterValueForName(kelvin));
EXPECT_EQ(String(), t->ParameterValueForName(kelvin));
}
TEST(ParsedContentHeaderFieldParametersTest, RelaxedParameterName) {
......@@ -91,20 +93,72 @@ TEST(ParsedContentHeaderFieldParametersTest, RelaxedParameterName) {
CheckValidity(true, input, Mode::kRelaxed);
ParsedContentHeaderFieldParameters t =
ParsedContentHeaderFieldParameters::CreateForTesting(input,
Mode::kRelaxed);
EXPECT_TRUE(t.IsValid());
EXPECT_EQ(2u, t.ParameterCount());
EXPECT_EQ("q/t:()<>@,:\\/[]?=", t.ParameterValueForName("z"));
EXPECT_EQ("u", t.ParameterValueForName("y"));
WTF::Optional<ParsedContentHeaderFieldParameters> t =
ParsedContentHeaderFieldParameters::Parse(HeaderFieldTokenizer(input),
Mode::kRelaxed);
ASSERT_TRUE(t);
EXPECT_EQ(2u, t->ParameterCount());
EXPECT_FALSE(t->HasDuplicatedNames());
EXPECT_EQ("q/t:()<>@,:\\/[]?=", t->ParameterValueForName("z"));
EXPECT_EQ("u", t->ParameterValueForName("y"));
}
TEST(ParsedContentHeaderFieldParametersTest, BeginEnd) {
String input = "; a=b; a=c; b=d";
WTF::Optional<ParsedContentHeaderFieldParameters> t =
ParsedContentHeaderFieldParameters::Parse(HeaderFieldTokenizer(input),
Mode::kNormal);
ASSERT_TRUE(t);
EXPECT_TRUE(t->HasDuplicatedNames());
EXPECT_EQ(3u, t->ParameterCount());
auto i = t->begin();
ASSERT_NE(i, t->end());
EXPECT_EQ(i->name, "a");
EXPECT_EQ(i->value, "b");
++i;
ASSERT_NE(i, t->end());
EXPECT_EQ(i->name, "a");
EXPECT_EQ(i->value, "c");
++i;
ASSERT_NE(i, t->end());
EXPECT_EQ(i->name, "b");
EXPECT_EQ(i->value, "d");
++i;
ASSERT_EQ(i, t->end());
}
TEST(ParsedContentHeaderFieldParametersTest, StrictParameterName) {
CheckValidity(true, "", Mode::kStrict);
CheckValidity(true, "; p1=a", Mode::kStrict);
CheckValidity(false, "; p1=a; p1=b", Mode::kStrict);
CheckValidity(true, "; p1=a; p2=b", Mode::kStrict);
TEST(ParsedContentHeaderFieldParametersTest, RBeginEnd) {
String input = "; a=B; A=c; b=d";
WTF::Optional<ParsedContentHeaderFieldParameters> t =
ParsedContentHeaderFieldParameters::Parse(HeaderFieldTokenizer(input),
Mode::kNormal);
ASSERT_TRUE(t);
EXPECT_TRUE(t->HasDuplicatedNames());
EXPECT_EQ(3u, t->ParameterCount());
auto i = t->rbegin();
ASSERT_NE(i, t->rend());
EXPECT_EQ(i->name, "b");
EXPECT_EQ(i->value, "d");
++i;
ASSERT_NE(i, t->rend());
EXPECT_EQ(i->name, "A");
EXPECT_EQ(i->value, "c");
++i;
ASSERT_NE(i, t->rend());
EXPECT_EQ(i->name, "a");
EXPECT_EQ(i->value, "B");
++i;
ASSERT_EQ(i, t->rend());
}
} // namespace
......
......@@ -105,7 +105,8 @@ ParsedContentType::ParsedContentType(const String& content_type, Mode mode) {
builder.Append(subtype);
mime_type_ = builder.ToString();
parameters_.ParseParameters(std::move(tokenizer), mode);
parameters_ =
ParsedContentHeaderFieldParameters::Parse(std::move(tokenizer), mode);
}
String ParsedContentType::Charset() const {
......
......@@ -35,8 +35,8 @@
#include "platform/PlatformExport.h"
#include "platform/network/ParsedContentHeaderFieldParameters.h"
#include "platform/wtf/Allocator.h"
#include "platform/wtf/HashMap.h"
#include "platform/wtf/text/StringHash.h"
#include "platform/wtf/Optional.h"
#include "platform/wtf/text/WTFString.h"
namespace blink {
......@@ -57,15 +57,18 @@ class PLATFORM_EXPORT ParsedContentType final {
// Note that in the case of multiple values for the same name, the last value
// is returned.
String ParameterValueForName(const String& name) const {
return parameters_.ParameterValueForName(name);
return IsValid() ? parameters_->ParameterValueForName(name) : String();
}
const ParsedContentHeaderFieldParameters& GetParameters() const {
DCHECK(IsValid());
return *parameters_;
}
size_t ParameterCount() const { return parameters_.ParameterCount(); }
bool IsValid() const { return parameters_.IsValid(); }
bool IsValid() const { return !!parameters_; }
private:
String mime_type_;
ParsedContentHeaderFieldParameters parameters_;
WTF::Optional<ParsedContentHeaderFieldParameters> parameters_;
};
} // namespace blink
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment