| // Copyright 2019 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/services/app_service/public/cpp/intent_util.h" |
| |
| #include <algorithm> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/compiler_specific.h" |
| #include "base/containers/flat_map.h" |
| #include "base/files/file_path.h" |
| #include "base/notreached.h" |
| #include "base/strings/string_split.h" |
| #include "base/strings/string_util.h" |
| #include "base/values.h" |
| #include "components/services/app_service/public/mojom/types.mojom-shared.h" |
| #include "components/services/app_service/public/mojom/types.mojom.h" |
| #include "third_party/abseil-cpp/absl/types/optional.h" |
| #include "third_party/blink/public/common/mime_util/mime_util.h" |
| |
| namespace { |
| |
| const char kWildCardAny[] = "*"; |
| const char kMimeTypeSeparator[] = "/"; |
| constexpr size_t kMimeTypeComponentSize = 2; |
| |
| const char kActionKey[] = "action"; |
| const char kUrlKey[] = "url"; |
| const char kMimeTypeKey[] = "mime_type"; |
| const char kFileUrlsKey[] = "file_urls"; |
| const char kActivityNameKey[] = "activity_name"; |
| const char kDriveShareUrlKey[] = "drive_share_url"; |
| const char kShareTextKey[] = "share_text"; |
| const char kShareTitleKey[] = "share_title"; |
| const char kStartTypeKey[] = "start_type"; |
| const char kCategoriesKey[] = "categories"; |
| const char kDataKey[] = "data"; |
| const char kUiBypassedKey[] = "ui_bypassed"; |
| const char kExtrasKey[] = "extras"; |
| const char kMimeTypeInodeDirectory[] = "inode/directory"; |
| |
| // Get the field from the |intent| that need to be checked/matched based on |
| // |condition_type|. |
| absl::optional<std::string> GetIntentConditionValueByType( |
| apps::mojom::ConditionType condition_type, |
| const apps::mojom::IntentPtr& intent) { |
| switch (condition_type) { |
| case apps::mojom::ConditionType::kAction: |
| return intent->action; |
| case apps::mojom::ConditionType::kScheme: |
| return intent->url.has_value() |
| ? absl::optional<std::string>(intent->url->scheme()) |
| : absl::nullopt; |
| case apps::mojom::ConditionType::kHost: |
| return intent->url.has_value() |
| ? absl::optional<std::string>(intent->url->host()) |
| : absl::nullopt; |
| case apps::mojom::ConditionType::kPattern: |
| return intent->url.has_value() |
| ? absl::optional<std::string>(intent->url->path()) |
| : absl::nullopt; |
| case apps::mojom::ConditionType::kMimeType: { |
| return intent->mime_type; |
| } |
| case apps::mojom::ConditionType::kFile: { |
| // Handled in IntentMatchesFileCondition. |
| NOTREACHED(); |
| return {}; |
| } |
| } |
| } |
| |
| bool ComponentMatched(const std::string& intent_component, |
| const std::string& filter_component) { |
| return filter_component == kWildCardAny || |
| intent_component == filter_component; |
| } |
| |
| // TODO(crbug.com/1092784): Handle file path with extension with mime type. |
| // Unlike Android mime type matching logic, if the intent mime type has *, it |
| // can only match with *, not anything. The reason for this is the way we find |
| // the common mime type for multiple files. It uses * to represent more than one |
| // types in the list, which will cause an issue if we treat that as we want to |
| // match with any filter. e.g. If we select a .zip, .jep and a .txt, the common |
| // mime type will be */*, with Android matching logic, it will match with filter |
| // that has mime type video, which is not what we expected. |
| bool MimeTypeMatched(const std::string& intent_mime_type, |
| const std::string& filter_mime_type) { |
| std::vector<std::string> intent_components = |
| base::SplitString(intent_mime_type, kMimeTypeSeparator, |
| base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); |
| |
| std::vector<std::string> filter_components = |
| base::SplitString(filter_mime_type, kMimeTypeSeparator, |
| base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); |
| |
| if (intent_components.size() > kMimeTypeComponentSize || |
| filter_components.size() > kMimeTypeComponentSize || |
| intent_components.size() == 0 || filter_components.size() == 0) { |
| return false; |
| } |
| |
| // If the filter component only contain main mime type, check if main type |
| // matches. |
| if (filter_components.size() == 1) { |
| return ComponentMatched(intent_components[0], filter_components[0]); |
| } |
| |
| // If the intent component only contain main mime type, complete the |
| // mime type. |
| if (intent_components.size() == 1) { |
| intent_components.push_back(kWildCardAny); |
| } |
| |
| // Both intent and intent filter can use wildcard for mime type. |
| for (size_t i = 0; i < kMimeTypeComponentSize; i++) { |
| if (!ComponentMatched(intent_components[i], filter_components[i])) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| bool ExtensionMatched(const std::string& file_name, |
| const std::string& filter_extension) { |
| if (filter_extension == kWildCardAny) |
| return true; |
| |
| // Normalise to have a preceding ".". |
| std::string normalised_extension = filter_extension; |
| if (filter_extension.length() > 0 && filter_extension[0] != '.') { |
| normalised_extension = '.' + normalised_extension; |
| } |
| base::FilePath::StringType handler_extension = |
| base::FilePath::FromUTF8Unsafe(normalised_extension).Extension(); |
| |
| base::FilePath file_path = base::FilePath::FromUTF8Unsafe(file_name); |
| |
| // Accept files whose extension or combined extension (e.g. ".tar.gz") |
| // match the filter extension. |
| return base::FilePath::CompareEqualIgnoreCase(handler_extension, |
| file_path.Extension()) || |
| base::FilePath::CompareEqualIgnoreCase(handler_extension, |
| file_path.FinalExtension()); |
| } |
| |
| } // namespace |
| |
| namespace apps_util { |
| |
| const char kIntentActionMain[] = "main"; |
| const char kIntentActionView[] = "view"; |
| const char kIntentActionSend[] = "send"; |
| const char kIntentActionSendMultiple[] = "send_multiple"; |
| const char kIntentActionCreateNote[] = "create_note"; |
| |
| apps::mojom::IntentPtr CreateIntentFromUrl(const GURL& url) { |
| auto intent = apps::mojom::Intent::New(); |
| intent->action = kIntentActionView; |
| intent->url = url; |
| return intent; |
| } |
| |
| apps::mojom::IntentPtr CreateCreateNoteIntent() { |
| auto intent = apps::mojom::Intent::New(); |
| intent->action = kIntentActionCreateNote; |
| return intent; |
| } |
| |
| apps::mojom::IntentPtr CreateViewIntentFromFiles( |
| std::vector<apps::mojom::IntentFilePtr> files) { |
| auto intent = apps::mojom::Intent::New(); |
| intent->action = kIntentActionView; |
| intent->files = std::move(files); |
| return intent; |
| } |
| |
| apps::mojom::IntentPtr CreateShareIntentFromFiles( |
| const std::vector<GURL>& filesystem_urls, |
| const std::vector<std::string>& mime_types) { |
| DCHECK_EQ(filesystem_urls.size(), mime_types.size()); |
| auto intent = apps::mojom::Intent::New(); |
| intent->mime_type = CalculateCommonMimeType(mime_types); |
| intent->files = std::vector<apps::mojom::IntentFilePtr>{}; |
| for (size_t i = 0; i < filesystem_urls.size(); i++) { |
| auto file = apps::mojom::IntentFile::New(); |
| file->url = filesystem_urls[i]; |
| file->mime_type = mime_types.at(i); |
| intent->files->push_back(std::move(file)); |
| } |
| intent->action = filesystem_urls.size() == 1 ? kIntentActionSend |
| : kIntentActionSendMultiple; |
| return intent; |
| } |
| |
| apps::mojom::IntentPtr CreateShareIntentFromFiles( |
| const std::vector<GURL>& filesystem_urls, |
| const std::vector<std::string>& mime_types, |
| const std::string& share_text, |
| const std::string& share_title) { |
| auto intent = CreateShareIntentFromFiles(filesystem_urls, mime_types); |
| if (!share_text.empty()) |
| intent->share_text = share_text; |
| if (!share_title.empty()) |
| intent->share_title = share_title; |
| return intent; |
| } |
| |
| apps::mojom::IntentPtr CreateShareIntentFromDriveFile( |
| const GURL& filesystem_url, |
| const std::string& mime_type, |
| const GURL& drive_share_url, |
| bool is_directory) { |
| auto intent = apps::mojom::Intent::New(); |
| intent->action = kIntentActionSend; |
| if (!is_directory) { |
| intent->mime_type = mime_type; |
| intent->files = std::vector<apps::mojom::IntentFilePtr>{}; |
| auto file = apps::mojom::IntentFile::New(); |
| file->url = filesystem_url; |
| intent->files->push_back(std::move(file)); |
| } |
| if (!drive_share_url.is_empty()) { |
| intent->drive_share_url = drive_share_url; |
| } |
| return intent; |
| } |
| |
| apps::mojom::IntentPtr CreateShareIntentFromText( |
| const std::string& share_text, |
| const std::string& share_title) { |
| auto intent = apps::mojom::Intent::New(); |
| intent->action = kIntentActionSend; |
| intent->mime_type = "text/plain"; |
| intent->share_text = share_text; |
| if (!share_title.empty()) |
| intent->share_title = share_title; |
| return intent; |
| } |
| |
| apps::mojom::IntentPtr CreateIntentForActivity(const std::string& activity, |
| const std::string& start_type, |
| const std::string& category) { |
| auto intent = apps::mojom::Intent::New(); |
| intent->action = kIntentActionMain; |
| intent->activity_name = activity; |
| intent->start_type = start_type; |
| intent->categories = std::vector<std::string>{category}; |
| return intent; |
| } |
| |
| bool ConditionValueMatches( |
| const std::string& value, |
| const apps::mojom::ConditionValuePtr& condition_value) { |
| switch (condition_value->match_type) { |
| // Fallthrough as kNone and kLiteral has same matching type. |
| case apps::mojom::PatternMatchType::kNone: |
| case apps::mojom::PatternMatchType::kLiteral: |
| return value == condition_value->value; |
| case apps::mojom::PatternMatchType::kPrefix: |
| return base::StartsWith(value, condition_value->value, |
| base::CompareCase::INSENSITIVE_ASCII); |
| case apps::mojom::PatternMatchType::kSuffix: |
| return base::EndsWith(value, condition_value->value, |
| base::CompareCase::INSENSITIVE_ASCII); |
| case apps::mojom::PatternMatchType::kGlob: |
| return MatchGlob(value, condition_value->value); |
| case apps::mojom::PatternMatchType::kMimeType: |
| // kMimeType as a match for kFile is handled in FileMatchesConditionValue. |
| return MimeTypeMatched(value, condition_value->value); |
| case apps::mojom::PatternMatchType::kFileExtension: |
| case apps::mojom::PatternMatchType::kIsDirectory: { |
| // Handled in FileMatchesConditionValue. |
| NOTREACHED(); |
| return false; |
| } |
| } |
| } |
| |
| bool FileMatchesConditionValue( |
| const apps::mojom::IntentFilePtr& file, |
| const apps::mojom::ConditionValuePtr& condition_value) { |
| switch (condition_value->match_type) { |
| case apps::mojom::PatternMatchType::kNone: |
| case apps::mojom::PatternMatchType::kLiteral: |
| case apps::mojom::PatternMatchType::kPrefix: |
| case apps::mojom::PatternMatchType::kSuffix: |
| NOTREACHED(); |
| return false; |
| case apps::mojom::PatternMatchType::kGlob: |
| return MatchGlob(file->url.spec(), condition_value->value); |
| case apps::mojom::PatternMatchType::kMimeType: |
| return file->mime_type.has_value() && |
| MimeTypeMatched(file->mime_type.value(), condition_value->value); |
| case apps::mojom::PatternMatchType::kFileExtension: { |
| return ExtensionMatched(file->url.ExtractFileName(), |
| condition_value->value); |
| } |
| case apps::mojom::PatternMatchType::kIsDirectory: |
| return file->is_directory == apps::mojom::OptionalBool::kTrue; |
| } |
| } |
| |
| bool FileMatchesAnyConditionValue( |
| const apps::mojom::IntentFilePtr& file, |
| const std::vector<apps::mojom::ConditionValuePtr>& condition_values) { |
| return std::any_of( |
| condition_values.begin(), condition_values.end(), |
| [&file](const apps::mojom::ConditionValuePtr& condition_value) { |
| return FileMatchesConditionValue(file, condition_value); |
| }); |
| } |
| |
| bool IntentMatchesFileCondition(const apps::mojom::IntentPtr& intent, |
| const apps::mojom::ConditionPtr& condition) { |
| DCHECK_EQ(condition->condition_type, apps::mojom::ConditionType::kFile); |
| |
| if (!intent->files.has_value() || intent->files->empty()) { |
| return false; |
| } |
| |
| return std::all_of(intent->files->begin(), intent->files->end(), |
| [&condition](const apps::mojom::IntentFilePtr& file) { |
| return FileMatchesAnyConditionValue( |
| file, condition->condition_values); |
| }); |
| } |
| |
| bool IntentMatchesCondition(const apps::mojom::IntentPtr& intent, |
| const apps::mojom::ConditionPtr& condition) { |
| if (condition->condition_type == apps::mojom::ConditionType::kFile) { |
| return IntentMatchesFileCondition(intent, condition); |
| } |
| |
| absl::optional<std::string> value_to_match = |
| GetIntentConditionValueByType(condition->condition_type, intent); |
| if (!value_to_match.has_value()) { |
| return false; |
| } |
| |
| bool matched_any = std::any_of( |
| condition->condition_values.begin(), condition->condition_values.end(), |
| [&value_to_match](const auto& condition_value) { |
| return ConditionValueMatches(value_to_match.value(), condition_value); |
| }); |
| return matched_any; |
| } |
| |
| bool IntentMatchesFilter(const apps::mojom::IntentPtr& intent, |
| const apps::mojom::IntentFilterPtr& filter) { |
| // Intent matches with this intent filter when all of the existing conditions |
| // match. |
| for (const auto& condition : filter->conditions) { |
| if (!IntentMatchesCondition(intent, condition)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| bool FilterIsForFileExtensions(const apps::mojom::IntentFilterPtr& filter) { |
| for (const auto& condition : filter->conditions) { |
| // We expect action conditions to be paired with file conditions. |
| if (condition->condition_type == apps::mojom::ConditionType::kAction) { |
| continue; |
| } |
| if (condition->condition_type != apps::mojom::ConditionType::kFile) { |
| return false; |
| } |
| for (const auto& condition_value : condition->condition_values) { |
| if (condition_value->match_type != |
| apps::mojom::PatternMatchType::kFileExtension) { |
| return false; |
| } |
| } |
| } |
| return true; |
| } |
| |
| namespace { |
| |
| void GetMimeTypesAndExtensions(const apps::mojom::IntentFilterPtr& filter, |
| std::set<std::string>& mime_types, |
| std::set<std::string>& file_extensions) { |
| for (const auto& condition : filter->conditions) { |
| if (condition->condition_type != apps::mojom::ConditionType::kFile) { |
| continue; |
| } |
| for (const auto& condition_value : condition->condition_values) { |
| if (condition_value->match_type == |
| apps::mojom::PatternMatchType::kFileExtension) { |
| file_extensions.insert(condition_value->value); |
| } |
| if (condition_value->match_type == |
| apps::mojom::PatternMatchType::kMimeType) { |
| mime_types.insert(condition_value->value); |
| } |
| } |
| } |
| } |
| |
| } // namespace |
| |
| bool IsGenericFileHandler(const apps::mojom::IntentPtr& intent, |
| const apps::mojom::IntentFilterPtr& filter) { |
| if (!intent->files.has_value()) |
| return false; |
| |
| std::set<std::string> mime_types; |
| std::set<std::string> file_extensions; |
| GetMimeTypesAndExtensions(filter, mime_types, file_extensions); |
| if (file_extensions.count("*") > 0 || mime_types.count("*") > 0 || |
| mime_types.count("*/*") > 0) |
| return true; |
| |
| // If a text/* file handler matches with an unsupported text mime type, we |
| // regard it as a generic match. |
| if (mime_types.count("text/*")) { |
| for (const auto& file : intent->files.value()) { |
| if (file->mime_type.has_value() && |
| blink::IsUnsupportedTextMimeType(file->mime_type.value())) { |
| return true; |
| } |
| } |
| } |
| |
| // If directory is selected, it is generic unless mime_types included |
| // 'inode/directory'. |
| for (const auto& file : intent->files.value()) { |
| if (file->is_directory == apps::mojom::OptionalBool::kTrue) |
| return mime_types.count(kMimeTypeInodeDirectory) == 0; |
| } |
| return false; |
| } |
| |
| bool IsShareIntent(const apps::mojom::IntentPtr& intent) { |
| return intent->action == kIntentActionSend || |
| intent->action == kIntentActionSendMultiple; |
| } |
| |
| // TODO(crbug.com/853604): For glob match, it is currently only for Android |
| // intent filters, so we will use the ARC intent filter implementation that is |
| // transcribed from Android codebase, to prevent divergence from Android code. |
| // This is now also used for mime type matching. |
| bool MatchGlob(const std::string& value, const std::string& pattern) { |
| #define GET_CHAR(s, i) ((UNLIKELY(i >= s.length())) ? '\0' : s[i]) |
| |
| const size_t NP = pattern.length(); |
| const size_t NS = value.length(); |
| if (NP == 0) { |
| return NS == 0; |
| } |
| size_t ip = 0, is = 0; |
| char nextChar = GET_CHAR(pattern, 0); |
| while (ip < NP && is < NS) { |
| char c = nextChar; |
| ++ip; |
| nextChar = GET_CHAR(pattern, ip); |
| const bool escaped = (c == '\\'); |
| if (escaped) { |
| c = nextChar; |
| ++ip; |
| nextChar = GET_CHAR(pattern, ip); |
| } |
| if (nextChar == '*') { |
| if (!escaped && c == '.') { |
| if (ip >= (NP - 1)) { |
| // At the end with a pattern match |
| return true; |
| } |
| ++ip; |
| nextChar = GET_CHAR(pattern, ip); |
| // Consume everything until the next char in the pattern is found. |
| if (nextChar == '\\') { |
| ++ip; |
| nextChar = GET_CHAR(pattern, ip); |
| } |
| do { |
| if (GET_CHAR(value, is) == nextChar) { |
| break; |
| } |
| ++is; |
| } while (is < NS); |
| if (is == NS) { |
| // Next char in the pattern didn't exist in the match. |
| return false; |
| } |
| ++ip; |
| nextChar = GET_CHAR(pattern, ip); |
| ++is; |
| } else { |
| // Consume only characters matching the one before '*'. |
| do { |
| if (GET_CHAR(value, is) != c) { |
| break; |
| } |
| ++is; |
| } while (is < NS); |
| ++ip; |
| nextChar = GET_CHAR(pattern, ip); |
| } |
| } else { |
| if (c != '.' && GET_CHAR(value, is) != c) |
| return false; |
| ++is; |
| } |
| } |
| |
| if (ip >= NP && is >= NS) { |
| // Reached the end of both strings |
| return true; |
| } |
| |
| // One last check: we may have finished the match string, but still have a |
| // '.*' at the end of the pattern, which is still a match. |
| if (ip == NP - 2 && GET_CHAR(pattern, ip) == '.' && |
| GET_CHAR(pattern, ip + 1) == '*') { |
| return true; |
| } |
| |
| return false; |
| |
| #undef GET_CHAR |
| } |
| |
| bool OnlyShareToDrive(const apps::mojom::IntentPtr& intent) { |
| return IsShareIntent(intent) && intent->drive_share_url && |
| !intent->share_text && !intent->files; |
| } |
| |
| bool IsIntentValid(const apps::mojom::IntentPtr& intent) { |
| // TODO(crbug.com/853604):Add more checks here to make this a general intent |
| // validity check. Return false if this is a share intent with no file or |
| // text. |
| if (IsShareIntent(intent)) |
| return intent->share_text || intent->files; |
| |
| return true; |
| } |
| |
| base::Value ConvertIntentToValue(const apps::mojom::IntentPtr& intent) { |
| base::Value intent_value(base::Value::Type::DICTIONARY); |
| intent_value.SetStringKey(kActionKey, intent->action); |
| |
| if (intent->url.has_value()) { |
| DCHECK(intent->url.value().is_valid()); |
| intent_value.SetStringKey(kUrlKey, intent->url.value().spec()); |
| } |
| |
| if (intent->mime_type.has_value() && !intent->mime_type.value().empty()) |
| intent_value.SetStringKey(kMimeTypeKey, intent->mime_type.value()); |
| |
| if (intent->files.has_value() && !intent->files.value().empty()) { |
| base::Value file_urls_list(base::Value::Type::LIST); |
| for (const auto& file : intent->files.value()) { |
| DCHECK(file->url.is_valid()); |
| file_urls_list.Append(base::Value(file->url.spec())); |
| } |
| intent_value.SetKey(kFileUrlsKey, std::move(file_urls_list)); |
| } |
| |
| if (intent->activity_name.has_value() && |
| !intent->activity_name.value().empty()) { |
| intent_value.SetStringKey(kActivityNameKey, intent->activity_name.value()); |
| } |
| |
| if (intent->drive_share_url.has_value()) { |
| DCHECK(intent->drive_share_url.value().is_valid()); |
| intent_value.SetStringKey(kDriveShareUrlKey, |
| intent->drive_share_url.value().spec()); |
| } |
| |
| if (intent->share_text.has_value() && !intent->share_text.value().empty()) |
| intent_value.SetStringKey(kShareTextKey, intent->share_text.value()); |
| |
| if (intent->share_title.has_value() && !intent->share_title.value().empty()) |
| intent_value.SetStringKey(kShareTitleKey, intent->share_title.value()); |
| |
| if (intent->start_type.has_value() && !intent->start_type.value().empty()) |
| intent_value.SetStringKey(kStartTypeKey, intent->start_type.value()); |
| |
| if (intent->categories.has_value() && !intent->categories.value().empty()) { |
| base::Value categories(base::Value::Type::LIST); |
| for (const auto& category : intent->categories.value()) { |
| categories.Append(base::Value(category)); |
| } |
| intent_value.SetKey(kCategoriesKey, std::move(categories)); |
| } |
| |
| if (intent->data.has_value() && !intent->data.value().empty()) |
| intent_value.SetStringKey(kDataKey, intent->data.value()); |
| |
| if (intent->ui_bypassed != apps::mojom::OptionalBool::kUnknown) { |
| intent_value.SetBoolKey( |
| kUiBypassedKey, |
| intent->ui_bypassed == apps::mojom::OptionalBool::kTrue ? true : false); |
| } |
| |
| if (intent->extras.has_value() && !intent->extras.value().empty()) { |
| base::Value extras(base::Value::Type::DICTIONARY); |
| for (const auto& extra : intent->extras.value()) { |
| extras.SetStringKey(extra.first, extra.second); |
| } |
| intent_value.SetKey(kExtrasKey, std::move(extras)); |
| } |
| |
| return intent_value; |
| } |
| |
| absl::optional<std::string> GetStringValueFromDict( |
| const base::DictionaryValue& dict, |
| const std::string& key_name) { |
| const base::Value* value = dict.FindKey(key_name); |
| if (!value) |
| return absl::nullopt; |
| |
| const std::string* string_value = value->GetIfString(); |
| if (!string_value || string_value->empty()) |
| return absl::nullopt; |
| |
| return *string_value; |
| } |
| |
| apps::mojom::OptionalBool GetBoolValueFromDict( |
| const base::DictionaryValue& dict, |
| const std::string& key_name) { |
| absl::optional<bool> value = dict.FindBoolKey(key_name); |
| if (!value.has_value()) |
| return apps::mojom::OptionalBool::kUnknown; |
| |
| return value.value() ? apps::mojom::OptionalBool::kTrue |
| : apps::mojom::OptionalBool::kFalse; |
| } |
| |
| absl::optional<GURL> GetGurlValueFromDict(const base::DictionaryValue& dict, |
| const std::string& key_name) { |
| const std::string* url_spec = dict.FindStringKey(key_name); |
| if (!url_spec) |
| return absl::nullopt; |
| |
| GURL url(*url_spec); |
| if (!url.is_valid()) |
| return absl::nullopt; |
| |
| return url; |
| } |
| |
| absl::optional<std::vector<apps::mojom::IntentFilePtr>> GetFilesFromDict( |
| const base::DictionaryValue& dict, |
| const std::string& key_name) { |
| const base::Value* value = dict.FindListKey(key_name); |
| if (!value || !value->is_list() || value->GetListDeprecated().empty()) |
| return absl::nullopt; |
| |
| std::vector<apps::mojom::IntentFilePtr> files; |
| for (const auto& item : value->GetListDeprecated()) { |
| GURL url(item.GetString()); |
| if (url.is_valid()) { |
| auto file = apps::mojom::IntentFile::New(); |
| file->url = std::move(url); |
| files.push_back(std::move(file)); |
| } |
| } |
| return files; |
| } |
| |
| absl::optional<std::vector<std::string>> GetCategoriesFromDict( |
| const base::DictionaryValue& dict, |
| const std::string& key_name) { |
| const base::Value* value = dict.FindListKey(key_name); |
| if (!value || !value->is_list() || value->GetListDeprecated().empty()) |
| return absl::nullopt; |
| |
| std::vector<std::string> categories; |
| for (const auto& item : value->GetListDeprecated()) |
| categories.push_back(item.GetString()); |
| |
| return categories; |
| } |
| |
| absl::optional<base::flat_map<std::string, std::string>> GetExtrasFromDict( |
| const base::DictionaryValue& dict, |
| const std::string& key_name) { |
| const base::Value* value = dict.FindDictKey(key_name); |
| if (!value || !value->is_dict()) |
| return absl::nullopt; |
| |
| base::flat_map<std::string, std::string> extras; |
| for (auto pair : value->DictItems()) { |
| if (pair.second.is_string()) |
| extras[pair.first] = pair.second.GetString(); |
| } |
| |
| return extras; |
| } |
| |
| apps::mojom::IntentPtr ConvertValueToIntent(base::Value&& value) { |
| auto intent = apps::mojom::Intent::New(); |
| |
| base::DictionaryValue* dict = nullptr; |
| if (!value.is_dict() || !value.GetAsDictionary(&dict)) |
| return intent; |
| |
| auto action = GetStringValueFromDict(*dict, kActionKey); |
| if (!action.has_value()) |
| return intent; |
| intent->action = action.value(); |
| intent->url = GetGurlValueFromDict(*dict, kUrlKey); |
| intent->mime_type = GetStringValueFromDict(*dict, kMimeTypeKey); |
| intent->files = GetFilesFromDict(*dict, kFileUrlsKey); |
| intent->activity_name = GetStringValueFromDict(*dict, kActivityNameKey); |
| intent->drive_share_url = GetGurlValueFromDict(*dict, kDriveShareUrlKey); |
| intent->share_text = GetStringValueFromDict(*dict, kShareTextKey); |
| intent->share_title = GetStringValueFromDict(*dict, kShareTitleKey); |
| intent->start_type = GetStringValueFromDict(*dict, kStartTypeKey); |
| intent->categories = GetCategoriesFromDict(*dict, kCategoriesKey); |
| intent->data = GetStringValueFromDict(*dict, kDataKey); |
| intent->ui_bypassed = GetBoolValueFromDict(*dict, kUiBypassedKey); |
| intent->extras = GetExtrasFromDict(*dict, kExtrasKey); |
| |
| return intent; |
| } |
| |
| std::string CalculateCommonMimeType( |
| const std::vector<std::string>& mime_types) { |
| const std::string any_mime_type = std::string(kWildCardAny) + |
| std::string(kMimeTypeSeparator) + |
| std::string(kWildCardAny); |
| if (mime_types.size() == 0) { |
| return any_mime_type; |
| } |
| |
| std::vector<std::string> common_type = |
| base::SplitString(mime_types[0], kMimeTypeSeparator, |
| base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); |
| if (common_type.size() != 2) { |
| return any_mime_type; |
| } |
| |
| for (auto& mime_type : mime_types) { |
| std::vector<std::string> type = |
| base::SplitString(mime_type, kMimeTypeSeparator, base::TRIM_WHITESPACE, |
| base::SPLIT_WANT_NONEMPTY); |
| if (type.size() != kMimeTypeComponentSize) { |
| return any_mime_type; |
| } |
| if (common_type[0] != type[0]) { |
| return any_mime_type; |
| } |
| if (common_type[1] != type[1]) { |
| common_type[1] = kWildCardAny; |
| } |
| } |
| return common_type[0] + kMimeTypeSeparator + common_type[1]; |
| } |
| |
| SharedText ExtractSharedText(const std::string& share_text) { |
| SharedText shared_text; |
| std::string extracted_text = share_text; |
| GURL extracted_url; |
| size_t separator_pos = extracted_text.find_last_of(' '); |
| size_t newline_pos = extracted_text.find_last_of('\n'); |
| if (newline_pos != std::string::npos && |
| (separator_pos == std::string::npos || separator_pos < newline_pos)) { |
| separator_pos = newline_pos; |
| } |
| |
| if (separator_pos == std::string::npos) { |
| extracted_url = GURL(extracted_text); |
| if (extracted_url.is_valid()) |
| extracted_text.clear(); |
| } else { |
| extracted_url = GURL(extracted_text.substr(separator_pos + 1)); |
| if (extracted_url.is_valid()) |
| extracted_text.erase(separator_pos); |
| } |
| |
| if (!extracted_text.empty()) |
| shared_text.text = extracted_text; |
| |
| if (extracted_url.is_valid()) |
| shared_text.url = extracted_url; |
| |
| return shared_text; |
| } |
| |
| } // namespace apps_util |