OLD | NEW |
| (Empty) |
1 // Copyright 2014 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/password_manager/core/browser/affiliation_utils.h" | |
6 | |
7 #include <algorithm> | |
8 #include <ostream> | |
9 | |
10 #include "base/base64.h" | |
11 #include "base/strings/string_piece.h" | |
12 #include "base/strings/string_util.h" | |
13 #include "components/autofill/core/common/password_form.h" | |
14 #include "components/url_formatter/elide_url.h" | |
15 #include "components/variations/variations_associated_data.h" | |
16 #include "net/base/escape.h" | |
17 #include "url/third_party/mozilla/url_parse.h" | |
18 #include "url/url_canon_stdstring.h" | |
19 | |
20 namespace password_manager { | |
21 | |
22 namespace { | |
23 | |
24 // The scheme used for identifying Android applications. | |
25 const char kAndroidAppScheme[] = "android"; | |
26 | |
27 // Returns a StringPiece corresponding to |component| in |uri|, or the empty | |
28 // string in case there is no such component. | |
29 base::StringPiece ComponentString(const std::string& uri, | |
30 const url::Component& component) { | |
31 if (!component.is_valid()) | |
32 return base::StringPiece(); | |
33 return base::StringPiece(uri.c_str() + component.begin, component.len); | |
34 } | |
35 | |
36 // Returns true if the passed ASCII |input| string contains nothing else than | |
37 // alphanumeric characters and those in |other_characters|. | |
38 bool ContainsOnlyAlphanumericAnd(const base::StringPiece& input, | |
39 const base::StringPiece& other_characters) { | |
40 for (char c : input) { | |
41 if (!base::IsAsciiAlpha(c) && !base::IsAsciiDigit(c) && | |
42 other_characters.find(c) == base::StringPiece::npos) | |
43 return false; | |
44 } | |
45 return true; | |
46 } | |
47 | |
48 // Canonicalizes a Web facet URI, and returns true if canonicalization was | |
49 // successful and produced a valid URI. | |
50 bool CanonicalizeWebFacetURI(const std::string& input_uri, | |
51 const url::Parsed& input_parsed, | |
52 std::string* canonical_uri) { | |
53 url::Parsed canonical_parsed; | |
54 url::StdStringCanonOutput canonical_output(canonical_uri); | |
55 | |
56 bool canonicalization_succeeded = url::CanonicalizeStandardURL( | |
57 input_uri.c_str(), input_uri.size(), input_parsed, nullptr, | |
58 &canonical_output, &canonical_parsed); | |
59 canonical_output.Complete(); | |
60 | |
61 if (canonicalization_succeeded && canonical_parsed.host.is_nonempty() && | |
62 !canonical_parsed.username.is_valid() && | |
63 !canonical_parsed.password.is_valid() && | |
64 ComponentString(*canonical_uri, canonical_parsed.path) == "/" && | |
65 !canonical_parsed.query.is_valid() && !canonical_parsed.ref.is_valid()) { | |
66 // Get rid of the trailing slash added by url::CanonicalizeStandardURL(). | |
67 DCHECK_EQ((size_t)canonical_parsed.path.begin, canonical_uri->size() - 1); | |
68 canonical_uri->erase(canonical_parsed.path.begin, | |
69 canonical_parsed.path.len); | |
70 return true; | |
71 } | |
72 return false; | |
73 } | |
74 | |
75 // Adds padding until the length of the base64-encoded |data| becomes a multiple | |
76 // of 4, and returns true if the thusly obtained |data| is now correctly padded, | |
77 // i.e., there are at most 2 padding characters ('=') at the very end. | |
78 bool CanonicalizeBase64Padding(std::string* data) { | |
79 while (data->size() % 4u != 0u) | |
80 data->push_back('='); | |
81 | |
82 size_t first_padding = data->find_first_of('='); | |
83 return first_padding == std::string::npos || | |
84 (data->size() - first_padding <= 2u && | |
85 data->find_first_not_of('=', first_padding) == std::string::npos); | |
86 } | |
87 | |
88 // Canonicalizes the username component in an Android facet URI (containing the | |
89 // certificate hash), and returns true if canonicalization was successful and | |
90 // produced a valid non-empty component. | |
91 bool CanonicalizeHashComponent(const base::StringPiece& input_hash, | |
92 url::CanonOutput* canonical_output) { | |
93 // Characters other than alphanumeric that are used in the "URL and filename | |
94 // safe" base64 alphabet; plus the padding ('='). | |
95 const char kBase64NonAlphanumericChars[] = "-_="; | |
96 | |
97 // We need net::UnescapeRule::URL_SPECIAL_CHARS_EXCEPT_PATH_SEPARATORS to | |
98 // unescape the padding ('='). | |
99 std::string base64_encoded_hash = net::UnescapeURLComponent( | |
100 input_hash.as_string(), | |
101 net::UnescapeRule::URL_SPECIAL_CHARS_EXCEPT_PATH_SEPARATORS); | |
102 | |
103 if (!base64_encoded_hash.empty() && | |
104 CanonicalizeBase64Padding(&base64_encoded_hash) && | |
105 ContainsOnlyAlphanumericAnd(base64_encoded_hash, | |
106 kBase64NonAlphanumericChars)) { | |
107 canonical_output->Append(base64_encoded_hash.data(), | |
108 base64_encoded_hash.size()); | |
109 canonical_output->push_back('@'); | |
110 return true; | |
111 } | |
112 return false; | |
113 } | |
114 | |
115 // Canonicalizes the host component in an Android facet URI (containing the | |
116 // package name), and returns true if canonicalization was successful and | |
117 // produced a valid non-empty component. | |
118 bool CanonicalizePackageNameComponent( | |
119 const base::StringPiece& input_package_name, | |
120 url::CanonOutput* canonical_output) { | |
121 // Characters other than alphanumeric that are permitted in the package names. | |
122 const char kPackageNameNonAlphanumericChars[] = "._"; | |
123 | |
124 std::string package_name = net::UnescapeURLComponent( | |
125 input_package_name.as_string(), net::UnescapeRule::NORMAL); | |
126 | |
127 // TODO(engedy): We might want to use a regex to check this more throughly. | |
128 if (!package_name.empty() && | |
129 ContainsOnlyAlphanumericAnd(package_name, | |
130 kPackageNameNonAlphanumericChars)) { | |
131 canonical_output->Append(package_name.data(), package_name.size()); | |
132 return true; | |
133 } | |
134 return false; | |
135 } | |
136 | |
137 // Canonicalizes an Android facet URI, and returns true if canonicalization was | |
138 // successful and produced a valid URI. | |
139 bool CanonicalizeAndroidFacetURI(const std::string& input_uri, | |
140 const url::Parsed& input_parsed, | |
141 std::string* canonical_uri) { | |
142 url::StdStringCanonOutput canonical_output(canonical_uri); | |
143 | |
144 url::Component unused; | |
145 bool success = url::CanonicalizeScheme( | |
146 input_uri.c_str(), input_parsed.scheme, &canonical_output, &unused); | |
147 | |
148 canonical_output.push_back('/'); | |
149 canonical_output.push_back('/'); | |
150 | |
151 // We cannot use url::CanonicalizeUserInfo as that would percent encode the | |
152 // base64 padding characters ('='). | |
153 success &= CanonicalizeHashComponent( | |
154 ComponentString(input_uri, input_parsed.username), &canonical_output); | |
155 | |
156 // We cannot use url::CanonicalizeHost as that would convert the package name | |
157 // to lower case, but the package name is case sensitive. | |
158 success &= CanonicalizePackageNameComponent( | |
159 ComponentString(input_uri.data(), input_parsed.host), &canonical_output); | |
160 | |
161 canonical_output.Complete(); | |
162 | |
163 return success && !input_parsed.password.is_nonempty() && | |
164 (!input_parsed.path.is_nonempty() || | |
165 ComponentString(input_uri, input_parsed.path) == "/") && | |
166 !input_parsed.port.is_nonempty() && !input_parsed.query.is_valid() && | |
167 !input_parsed.ref.is_valid(); | |
168 } | |
169 | |
170 // Computes the canonicalized form of |uri| into |canonical_uri|, and returns | |
171 // true if canonicalization was successful and produced a valid URI. | |
172 bool ParseAndCanonicalizeFacetURI(const std::string& input_uri, | |
173 std::string* canonical_uri) { | |
174 DCHECK(canonical_uri); | |
175 canonical_uri->clear(); | |
176 canonical_uri->reserve(input_uri.size() + 32); | |
177 | |
178 url::Parsed input_parsed; | |
179 url::ParseStandardURL(input_uri.c_str(), input_uri.size(), &input_parsed); | |
180 | |
181 base::StringPiece scheme = ComponentString(input_uri, input_parsed.scheme); | |
182 if (base::LowerCaseEqualsASCII(scheme, url::kHttpsScheme)) { | |
183 return CanonicalizeWebFacetURI(input_uri, input_parsed, canonical_uri); | |
184 } else if (base::LowerCaseEqualsASCII(scheme, kAndroidAppScheme)) { | |
185 return CanonicalizeAndroidFacetURI(input_uri, input_parsed, canonical_uri); | |
186 } | |
187 return false; | |
188 } | |
189 | |
190 } // namespace | |
191 | |
192 | |
193 // FacetURI ------------------------------------------------------------------- | |
194 | |
195 FacetURI::FacetURI() : is_valid_(false) { | |
196 } | |
197 | |
198 // static | |
199 FacetURI FacetURI::FromPotentiallyInvalidSpec(const std::string& spec) { | |
200 std::string canonical_spec; | |
201 bool is_valid = ParseAndCanonicalizeFacetURI(spec, &canonical_spec); | |
202 return FacetURI(canonical_spec, is_valid); | |
203 } | |
204 | |
205 // static | |
206 FacetURI FacetURI::FromCanonicalSpec(const std::string& canonical_spec) { | |
207 return FacetURI(canonical_spec, true); | |
208 } | |
209 | |
210 bool FacetURI::operator==(const FacetURI& other) const { | |
211 DCHECK(is_empty() || is_valid()); | |
212 DCHECK(other.is_empty() || other.is_valid()); | |
213 return canonical_spec_ == other.canonical_spec_; | |
214 } | |
215 | |
216 bool FacetURI::operator!=(const FacetURI& other) const { | |
217 DCHECK(is_empty() || is_valid()); | |
218 DCHECK(other.is_empty() || other.is_valid()); | |
219 return canonical_spec_ != other.canonical_spec_; | |
220 } | |
221 | |
222 bool FacetURI::operator<(const FacetURI& other) const { | |
223 DCHECK(is_empty() || is_valid()); | |
224 DCHECK(other.is_empty() || other.is_valid()); | |
225 return canonical_spec_ < other.canonical_spec_; | |
226 } | |
227 | |
228 bool FacetURI::operator>(const FacetURI& other) const { | |
229 DCHECK(is_empty() || is_valid()); | |
230 DCHECK(other.is_empty() || other.is_valid()); | |
231 return canonical_spec_ > other.canonical_spec_; | |
232 } | |
233 | |
234 bool FacetURI::IsValidWebFacetURI() const { | |
235 return scheme() == url::kHttpsScheme; | |
236 } | |
237 | |
238 bool FacetURI::IsValidAndroidFacetURI() const { | |
239 return scheme() == kAndroidAppScheme; | |
240 } | |
241 | |
242 std::string FacetURI::scheme() const { | |
243 return is_valid() | |
244 ? ComponentString(canonical_spec_, parsed_.scheme).as_string() | |
245 : ""; | |
246 } | |
247 | |
248 std::string FacetURI::android_package_name() const { | |
249 if (!IsValidAndroidFacetURI()) | |
250 return ""; | |
251 return ComponentString(canonical_spec_, parsed_.host).as_string(); | |
252 } | |
253 | |
254 FacetURI::FacetURI(const std::string& canonical_spec, bool is_valid) | |
255 : is_valid_(is_valid), canonical_spec_(canonical_spec) { | |
256 // TODO(engedy): Refactor code in order to avoid to avoid parsing the URL | |
257 // twice. | |
258 url::ParseStandardURL(canonical_spec_.c_str(), canonical_spec_.size(), | |
259 &parsed_); | |
260 } | |
261 | |
262 | |
263 // AffiliatedFacetsWithUpdateTime --------------------------------------------- | |
264 | |
265 AffiliatedFacetsWithUpdateTime::AffiliatedFacetsWithUpdateTime() { | |
266 } | |
267 | |
268 AffiliatedFacetsWithUpdateTime::AffiliatedFacetsWithUpdateTime( | |
269 const AffiliatedFacetsWithUpdateTime& other) = default; | |
270 | |
271 AffiliatedFacetsWithUpdateTime::~AffiliatedFacetsWithUpdateTime() { | |
272 } | |
273 | |
274 | |
275 // Helpers -------------------------------------------------------------------- | |
276 | |
277 std::ostream& operator<<(std::ostream& os, const FacetURI& facet_uri) { | |
278 return os << facet_uri.potentially_invalid_spec(); | |
279 } | |
280 | |
281 bool AreEquivalenceClassesEqual(const AffiliatedFacets& a, | |
282 const AffiliatedFacets& b) { | |
283 if (a.size() != b.size()) | |
284 return false; | |
285 | |
286 std::vector<FacetURI> a_sorted(a.begin(), a.end()); | |
287 std::vector<FacetURI> b_sorted(b.begin(), b.end()); | |
288 std::sort(a_sorted.begin(), a_sorted.end()); | |
289 std::sort(b_sorted.begin(), b_sorted.end()); | |
290 return std::equal(a_sorted.begin(), a_sorted.end(), b_sorted.begin()); | |
291 } | |
292 | |
293 bool IsValidAndroidFacetURI(const std::string& url) { | |
294 FacetURI facet = FacetURI::FromPotentiallyInvalidSpec(url); | |
295 return facet.IsValidAndroidFacetURI(); | |
296 } | |
297 | |
298 std::string GetHumanReadableOrigin( | |
299 const autofill::PasswordForm& password_form) { | |
300 FacetURI facet_uri = | |
301 FacetURI::FromPotentiallyInvalidSpec(password_form.signon_realm); | |
302 if (facet_uri.IsValidAndroidFacetURI()) | |
303 return GetHumanReadableOriginForAndroidUri(facet_uri); | |
304 | |
305 return base::UTF16ToUTF8( | |
306 url_formatter::FormatUrlForSecurityDisplay(password_form.origin)); | |
307 } | |
308 | |
309 std::string GetHumanReadableOriginForAndroidUri(const FacetURI facet_uri) { | |
310 DCHECK(facet_uri.IsValidAndroidFacetURI()); | |
311 return facet_uri.scheme() + "://" + facet_uri.android_package_name(); | |
312 } | |
313 | |
314 } // namespace password_manager | |
OLD | NEW |