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