1 // Copyright 2018 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 "components/signin/public/base/avatar_icon_util.h"
6
7 #include <string>
8 #include <vector>
9
10 #include "base/stl_util.h"
11 #include "base/strings/string_split.h"
12 #include "base/strings/string_util.h"
13 #include "base/strings/stringprintf.h"
14 #include "third_party/re2/src/re2/re2.h"
15 #include "url/gurl.h"
16
17 namespace {
18
19 // Separator of URL path components.
20 constexpr char kURLPathSeparator[] = "/";
21
22 // Constants describing legacy image URL format.
23 // See https://crbug.com/733306#c3 for details.
24 constexpr size_t kLegacyURLPathComponentsCount = 6;
25 constexpr size_t kLegacyURLPathComponentsCountWithOptions = 7;
26 constexpr size_t kLegacyURLPathOptionsComponentPosition = 5;
27 // Constants describing content image URL format.
28 // See https://crbug.com/911332#c15 for details.
29 constexpr size_t kContentURLPathMinComponentsCount = 2;
30 constexpr size_t kContentURLPathMaxComponentsCount = 3;
31 constexpr char kContentURLOptionsStartChar = '=';
32 // Various options that can be embedded in image URL.
33 constexpr char kImageURLOptionSeparator[] = "-";
34 constexpr char kImageURLOptionSizePattern[] = R"(s\d+)";
35 constexpr char kImageURLOptionSizeFormat[] = "s%d";
36 constexpr char kImageURLOptionSquareCrop[] = "c";
37 // Option to disable default avatar if user doesn't have a custom one.
38 constexpr char kImageURLOptionNoSilhouette[] = "ns";
39
BuildImageURLOptionsString(int image_size,bool no_silhouette,const std::string & existing_options)40 std::string BuildImageURLOptionsString(int image_size,
41 bool no_silhouette,
42 const std::string& existing_options) {
43 std::vector<std::string> url_options =
44 base::SplitString(existing_options, kImageURLOptionSeparator,
45 base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
46
47 RE2 size_pattern(kImageURLOptionSizePattern);
48 base::EraseIf(url_options, [&size_pattern](const std::string& str) {
49 return RE2::FullMatch(str, size_pattern);
50 });
51 base::Erase(url_options, kImageURLOptionSquareCrop);
52 base::Erase(url_options, kImageURLOptionNoSilhouette);
53
54 url_options.push_back(
55 base::StringPrintf(kImageURLOptionSizeFormat, image_size));
56 url_options.push_back(kImageURLOptionSquareCrop);
57 if (no_silhouette)
58 url_options.push_back(kImageURLOptionNoSilhouette);
59 return base::JoinString(url_options, kImageURLOptionSeparator);
60 }
61
62 // Returns an empty vector if |url_components| couldn't be processed as a legacy
63 // image URL.
TryProcessAsLegacyImageURL(std::vector<std::string> url_components,int image_size,bool no_silhouette)64 std::vector<std::string> TryProcessAsLegacyImageURL(
65 std::vector<std::string> url_components,
66 int image_size,
67 bool no_silhouette) {
68 if (url_components.back().empty())
69 return {};
70
71 if (url_components.size() == kLegacyURLPathComponentsCount) {
72 url_components.insert(
73 url_components.begin() + kLegacyURLPathOptionsComponentPosition,
74 BuildImageURLOptionsString(image_size, no_silhouette, std::string()));
75 return url_components;
76 }
77
78 if (url_components.size() == kLegacyURLPathComponentsCountWithOptions) {
79 std::string options =
80 url_components.at(kLegacyURLPathOptionsComponentPosition);
81 url_components[kLegacyURLPathOptionsComponentPosition] =
82 BuildImageURLOptionsString(image_size, no_silhouette, options);
83 return url_components;
84 }
85
86 return {};
87 }
88
89 // Returns an empty vector if |url_components| couldn't be processed as a
90 // content image URL.
TryProcessAsContentImageURL(std::vector<std::string> url_components,int image_size,bool no_silhouette)91 std::vector<std::string> TryProcessAsContentImageURL(
92 std::vector<std::string> url_components,
93 int image_size,
94 bool no_silhouette) {
95 if (url_components.size() < kContentURLPathMinComponentsCount ||
96 url_components.size() > kContentURLPathMaxComponentsCount ||
97 url_components.back().empty()) {
98 return {};
99 }
100
101 std::string* options_component = &url_components.back();
102 // Extract existing options from |options_component|.
103 const size_t options_pos =
104 options_component->find(kContentURLOptionsStartChar);
105 std::string component_without_options =
106 options_component->substr(0, options_pos);
107 std::string existing_options =
108 options_pos == std::string::npos
109 ? ""
110 : options_component->substr(options_pos + 1);
111 // Update options in |options_component|.
112 *options_component =
113 component_without_options + kContentURLOptionsStartChar +
114 BuildImageURLOptionsString(image_size, no_silhouette, existing_options);
115 return url_components;
116 }
117
118 } // namespace
119
120 namespace signin {
121
122 const int kAccountInfoImageSize = 256;
123
GetAvatarImageURLWithOptions(const GURL & old_url,int image_size,bool no_silhouette)124 GURL GetAvatarImageURLWithOptions(const GURL& old_url,
125 int image_size,
126 bool no_silhouette) {
127 DCHECK(old_url.is_valid());
128
129 std::vector<std::string> components =
130 base::SplitString(old_url.path(), kURLPathSeparator,
131 base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
132
133 auto new_components =
134 TryProcessAsContentImageURL(components, image_size, no_silhouette);
135
136 if (new_components.empty()) {
137 new_components =
138 TryProcessAsLegacyImageURL(components, image_size, no_silhouette);
139 }
140
141 if (new_components.empty()) {
142 // URL doesn't match any known patterns, so return unchanged.
143 return old_url;
144 }
145
146 std::string new_path = base::JoinString(new_components, kURLPathSeparator);
147 GURL::Replacements replacement;
148 replacement.SetPathStr(new_path);
149 return old_url.ReplaceComponents(replacement);
150 }
151
152 } // namespace signin
153