0

[ChromeDriver] Add Cookie spec compliance

Make Add Cookie command compliant to W3C spec. Also added a set of
helper functions to help parsing some property types used in W3C spec.
ChromeDriver now passes all WPT WebDriver tests related to cookie.

Bug: chromedriver:2002
Change-Id: I3779a14e010bca865498a9d40710974e38ede829
Reviewed-on: https://chromium-review.googlesource.com/c/1366925
Commit-Queue: John Chen <johnchen@chromium.org>
Reviewed-by: Caleb Rouleau <crouleau@chromium.org>
Cr-Commit-Position: refs/heads/master@{#615539}
This commit is contained in:
John Chen
2018-12-11 16:01:41 +00:00
committed by Commit Bot
parent 73db295652
commit c83119b466
6 changed files with 403 additions and 10 deletions

@ -569,8 +569,8 @@ Status WebViewImpl::AddCookie(const std::string& name,
params.SetString("path", path);
params.SetBoolean("secure", secure);
params.SetBoolean("httpOnly", httpOnly);
params.SetDouble("expirationDate", expiry);
params.SetDouble("expires", expiry);
if (expiry >= 0)
params.SetDouble("expires", expiry);
std::unique_ptr<base::DictionaryValue> result;
Status status =

@ -430,3 +430,110 @@ Status NotifyCommandListenersBeforeCommand(Session* session,
}
return Status(kOk);
}
namespace {
template <typename T>
bool GetOptionalValue(const base::DictionaryValue* dict,
base::StringPiece path,
T* out_value,
bool* has_value,
bool (base::Value::*getter)(T*) const) {
if (has_value != nullptr)
*has_value = false;
const base::Value* value;
if (!dict->Get(path, &value) || value->is_none())
return true;
if ((value->*getter)(out_value)) {
if (has_value != nullptr)
*has_value = true;
return true;
}
return false;
}
} // namespace
bool GetOptionalBool(const base::DictionaryValue* dict,
base::StringPiece path,
bool* out_value,
bool* has_value) {
return GetOptionalValue(dict, path, out_value, has_value,
&base::Value::GetAsBoolean);
}
bool GetOptionalInt(const base::DictionaryValue* dict,
base::StringPiece path,
int* out_value,
bool* has_value) {
if (GetOptionalValue(dict, path, out_value, has_value,
&base::Value::GetAsInteger)) {
return true;
}
// See if we have a double that contains an int value.
double d;
if (!dict->GetDouble(path, &d))
return false;
int i = static_cast<int>(d);
if (i == d) {
*out_value = i;
if (has_value != nullptr)
*has_value = true;
return true;
}
return false;
}
bool GetOptionalDouble(const base::DictionaryValue* dict,
base::StringPiece path,
double* out_value,
bool* has_value) {
// base::Value::GetAsDouble already converts int to double if needed.
return GetOptionalValue(dict, path, out_value, has_value,
&base::Value::GetAsDouble);
}
bool GetOptionalString(const base::DictionaryValue* dict,
base::StringPiece path,
std::string* out_value,
bool* has_value) {
return GetOptionalValue(dict, path, out_value, has_value,
&base::Value::GetAsString);
}
bool GetOptionalSafeInt(const base::DictionaryValue* dict,
base::StringPiece path,
int64_t* out_value,
bool* has_value) {
// Check if we have a normal int, which is always a safe int.
int temp_int;
bool temp_has_value;
if (GetOptionalValue(dict, path, &temp_int, &temp_has_value,
&base::Value::GetAsInteger)) {
if (has_value != nullptr)
*has_value = temp_has_value;
if (temp_has_value)
*out_value = temp_int;
return true;
}
// Check if we have a double, which may or may not contain a safe int value.
double temp_double;
if (!dict->GetDouble(path, &temp_double))
return false;
// Verify that the value is an integer.
int64_t temp_int64 = static_cast<int64_t>(temp_double);
if (temp_int64 != temp_double)
return false;
// Verify that the value is in the range for safe integer.
if (temp_int64 >= (1ll << 53) || temp_int64 <= -(1ll << 53))
return false;
// Got a good value.
*out_value = temp_int64;
if (has_value != nullptr)
*has_value = true;
return true;
}

@ -7,6 +7,8 @@
#include <string>
#include "base/values.h"
namespace base {
class FilePath;
class ListValue;
@ -45,4 +47,45 @@ Status UnzipSoleFile(const base::FilePath& unzip_dir,
Status NotifyCommandListenersBeforeCommand(Session* session,
const std::string& command_name);
// Functions to get an optional value of the given type from a dictionary.
// Each function has three different outcomes:
// * Value exists and is of right type:
// returns true, *has_value = true, *out_value gets the actual value.
// * Value does not exist, or exists with value of undefined:
// returns true, *has_value = false, *out_value is unchanged.
// * Value exists but is of wrong type (error condition):
// returns false, *has_value undefined, *out_value is unchanged.
// In addition to provide a convenient way to fetch optional values that are
// common in W3C WebDriver spec, some of these functions also handles the
// differences in the definition of an integer:
// * base::Value uses a definition similar to C++, thus 2.0 is not an integer.
// Also, integers are limited to 32-bit.
// * WebDriver spec (https://www.w3.org/TR/webdriver/#dfn-integer) defines
// integer to be a number that is unchanged under the ToInteger operation,
// thus 2.0 is an integer. Also, the spec sometimes use "safe integer"
// (https://www.w3.org/TR/webdriver/#dfn-maximum-safe-integer), whose
// absolute value can occupy up to 53 bits.
bool GetOptionalBool(const base::DictionaryValue* dict,
base::StringPiece path,
bool* out_value,
bool* has_value = nullptr);
bool GetOptionalInt(const base::DictionaryValue* dict,
base::StringPiece path,
int* out_value,
bool* has_value = nullptr);
bool GetOptionalDouble(const base::DictionaryValue* dict,
base::StringPiece path,
double* out_value,
bool* has_value = nullptr);
bool GetOptionalString(const base::DictionaryValue* dict,
base::StringPiece path,
std::string* out_value,
bool* has_value = nullptr);
// Handles "safe integer" mentioned in W3C spec,
// https://www.w3.org/TR/webdriver/#dfn-maximum-safe-integer.
bool GetOptionalSafeInt(const base::DictionaryValue* dict,
base::StringPiece path,
int64_t* out_value,
bool* has_value = nullptr);
#endif // CHROME_TEST_CHROMEDRIVER_UTIL_H_

@ -48,3 +48,225 @@ TEST(UnzipSoleFile, Archive) {
ASSERT_TRUE(base::ReadFileToString(file, &contents));
ASSERT_STREQ("COW\n", contents.c_str());
}
namespace {
const base::StringPiece key = "key";
const int64_t max_safe_int = (1ll << 53) - 1;
void DictNoInit(base::DictionaryValue* dict) {}
void DictInitNull(base::DictionaryValue* dict) {
dict->Set(key, std::make_unique<base::Value>());
}
class DictInitBool {
bool init_value;
public:
explicit DictInitBool(bool v) : init_value(v) {}
void operator()(base::DictionaryValue* dict) {
dict->SetBoolean(key, init_value);
}
};
class DictInitInt {
int init_value;
public:
explicit DictInitInt(int v) : init_value(v) {}
void operator()(base::DictionaryValue* dict) {
dict->SetInteger(key, init_value);
}
};
class DictInitDouble {
double init_value;
public:
explicit DictInitDouble(double v) : init_value(v) {}
void operator()(base::DictionaryValue* dict) {
dict->SetDouble(key, init_value);
}
};
class DictInitString {
std::string init_value;
public:
explicit DictInitString(const std::string& v) : init_value(v) {}
void operator()(base::DictionaryValue* dict) {
dict->SetString(key, init_value);
}
};
template <typename ResultType, typename DictInitFunc>
void TestGetOptionalValue(bool (*func_to_test)(const base::DictionaryValue*,
base::StringPiece,
ResultType*,
bool*),
DictInitFunc dict_init_func,
const ResultType& init_result_value,
const ResultType& expected_result_value,
bool expected_return_value,
bool expected_has_value) {
base::DictionaryValue dict;
dict_init_func(&dict);
ResultType result_value = init_result_value;
bool has_value;
bool return_value = func_to_test(&dict, key, &result_value, &has_value);
ASSERT_EQ(return_value, expected_return_value);
ASSERT_EQ(result_value, expected_result_value);
if (return_value)
ASSERT_EQ(has_value, expected_has_value);
result_value = init_result_value;
return_value = func_to_test(&dict, key, &result_value, nullptr);
ASSERT_EQ(return_value, expected_return_value);
ASSERT_EQ(result_value, expected_result_value);
}
} // namespace
TEST(GetOptionalValue, BoolNone) {
TestGetOptionalValue<bool>(GetOptionalBool, DictNoInit, true, true, true,
false);
}
TEST(GetOptionalValue, IntNone) {
TestGetOptionalValue<int>(GetOptionalInt, DictNoInit, 12345, 12345, true,
false);
}
TEST(GetOptionalValue, DoubleNone) {
TestGetOptionalValue<double>(GetOptionalDouble, DictNoInit, 123.45, 123.45,
true, false);
}
TEST(GetOptionalValue, StringNone) {
TestGetOptionalValue<std::string>(GetOptionalString, DictNoInit, "abcde",
"abcde", true, false);
}
TEST(GetOptionalValue, SafeIntNone) {
TestGetOptionalValue<int64_t>(GetOptionalSafeInt, DictNoInit, 12345, 12345,
true, false);
}
TEST(GetOptionalValue, BoolNull) {
TestGetOptionalValue<bool>(GetOptionalBool, DictInitNull, true, true, true,
false);
}
TEST(GetOptionalValue, IntNull) {
TestGetOptionalValue<int>(GetOptionalInt, DictInitNull, 12345, 12345, true,
false);
}
TEST(GetOptionalValue, DoubleNull) {
TestGetOptionalValue<double>(GetOptionalDouble, DictInitNull, 123.45, 123.45,
true, false);
}
TEST(GetOptionalValue, StringNull) {
TestGetOptionalValue<std::string>(GetOptionalString, DictInitNull, "abcde",
"abcde", true, false);
}
TEST(GetOptionalValue, SafeIntNull) {
TestGetOptionalValue<int64_t>(GetOptionalSafeInt, DictInitNull, 12345, 12345,
true, false);
}
TEST(GetOptionalValue, BoolWrongType) {
TestGetOptionalValue<bool>(GetOptionalBool, DictInitString("test"), true,
true, false, false);
}
TEST(GetOptionalValue, IntWrongType) {
TestGetOptionalValue<int>(GetOptionalInt, DictInitString("test"), 12345,
12345, false, false);
}
TEST(GetOptionalValue, DoubleWrongType) {
TestGetOptionalValue<double>(GetOptionalDouble, DictInitString("test"),
123.45, 123.45, false, false);
}
TEST(GetOptionalValue, StringWrongType) {
TestGetOptionalValue<std::string>(GetOptionalString, DictInitBool(false),
"abcde", "abcde", false, false);
}
TEST(GetOptionalValue, SafeIntWrongType) {
TestGetOptionalValue<int64_t>(GetOptionalSafeInt, DictInitString("test"),
12345, 12345, false, false);
}
TEST(GetOptionalValue, BoolNoConversion) {
TestGetOptionalValue<bool>(GetOptionalBool, DictInitBool(false), true, false,
true, true);
}
TEST(GetOptionalValue, IntNoConversion) {
TestGetOptionalValue<int>(GetOptionalInt, DictInitInt(54321), 12345, 54321,
true, true);
}
TEST(GetOptionalValue, DoubleNoConversion) {
TestGetOptionalValue<double>(GetOptionalDouble, DictInitDouble(54.321),
123.45, 54.321, true, true);
}
TEST(GetOptionalValue, StringNoConversion) {
TestGetOptionalValue<std::string>(GetOptionalString, DictInitString("xyz"),
"abcde", "xyz", true, true);
}
TEST(GetOptionalValue, SafeIntNoConversion) {
TestGetOptionalValue<int64_t>(GetOptionalSafeInt, DictInitInt(54321), 12345,
54321, true, true);
}
TEST(GetOptionalValue, DoubleToInt) {
TestGetOptionalValue<int>(GetOptionalInt, DictInitDouble(54321.0), 12345,
54321, true, true);
}
TEST(GetOptionalValue, DoubleToSafeInt) {
TestGetOptionalValue<int64_t>(GetOptionalSafeInt, DictInitDouble(54321.0),
12345, 54321, true, true);
}
TEST(GetOptionalValue, DoubleToIntError) {
TestGetOptionalValue<int>(GetOptionalInt, DictInitDouble(5432.1), 12345,
12345, false, false);
}
TEST(GetOptionalValue, DoubleToSafeIntError) {
TestGetOptionalValue<int64_t>(GetOptionalSafeInt, DictInitDouble(5432.1),
12345, 12345, false, false);
}
TEST(GetOptionalValue, IntToDouble) {
TestGetOptionalValue<double>(GetOptionalDouble, DictInitInt(54), 123.45, 54.0,
true, true);
}
TEST(GetOptionalValue, SafeIntMax) {
TestGetOptionalValue<int64_t>(GetOptionalSafeInt,
DictInitDouble(max_safe_int), 12345,
max_safe_int, true, true);
}
TEST(GetOptionalValue, SafeIntMaxToIntError) {
TestGetOptionalValue<int>(GetOptionalInt, DictInitDouble(max_safe_int), 12345,
12345, false, false);
}
TEST(GetOptionalValue, SafeIntTooLarge) {
TestGetOptionalValue<int64_t>(GetOptionalSafeInt,
DictInitDouble(max_safe_int + 1), 12345, 12345,
false, false);
}

@ -47,6 +47,7 @@ namespace {
static const char kUnreachableWebDataURL[] = "chrome-error://chromewebdata/";
const char kDeprecatedUnreachableWebDataURL[] = "data:text/html,chromewebdata";
// TODO(johnchen@chromium.org): Remove when we stop supporting legacy protocol.
// Defaults to 20 years into the future when adding a cookie.
const double kDefaultCookieExpiryTime = 20*365*24*60*60;
@ -1560,17 +1561,37 @@ Status ExecuteAddCookie(Session* session,
if (status.IsError())
return status;
std::string domain;
cookie->GetString("domain", &domain);
if (!GetOptionalString(cookie, "domain", &domain))
return Status(kInvalidArgument, "invalid 'domain'");
std::string path("/");
cookie->GetString("path", &path);
if (!GetOptionalString(cookie, "path", &path))
return Status(kInvalidArgument, "invalid 'path'");
bool secure = false;
cookie->GetBoolean("secure", &secure);
if (!GetOptionalBool(cookie, "secure", &secure))
return Status(kInvalidArgument, "invalid 'secure'");
bool httpOnly = false;
cookie->GetBoolean("httpOnly", &httpOnly);
if (!GetOptionalBool(cookie, "httpOnly", &httpOnly))
return Status(kInvalidArgument, "invalid 'httpOnly'");
double expiry;
if (!cookie->GetDouble("expiry", &expiry))
expiry = (base::Time::Now() - base::Time::UnixEpoch()).InSeconds() +
kDefaultCookieExpiryTime;
bool has_value;
if (session->w3c_compliant) {
// W3C spec says expiry is a safe integer.
int64_t expiry_int64;
if (!GetOptionalSafeInt(cookie, "expiry", &expiry_int64, &has_value) ||
(has_value && expiry_int64 < 0))
return Status(kInvalidArgument, "invalid 'expiry'");
// Use negative value to indicate expiry not specified.
expiry = has_value ? static_cast<double>(expiry_int64) : -1.0;
} else {
// JSON wire protocol didn't specify the type of expiry, but ChromeDriver
// has always accepted double, so we keep that in legacy mode.
if (!GetOptionalDouble(cookie, "expiry", &expiry, &has_value) ||
(has_value && expiry < 0))
return Status(kInvalidArgument, "invalid 'expiry'");
if (!has_value)
expiry = (base::Time::Now() - base::Time::UnixEpoch()).InSeconds() +
kDefaultCookieExpiryTime;
}
return web_view->AddCookie(name, url, cookie_value, domain, path,
secure, httpOnly, expiry);
}

@ -47,7 +47,7 @@ Below is a list of all WebDriver commands and their current support in ChromeDri
| POST | /session/{session id}/execute/async | Execute Async Script | Partially Complete | [2398](https://bugs.chromium.org/p/chromedriver/issues/detail?id=2398) [2556](https://bugs.chromium.org/p/chromedriver/issues/detail?id=2556)
| GET | /session/{session id}/cookie | Get All Cookies | Complete |
| GET | /session/{session id}/cookie/{name} | Get Named Cookie | Complete |
| POST | /session/{session id}/cookie | Add Cookie | Partially Complete | [2002](https://bugs.chromium.org/p/chromedriver/issues/detail?id=2002)
| POST | /session/{session id}/cookie | Add Cookie | Complete |
| DELETE | /session/{session id}/cookie/{name} | Delete Cookie | Complete |
| DELETE | /session/{session id)/cookie | Delete All Cookies | Complete |
| POST | /session/{session id}/actions | Perform Actions | Incomplete | [1897](https://bugs.chromium.org/p/chromedriver/issues/detail?id=1897)