1 // Copyright 2020 The Chromium Authors. All rights reserved.
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 "extensions/common/manifest_handlers/web_app_file_handler.h"
6
7 #include <stddef.h>
8
9 #include <memory>
10
11 #include "base/containers/flat_set.h"
12 #include "base/optional.h"
13 #include "base/strings/string_number_conversions.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "base/values.h"
16 #include "extensions/common/error_utils.h"
17 #include "extensions/common/install_warning.h"
18 #include "extensions/common/manifest_constants.h"
19
20 namespace extensions {
21
22 namespace keys = manifest_keys;
23 namespace errors = manifest_errors;
24
25 namespace {
26
LoadFileExtensions(const base::Value & entry,int * error_index)27 base::Optional<base::flat_set<std::string>> LoadFileExtensions(
28 const base::Value& entry,
29 int* error_index) {
30 auto extract_file_extension =
31 [](const base::Value& entry) -> base::Optional<std::string> {
32 if (!entry.is_string())
33 return base::nullopt;
34 std::string file_extension = entry.GetString();
35 if (file_extension.empty() || file_extension[0] != '.')
36 return base::nullopt;
37 return file_extension;
38 };
39
40 // An accept entry can validly map from a MIME type to either a single file
41 // extension (a string), or alist of file extensions.
42 if (entry.is_string()) {
43 base::Optional<std::string> file_extension = extract_file_extension(entry);
44 if (!file_extension) {
45 *error_index = 0;
46 return base::nullopt;
47 }
48 return base::flat_set<std::string>{*file_extension};
49 }
50
51 DCHECK(entry.is_list());
52 base::flat_set<std::string> file_extensions;
53 base::Value::ConstListView entry_list = entry.GetList();
54 for (size_t i = 0; i < entry_list.size(); i++) {
55 base::Optional<std::string> file_extension =
56 extract_file_extension(entry_list[i]);
57 if (!file_extension) {
58 *error_index = i;
59 return base::nullopt;
60 }
61 file_extensions.insert(*file_extension);
62 }
63 return file_extensions;
64 }
65
LoadWebAppFileHandler(const std::string & manifest_entry_index,const base::Value & manifest_entry,apps::FileHandlers * file_handlers,base::string16 * error,std::vector<InstallWarning> * install_warnings)66 bool LoadWebAppFileHandler(const std::string& manifest_entry_index,
67 const base::Value& manifest_entry,
68 apps::FileHandlers* file_handlers,
69 base::string16* error,
70 std::vector<InstallWarning>* install_warnings) {
71 DCHECK(error);
72
73 const base::Value* action = manifest_entry.FindKeyOfType(
74 keys::kWebAppFileHandlerAction, base::Value::Type::STRING);
75 if (!action) {
76 *error = ErrorUtils::FormatErrorMessageUTF16(
77 errors::kInvalidWebAppFileHandlerAction, manifest_entry_index);
78 return false;
79 }
80
81 apps::FileHandler file_handler;
82 file_handler.action = GURL(action->GetString());
83
84 if (!file_handler.action.is_valid()) {
85 *error = ErrorUtils::FormatErrorMessageUTF16(
86 errors::kInvalidWebAppFileHandlerAction, manifest_entry_index);
87 return false;
88 }
89
90 const base::Value* accept = manifest_entry.FindKeyOfType(
91 keys::kWebAppFileHandlerAccept, base::Value::Type::DICTIONARY);
92 if (!accept) {
93 *error = ErrorUtils::FormatErrorMessageUTF16(
94 errors::kInvalidWebAppFileHandlerAccept, manifest_entry_index);
95 return false;
96 }
97
98 if (accept->DictEmpty()) {
99 *error = ErrorUtils::FormatErrorMessageUTF16(
100 errors::kInvalidWebAppFileHandlerEmptyAccept, manifest_entry_index);
101 return false;
102 }
103
104 for (const auto& manifest_accept_entry : accept->DictItems()) {
105 apps::FileHandler::AcceptEntry accept_entry;
106 accept_entry.mime_type = manifest_accept_entry.first;
107
108 if (!manifest_accept_entry.second.is_string() &&
109 !manifest_accept_entry.second.is_list()) {
110 *error = ErrorUtils::FormatErrorMessageUTF16(
111 errors::kInvalidWebAppFileHandlerFileExtensions, manifest_entry_index,
112 accept_entry.mime_type);
113 return false;
114 }
115
116 int error_index = -1;
117 base::Optional<base::flat_set<std::string>> file_extensions =
118 LoadFileExtensions(manifest_accept_entry.second, &error_index);
119 if (!file_extensions) {
120 *error = ErrorUtils::FormatErrorMessageUTF16(
121 errors::kInvalidWebAppFileHandlerFileExtension, manifest_entry_index,
122 accept_entry.mime_type, base::NumberToString(error_index));
123 return false;
124 }
125
126 accept_entry.file_extensions = std::move(*file_extensions);
127 file_handler.accept.push_back(std::move(accept_entry));
128 }
129
130 file_handlers->push_back(std::move(file_handler));
131 return true;
132 }
133
134 } // namespace
135
136 WebAppFileHandlers::WebAppFileHandlers() = default;
137 WebAppFileHandlers::~WebAppFileHandlers() = default;
138
139 // static
GetWebAppFileHandlers(const Extension * extension)140 const apps::FileHandlers* WebAppFileHandlers::GetWebAppFileHandlers(
141 const Extension* extension) {
142 if (!extension)
143 return nullptr;
144 WebAppFileHandlers* manifest_data = static_cast<WebAppFileHandlers*>(
145 extension->GetManifestData(keys::kWebAppFileHandlers));
146 return manifest_data ? &manifest_data->file_handlers : nullptr;
147 }
148
149 WebAppFileHandlersParser::WebAppFileHandlersParser() = default;
150 WebAppFileHandlersParser::~WebAppFileHandlersParser() = default;
151
Parse(Extension * extension,base::string16 * error)152 bool WebAppFileHandlersParser::Parse(Extension* extension,
153 base::string16* error) {
154 // The "web_app_file_handlers" key is only available for Bookmark Apps.
155 // Including it elsewhere results in an install warning, and the file handlers
156 // are not parsed.
157 if (!extension->from_bookmark()) {
158 extension->AddInstallWarning(
159 InstallWarning(errors::kInvalidWebAppFileHandlersNotBookmarkApp));
160 return true;
161 }
162
163 std::unique_ptr<WebAppFileHandlers> manifest_data =
164 std::make_unique<WebAppFileHandlers>();
165
166 const base::Value* file_handlers = nullptr;
167 if (!extension->manifest()->GetList(keys::kWebAppFileHandlers,
168 &file_handlers)) {
169 *error = base::ASCIIToUTF16(errors::kInvalidWebAppFileHandlers);
170 return false;
171 }
172
173 std::vector<InstallWarning> install_warnings;
174
175 base::Value::ConstListView file_handlers_list = file_handlers->GetList();
176 for (size_t i = 0; i < file_handlers_list.size(); i++) {
177 std::string manifest_entry_index = base::NumberToString(i);
178 if (!file_handlers_list[i].is_dict()) {
179 *error = ErrorUtils::FormatErrorMessageUTF16(
180 errors::kInvalidWebAppFileHandler, manifest_entry_index);
181 return false;
182 }
183 if (!LoadWebAppFileHandler(manifest_entry_index, file_handlers_list[i],
184 &manifest_data->file_handlers, error,
185 &install_warnings)) {
186 return false;
187 }
188 }
189
190 extension->SetManifestData(keys::kWebAppFileHandlers,
191 std::move(manifest_data));
192 extension->AddInstallWarnings(std::move(install_warnings));
193 return true;
194 }
195
Keys() const196 base::span<const char* const> WebAppFileHandlersParser::Keys() const {
197 static constexpr const char* kKeys[] = {keys::kWebAppFileHandlers};
198 return kKeys;
199 }
200
201 } // namespace extensions
202