Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(33)

Side by Side Diff: chrome/common/extensions/permissions/permission_set.cc

Issue 10649003: Move each permission classes to its own files in extensions/permissions (Closed) Base URL: http://git.chromium.org/chromium/src.git@master
Patch Set: Rebase on HEAD Created 8 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright (c) 2012 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 "chrome/common/extensions/permissions/permission_set.h"
6
7 #include <algorithm>
8 #include <string>
9
10 #include "chrome/common/extensions/extension.h"
11 #include "chrome/common/extensions/permissions/permissions_info.h"
12 #include "chrome/common/extensions/url_pattern.h"
13 #include "chrome/common/extensions/url_pattern_set.h"
14 #include "content/public/common/url_constants.h"
15 #include "grit/generated_resources.h"
16 #include "net/base/registry_controlled_domain.h"
17 #include "ui/base/l10n/l10n_util.h"
18
19 namespace {
20
21 // Helper for GetDistinctHosts(): com > net > org > everything else.
22 bool RcdBetterThan(std::string a, std::string b) {
23 if (a == b)
24 return false;
25 if (a == "com")
26 return true;
27 if (a == "net")
28 return b != "com";
29 if (a == "org")
30 return b != "com" && b != "net";
31 return false;
32 }
33
34 // Names of API modules that can be used without listing it in the
35 // permissions section of the manifest.
36 const char* kNonPermissionModuleNames[] = {
37 "app",
38 "appWindow",
39 "browserAction",
40 "devtools",
41 "events",
42 "extension",
43 "i18n",
44 "omnibox",
45 "pageAction",
46 "pageActions",
47 "permissions",
48 "runtime",
49 "test",
50 "types"
51 };
52 const size_t kNumNonPermissionModuleNames =
53 arraysize(kNonPermissionModuleNames);
54
55 // Names of functions (within modules requiring permissions) that can be used
56 // without asking for the module permission. In other words, functions you can
57 // use with no permissions specified.
58 const char* kNonPermissionFunctionNames[] = {
59 "management.getPermissionWarningsByManifest",
60 "tabs.create",
61 "tabs.onRemoved",
62 "tabs.remove",
63 "tabs.update",
64 };
65 const size_t kNumNonPermissionFunctionNames =
66 arraysize(kNonPermissionFunctionNames);
67
68 void AddPatternsAndRemovePaths(const URLPatternSet& set, URLPatternSet* out) {
69 DCHECK(out);
70 for (URLPatternSet::const_iterator i = set.begin(); i != set.end(); ++i) {
71 URLPattern p = *i;
72 p.SetPath("/*");
73 out->AddPattern(p);
74 }
75 }
76
77 // Strips out the API name from a function or event name.
78 // Functions will be of the form api_name.function
79 // Events will be of the form api_name/id or api_name.optional.stuff
80 std::string GetPermissionName(const std::string& function_name) {
81 size_t separator = function_name.find_first_of("./");
82 if (separator != std::string::npos)
83 return function_name.substr(0, separator);
84 else
85 return function_name;
86 }
87
88 } // namespace
89
90 namespace extensions {
91
92 //
93 // PermissionSet
94 //
95
96 PermissionSet::PermissionSet() {}
97
98 PermissionSet::PermissionSet(
99 const extensions::Extension* extension,
100 const APIPermissionSet& apis,
101 const URLPatternSet& explicit_hosts,
102 const OAuth2Scopes& scopes)
103 : apis_(apis),
104 scopes_(scopes) {
105 DCHECK(extension);
106 AddPatternsAndRemovePaths(explicit_hosts, &explicit_hosts_);
107 InitImplicitExtensionPermissions(extension);
108 InitEffectiveHosts();
109 }
110
111 PermissionSet::PermissionSet(
112 const APIPermissionSet& apis,
113 const URLPatternSet& explicit_hosts,
114 const URLPatternSet& scriptable_hosts)
115 : apis_(apis),
116 scriptable_hosts_(scriptable_hosts) {
117 AddPatternsAndRemovePaths(explicit_hosts, &explicit_hosts_);
118 InitEffectiveHosts();
119 }
120
121 PermissionSet::PermissionSet(
122 const APIPermissionSet& apis,
123 const URLPatternSet& explicit_hosts,
124 const URLPatternSet& scriptable_hosts,
125 const OAuth2Scopes& scopes)
126 : apis_(apis),
127 scriptable_hosts_(scriptable_hosts),
128 scopes_(scopes) {
129 AddPatternsAndRemovePaths(explicit_hosts, &explicit_hosts_);
130 InitEffectiveHosts();
131 }
132
133 PermissionSet::PermissionSet(
134 const OAuth2Scopes& scopes)
135 : scopes_(scopes) {
136 InitEffectiveHosts();
137 }
138
139 // static
140 PermissionSet* PermissionSet::CreateDifference(
141 const PermissionSet* set1,
142 const PermissionSet* set2) {
143 scoped_refptr<PermissionSet> empty = new PermissionSet();
144 const PermissionSet* set1_safe = (set1 == NULL) ? empty : set1;
145 const PermissionSet* set2_safe = (set2 == NULL) ? empty : set2;
146
147 APIPermissionSet apis;
148 std::set_difference(set1_safe->apis().begin(), set1_safe->apis().end(),
149 set2_safe->apis().begin(), set2_safe->apis().end(),
150 std::insert_iterator<APIPermissionSet>(
151 apis, apis.begin()));
152
153 URLPatternSet explicit_hosts;
154 URLPatternSet::CreateDifference(set1_safe->explicit_hosts(),
155 set2_safe->explicit_hosts(),
156 &explicit_hosts);
157
158 URLPatternSet scriptable_hosts;
159 URLPatternSet::CreateDifference(set1_safe->scriptable_hosts(),
160 set2_safe->scriptable_hosts(),
161 &scriptable_hosts);
162
163 OAuth2Scopes scopes;
164 std::set_difference(set1_safe->scopes().begin(), set1_safe->scopes().end(),
165 set2_safe->scopes().begin(), set2_safe->scopes().end(),
166 std::insert_iterator<OAuth2Scopes>(
167 scopes, scopes.begin()));
168
169 return new PermissionSet(
170 apis, explicit_hosts, scriptable_hosts, scopes);
171 }
172
173 // static
174 PermissionSet* PermissionSet::CreateIntersection(
175 const PermissionSet* set1,
176 const PermissionSet* set2) {
177 scoped_refptr<PermissionSet> empty = new PermissionSet();
178 const PermissionSet* set1_safe = (set1 == NULL) ? empty : set1;
179 const PermissionSet* set2_safe = (set2 == NULL) ? empty : set2;
180
181 APIPermissionSet apis;
182 std::set_intersection(set1_safe->apis().begin(), set1_safe->apis().end(),
183 set2_safe->apis().begin(), set2_safe->apis().end(),
184 std::insert_iterator<APIPermissionSet>(
185 apis, apis.begin()));
186 URLPatternSet explicit_hosts;
187 URLPatternSet::CreateIntersection(set1_safe->explicit_hosts(),
188 set2_safe->explicit_hosts(),
189 &explicit_hosts);
190
191 URLPatternSet scriptable_hosts;
192 URLPatternSet::CreateIntersection(set1_safe->scriptable_hosts(),
193 set2_safe->scriptable_hosts(),
194 &scriptable_hosts);
195
196 OAuth2Scopes scopes;
197 std::set_intersection(set1_safe->scopes().begin(), set1_safe->scopes().end(),
198 set2_safe->scopes().begin(), set2_safe->scopes().end(),
199 std::insert_iterator<OAuth2Scopes>(
200 scopes, scopes.begin()));
201
202 return new PermissionSet(
203 apis, explicit_hosts, scriptable_hosts, scopes);
204 }
205
206 // static
207 PermissionSet* PermissionSet::CreateUnion(
208 const PermissionSet* set1,
209 const PermissionSet* set2) {
210 scoped_refptr<PermissionSet> empty = new PermissionSet();
211 const PermissionSet* set1_safe = (set1 == NULL) ? empty : set1;
212 const PermissionSet* set2_safe = (set2 == NULL) ? empty : set2;
213
214 APIPermissionSet apis;
215 std::set_union(set1_safe->apis().begin(), set1_safe->apis().end(),
216 set2_safe->apis().begin(), set2_safe->apis().end(),
217 std::insert_iterator<APIPermissionSet>(
218 apis, apis.begin()));
219
220 URLPatternSet explicit_hosts;
221 URLPatternSet::CreateUnion(set1_safe->explicit_hosts(),
222 set2_safe->explicit_hosts(),
223 &explicit_hosts);
224
225 URLPatternSet scriptable_hosts;
226 URLPatternSet::CreateUnion(set1_safe->scriptable_hosts(),
227 set2_safe->scriptable_hosts(),
228 &scriptable_hosts);
229
230 OAuth2Scopes scopes;
231 std::set_union(set1_safe->scopes().begin(), set1_safe->scopes().end(),
232 set2_safe->scopes().begin(), set2_safe->scopes().end(),
233 std::insert_iterator<OAuth2Scopes>(
234 scopes, scopes.begin()));
235
236 return new PermissionSet(
237 apis, explicit_hosts, scriptable_hosts, scopes);
238 }
239
240 bool PermissionSet::operator==(
241 const PermissionSet& rhs) const {
242 return apis_ == rhs.apis_ &&
243 scriptable_hosts_ == rhs.scriptable_hosts_ &&
244 explicit_hosts_ == rhs.explicit_hosts_ &&
245 scopes_ == rhs.scopes_;
246 }
247
248 bool PermissionSet::Contains(const PermissionSet& set) const {
249 // Every set includes the empty set.
250 if (set.IsEmpty())
251 return true;
252
253 if (!std::includes(apis_.begin(), apis_.end(),
254 set.apis().begin(), set.apis().end()))
255 return false;
256
257 if (!explicit_hosts().Contains(set.explicit_hosts()))
258 return false;
259
260 if (!scriptable_hosts().Contains(set.scriptable_hosts()))
261 return false;
262
263 if (!std::includes(scopes_.begin(), scopes_.end(),
264 set.scopes().begin(), set.scopes().end()))
265 return false;
266
267 return true;
268 }
269
270 std::set<std::string> PermissionSet::GetAPIsAsStrings() const {
271 PermissionsInfo* info = PermissionsInfo::GetInstance();
272 std::set<std::string> apis_str;
273 for (APIPermissionSet::const_iterator i = apis_.begin();
274 i != apis_.end(); ++i) {
275 APIPermission* permission = info->GetByID(*i);
276 if (permission)
277 apis_str.insert(permission->name());
278 }
279 return apis_str;
280 }
281
282 std::set<std::string> PermissionSet::
283 GetAPIsWithAnyAccessAsStrings() const {
284 std::set<std::string> result = GetAPIsAsStrings();
285 for (size_t i = 0; i < kNumNonPermissionModuleNames; ++i)
286 result.insert(kNonPermissionModuleNames[i]);
287 for (size_t i = 0; i < kNumNonPermissionFunctionNames; ++i)
288 result.insert(GetPermissionName(kNonPermissionFunctionNames[i]));
289 return result;
290 }
291
292 bool PermissionSet::HasAnyAccessToAPI(
293 const std::string& api_name) const {
294 if (HasAccessToFunction(api_name))
295 return true;
296
297 for (size_t i = 0; i < kNumNonPermissionFunctionNames; ++i) {
298 if (api_name == GetPermissionName(kNonPermissionFunctionNames[i]))
299 return true;
300 }
301
302 return false;
303 }
304
305 std::set<std::string>
306 PermissionSet::GetDistinctHostsForDisplay() const {
307 return GetDistinctHosts(effective_hosts_, true, true);
308 }
309
310 PermissionMessages
311 PermissionSet::GetPermissionMessages() const {
312 PermissionMessages messages;
313
314 if (HasEffectiveFullAccess()) {
315 messages.push_back(PermissionMessage(
316 PermissionMessage::kFullAccess,
317 l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_FULL_ACCESS)));
318 return messages;
319 }
320
321 if (HasEffectiveAccessToAllHosts()) {
322 messages.push_back(PermissionMessage(
323 PermissionMessage::kHostsAll,
324 l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_ALL_HOSTS)));
325 } else {
326 std::set<std::string> hosts = GetDistinctHostsForDisplay();
327 if (!hosts.empty())
328 messages.push_back(PermissionMessage::CreateFromHostList(hosts));
329 }
330
331 std::set<PermissionMessage> simple_msgs =
332 GetSimplePermissionMessages();
333 messages.insert(messages.end(), simple_msgs.begin(), simple_msgs.end());
334
335 return messages;
336 }
337
338 std::vector<string16> PermissionSet::GetWarningMessages() const {
339 std::vector<string16> messages;
340 PermissionMessages permissions = GetPermissionMessages();
341
342 bool audio_capture = false;
343 bool video_capture = false;
344 for (PermissionMessages::const_iterator i = permissions.begin();
345 i != permissions.end(); ++i) {
346 if (i->id() == PermissionMessage::kAudioCapture)
347 audio_capture = true;
348 if (i->id() == PermissionMessage::kVideoCapture)
349 video_capture = true;
350 }
351
352 for (PermissionMessages::const_iterator i = permissions.begin();
353 i != permissions.end(); ++i) {
354 if (audio_capture && video_capture) {
355 if (i->id() == PermissionMessage::kAudioCapture) {
356 messages.push_back(l10n_util::GetStringUTF16(
357 IDS_EXTENSION_PROMPT_WARNING_AUDIO_AND_VIDEO_CAPTURE));
358 continue;
359 } else if (i->id() == PermissionMessage::kVideoCapture) {
360 // The combined message will be pushed above.
361 continue;
362 }
363 }
364
365 messages.push_back(i->message());
366 }
367
368 return messages;
369 }
370
371 bool PermissionSet::IsEmpty() const {
372 // Not default if any host permissions are present.
373 if (!(explicit_hosts().is_empty() && scriptable_hosts().is_empty()))
374 return false;
375
376 // Or if it has no api permissions.
377 return apis().empty();
378 }
379
380 bool PermissionSet::HasAPIPermission(
381 APIPermission::ID permission) const {
382 return apis().find(permission) != apis().end();
383 }
384
385 bool PermissionSet::HasAccessToFunction(
386 const std::string& function_name) const {
387 // TODO(jstritar): Embed this information in each permission and add a method
388 // like GrantsAccess(function_name) to APIPermission. A "default"
389 // permission can then handle the modules and functions that everyone can
390 // access.
391 for (size_t i = 0; i < kNumNonPermissionFunctionNames; ++i) {
392 if (function_name == kNonPermissionFunctionNames[i])
393 return true;
394 }
395
396 std::string permission_name = GetPermissionName(function_name);
397 APIPermission* permission =
398 PermissionsInfo::GetInstance()->GetByName(permission_name);
399 if (permission && apis_.count(permission->id()))
400 return true;
401
402 for (size_t i = 0; i < kNumNonPermissionModuleNames; ++i) {
403 if (permission_name == kNonPermissionModuleNames[i]) {
404 return true;
405 }
406 }
407
408 return false;
409 }
410
411 bool PermissionSet::HasExplicitAccessToOrigin(
412 const GURL& origin) const {
413 return explicit_hosts().MatchesURL(origin);
414 }
415
416 bool PermissionSet::HasScriptableAccessToURL(
417 const GURL& origin) const {
418 // We only need to check our host list to verify access. The host list should
419 // already reflect any special rules (such as chrome://favicon, all hosts
420 // access, etc.).
421 return scriptable_hosts().MatchesURL(origin);
422 }
423
424 bool PermissionSet::HasEffectiveAccessToAllHosts() const {
425 // There are two ways this set can have effective access to all hosts:
426 // 1) it has an <all_urls> URL pattern.
427 // 2) it has a named permission with implied full URL access.
428 for (URLPatternSet::const_iterator host = effective_hosts().begin();
429 host != effective_hosts().end(); ++host) {
430 if (host->match_all_urls() ||
431 (host->match_subdomains() && host->host().empty()))
432 return true;
433 }
434
435 PermissionsInfo* info = PermissionsInfo::GetInstance();
436 for (APIPermissionSet::const_iterator i = apis().begin();
437 i != apis().end(); ++i) {
438 APIPermission* permission = info->GetByID(*i);
439 if (permission->implies_full_url_access())
440 return true;
441 }
442 return false;
443 }
444
445 bool PermissionSet::HasEffectiveAccessToURL(
446 const GURL& url) const {
447 return effective_hosts().MatchesURL(url);
448 }
449
450 bool PermissionSet::HasEffectiveFullAccess() const {
451 PermissionsInfo* info = PermissionsInfo::GetInstance();
452 for (APIPermissionSet::const_iterator i = apis().begin();
453 i != apis().end(); ++i) {
454 APIPermission* permission = info->GetByID(*i);
455 if (permission->implies_full_access())
456 return true;
457 }
458 return false;
459 }
460
461 bool PermissionSet::HasLessPrivilegesThan(
462 const PermissionSet* permissions) const {
463 // Things can't get worse than native code access.
464 if (HasEffectiveFullAccess())
465 return false;
466
467 // Otherwise, it's a privilege increase if the new one has full access.
468 if (permissions->HasEffectiveFullAccess())
469 return true;
470
471 if (HasLessHostPrivilegesThan(permissions))
472 return true;
473
474 if (HasLessAPIPrivilegesThan(permissions))
475 return true;
476
477 if (HasLessScopesThan(permissions))
478 return true;
479
480 return false;
481 }
482
483 PermissionSet::~PermissionSet() {}
484
485 // static
486 std::set<std::string> PermissionSet::GetDistinctHosts(
487 const URLPatternSet& host_patterns,
488 bool include_rcd,
489 bool exclude_file_scheme) {
490 // Use a vector to preserve order (also faster than a map on small sets).
491 // Each item is a host split into two parts: host without RCDs and
492 // current best RCD.
493 typedef std::vector<std::pair<std::string, std::string> > HostVector;
494 HostVector hosts_best_rcd;
495 for (URLPatternSet::const_iterator i = host_patterns.begin();
496 i != host_patterns.end(); ++i) {
497 if (exclude_file_scheme && i->scheme() == chrome::kFileScheme)
498 continue;
499
500 std::string host = i->host();
501
502 // Add the subdomain wildcard back to the host, if necessary.
503 if (i->match_subdomains())
504 host = "*." + host;
505
506 // If the host has an RCD, split it off so we can detect duplicates.
507 std::string rcd;
508 size_t reg_len = net::RegistryControlledDomainService::GetRegistryLength(
509 host, false);
510 if (reg_len && reg_len != std::string::npos) {
511 if (include_rcd) // else leave rcd empty
512 rcd = host.substr(host.size() - reg_len);
513 host = host.substr(0, host.size() - reg_len);
514 }
515
516 // Check if we've already seen this host.
517 HostVector::iterator it = hosts_best_rcd.begin();
518 for (; it != hosts_best_rcd.end(); ++it) {
519 if (it->first == host)
520 break;
521 }
522 // If this host was found, replace the RCD if this one is better.
523 if (it != hosts_best_rcd.end()) {
524 if (include_rcd && RcdBetterThan(rcd, it->second))
525 it->second = rcd;
526 } else { // Previously unseen host, append it.
527 hosts_best_rcd.push_back(std::make_pair(host, rcd));
528 }
529 }
530
531 // Build up the final vector by concatenating hosts and RCDs.
532 std::set<std::string> distinct_hosts;
533 for (HostVector::iterator it = hosts_best_rcd.begin();
534 it != hosts_best_rcd.end(); ++it)
535 distinct_hosts.insert(it->first + it->second);
536 return distinct_hosts;
537 }
538
539 void PermissionSet::InitImplicitExtensionPermissions(
540 const extensions::Extension* extension) {
541 // Add the implied permissions.
542 if (!extension->plugins().empty())
543 apis_.insert(APIPermission::kPlugin);
544
545 if (!extension->devtools_url().is_empty())
546 apis_.insert(APIPermission::kDevtools);
547
548 // The webRequest permission implies the internal version as well.
549 if (apis_.find(APIPermission::kWebRequest) != apis_.end())
550 apis_.insert(APIPermission::kWebRequestInternal);
551
552 // The fileBrowserHandler permission implies the internal version as well.
553 if (apis_.find(APIPermission::kFileBrowserHandler) != apis_.end())
554 apis_.insert(APIPermission::kFileBrowserHandlerInternal);
555
556 // Add the scriptable hosts.
557 for (UserScriptList::const_iterator content_script =
558 extension->content_scripts().begin();
559 content_script != extension->content_scripts().end(); ++content_script) {
560 URLPatternSet::const_iterator pattern =
561 content_script->url_patterns().begin();
562 for (; pattern != content_script->url_patterns().end(); ++pattern)
563 scriptable_hosts_.AddPattern(*pattern);
564 }
565 }
566
567 void PermissionSet::InitEffectiveHosts() {
568 effective_hosts_.ClearPatterns();
569
570 URLPatternSet::CreateUnion(
571 explicit_hosts(), scriptable_hosts(), &effective_hosts_);
572 }
573
574 std::set<PermissionMessage>
575 PermissionSet::GetSimplePermissionMessages() const {
576 std::set<PermissionMessage> messages;
577 PermissionsInfo* info = PermissionsInfo::GetInstance();
578 for (APIPermissionSet::const_iterator i = apis_.begin();
579 i != apis_.end(); ++i) {
580 DCHECK_GT(PermissionMessage::kNone,
581 PermissionMessage::kUnknown);
582 APIPermission* perm = info->GetByID(*i);
583 if (perm && perm->message_id() > PermissionMessage::kNone)
584 messages.insert(perm->GetMessage());
585 }
586 return messages;
587 }
588
589 bool PermissionSet::HasLessAPIPrivilegesThan(
590 const PermissionSet* permissions) const {
591 if (permissions == NULL)
592 return false;
593
594 std::set<PermissionMessage> current_warnings =
595 GetSimplePermissionMessages();
596 std::set<PermissionMessage> new_warnings =
597 permissions->GetSimplePermissionMessages();
598 std::set<PermissionMessage> delta_warnings;
599 std::set_difference(new_warnings.begin(), new_warnings.end(),
600 current_warnings.begin(), current_warnings.end(),
601 std::inserter(delta_warnings, delta_warnings.begin()));
602
603 // We have less privileges if there are additional warnings present.
604 return !delta_warnings.empty();
605 }
606
607 bool PermissionSet::HasLessHostPrivilegesThan(
608 const PermissionSet* permissions) const {
609 // If this permission set can access any host, then it can't be elevated.
610 if (HasEffectiveAccessToAllHosts())
611 return false;
612
613 // Likewise, if the other permission set has full host access, then it must be
614 // a privilege increase.
615 if (permissions->HasEffectiveAccessToAllHosts())
616 return true;
617
618 const URLPatternSet& old_list = effective_hosts();
619 const URLPatternSet& new_list = permissions->effective_hosts();
620
621 // TODO(jstritar): This is overly conservative with respect to subdomains.
622 // For example, going from *.google.com to www.google.com will be
623 // considered an elevation, even though it is not (http://crbug.com/65337).
624 std::set<std::string> new_hosts_set(GetDistinctHosts(new_list, false, false));
625 std::set<std::string> old_hosts_set(GetDistinctHosts(old_list, false, false));
626 std::set<std::string> new_hosts_only;
627
628 std::set_difference(new_hosts_set.begin(), new_hosts_set.end(),
629 old_hosts_set.begin(), old_hosts_set.end(),
630 std::inserter(new_hosts_only, new_hosts_only.begin()));
631
632 return !new_hosts_only.empty();
633 }
634
635 bool PermissionSet::HasLessScopesThan(
636 const PermissionSet* permissions) const {
637 if (permissions == NULL)
638 return false;
639
640 OAuth2Scopes current_scopes = scopes();
641 OAuth2Scopes new_scopes = permissions->scopes();
642 OAuth2Scopes delta_scopes;
643 std::set_difference(new_scopes.begin(), new_scopes.end(),
644 current_scopes.begin(), current_scopes.end(),
645 std::inserter(delta_scopes, delta_scopes.begin()));
646
647 // We have less privileges if there are additional scopes present.
648 return !delta_scopes.empty();
649 }
650
651 } // namespace extensions
OLDNEW
« no previous file with comments | « chrome/common/extensions/permissions/permission_set.h ('k') | chrome/common/extensions/permissions/permission_set_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698