1 // Copyright 2024 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "net/device_bound_sessions/session_json_utils.h"
6
7 #include "base/json/json_reader.h"
8
9 namespace net::device_bound_sessions {
10
11 namespace {
12
ParseScope(const base::Value::Dict & scope_dict)13 SessionParams::Scope ParseScope(const base::Value::Dict& scope_dict) {
14 SessionParams::Scope scope;
15
16 std::optional<bool> include_site = scope_dict.FindBool("include_site");
17 scope.include_site = include_site.value_or(false);
18 const base::Value::List* specifications_list =
19 scope_dict.FindList("scope_specification");
20 if (!specifications_list) {
21 return scope;
22 }
23
24 for (const auto& specification : *specifications_list) {
25 const base::Value::Dict* specification_dict = specification.GetIfDict();
26 if (!specification_dict) {
27 continue;
28 }
29
30 const std::string* type = specification_dict->FindString("type");
31 const std::string* domain = specification_dict->FindString("domain");
32 const std::string* path = specification_dict->FindString("path");
33 if (type && !type->empty() && domain && !domain->empty() && path &&
34 !path->empty()) {
35 if (*type == "include") {
36 scope.specifications.push_back(SessionParams::Scope::Specification{
37 SessionParams::Scope::Specification::Type::kInclude, *domain,
38 *path});
39 } else if (*type == "exclude") {
40 scope.specifications.push_back(SessionParams::Scope::Specification{
41 SessionParams::Scope::Specification::Type::kExclude, *domain,
42 *path});
43 }
44 }
45 }
46
47 return scope;
48 }
49
ParseCredentials(const base::Value::List & credentials_list)50 std::vector<SessionParams::Credential> ParseCredentials(
51 const base::Value::List& credentials_list) {
52 std::vector<SessionParams::Credential> cookie_credentials;
53 for (const auto& json_credential : credentials_list) {
54 SessionParams::Credential credential;
55 const base::Value::Dict* credential_dict = json_credential.GetIfDict();
56 if (!credential_dict) {
57 continue;
58 }
59 const std::string* type = credential_dict->FindString("type");
60 if (!type || *type != "cookie") {
61 continue;
62 }
63 const std::string* name = credential_dict->FindString("name");
64 const std::string* attributes = credential_dict->FindString("attributes");
65 if (name && attributes) {
66 cookie_credentials.push_back(
67 SessionParams::Credential{*name, *attributes});
68 }
69 }
70
71 return cookie_credentials;
72 }
73
74 } // namespace
75
ParseSessionInstructionJson(std::string_view response_json)76 std::optional<SessionParams> ParseSessionInstructionJson(
77 std::string_view response_json) {
78 // TODO(kristianm): Skip XSSI-escapes, see for example:
79 // https://hg.mozilla.org/mozilla-central/rev/4cee9ec9155e
80 // Discuss with others if XSSI should be part of the standard.
81
82 // TODO(kristianm): Decide if the standard should require parsing
83 // to fail fully if any item is wrong, or if that item should be
84 // ignored.
85
86 std::optional<base::Value::Dict> maybe_root = base::JSONReader::ReadDict(
87 response_json, base::JSON_PARSE_RFC, /*max_depth=*/5u);
88 if (!maybe_root) {
89 return std::nullopt;
90 }
91
92 base::Value::Dict* scope_dict = maybe_root->FindDict("scope");
93
94 std::string* session_id = maybe_root->FindString("session_identifier");
95 if (!session_id || session_id->empty()) {
96 return std::nullopt;
97 }
98
99 std::string* refresh_url = maybe_root->FindString("refresh_url");
100
101 std::vector<SessionParams::Credential> credentials;
102 base::Value::List* credentials_list = maybe_root->FindList("credentials");
103 if (credentials_list) {
104 credentials = ParseCredentials(*credentials_list);
105 }
106
107 if (credentials.empty()) {
108 return std::nullopt;
109 }
110
111 return SessionParams(
112 *session_id, refresh_url ? *refresh_url : "",
113 scope_dict ? ParseScope(*scope_dict) : SessionParams::Scope{},
114 std::move(credentials));
115 }
116
117 } // namespace net::device_bound_sessions
118