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