OLD | NEW |
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/common/extensions/extension.h" | 5 #include "chrome/common/extensions/extension.h" |
6 | 6 |
7 #include <algorithm> | 7 #include <algorithm> |
8 | 8 |
9 #include "base/base64.h" | 9 #include "base/base64.h" |
10 #include "base/basictypes.h" | 10 #include "base/basictypes.h" |
(...skipping 1111 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1122 *error = errors::kInvalidLaunchHeight; | 1122 *error = errors::kInvalidLaunchHeight; |
1123 return false; | 1123 return false; |
1124 } | 1124 } |
1125 } | 1125 } |
1126 | 1126 |
1127 return true; | 1127 return true; |
1128 } | 1128 } |
1129 | 1129 |
1130 bool Extension::LoadAppIsolation(const DictionaryValue* manifest, | 1130 bool Extension::LoadAppIsolation(const DictionaryValue* manifest, |
1131 std::string* error) { | 1131 std::string* error) { |
1132 // Only parse app isolation features if this switch is present. | |
1133 if (!CommandLine::ForCurrentProcess()->HasSwitch( | |
1134 switches::kEnableExperimentalExtensionApis)) | |
1135 return true; | |
1136 | |
1137 Value* temp = NULL; | 1132 Value* temp = NULL; |
1138 if (!manifest->Get(keys::kIsolation, &temp)) | 1133 if (!manifest->Get(keys::kIsolation, &temp)) |
1139 return true; | 1134 return true; |
1140 | 1135 |
1141 if (temp->GetType() != Value::TYPE_LIST) { | 1136 if (temp->GetType() != Value::TYPE_LIST) { |
1142 *error = errors::kInvalidIsolation; | 1137 *error = errors::kInvalidIsolation; |
1143 return false; | 1138 return false; |
1144 } | 1139 } |
1145 | 1140 |
1146 ListValue* isolation_list = static_cast<ListValue*>(temp); | 1141 ListValue* isolation_list = static_cast<ListValue*>(temp); |
(...skipping 342 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1489 | 1484 |
1490 // Initialize name. | 1485 // Initialize name. |
1491 string16 localized_name; | 1486 string16 localized_name; |
1492 if (!source.GetString(keys::kName, &localized_name)) { | 1487 if (!source.GetString(keys::kName, &localized_name)) { |
1493 *error = errors::kInvalidName; | 1488 *error = errors::kInvalidName; |
1494 return false; | 1489 return false; |
1495 } | 1490 } |
1496 base::i18n::AdjustStringForLocaleDirection(&localized_name); | 1491 base::i18n::AdjustStringForLocaleDirection(&localized_name); |
1497 name_ = UTF16ToUTF8(localized_name); | 1492 name_ = UTF16ToUTF8(localized_name); |
1498 | 1493 |
| 1494 // Initialize the permissions (optional). |
| 1495 ExtensionAPIPermissionSet api_permissions; |
| 1496 URLPatternSet host_permissions; |
| 1497 if (!ParsePermissions(&source, |
| 1498 keys::kPermissions, |
| 1499 flags, |
| 1500 error, |
| 1501 &api_permissions, |
| 1502 &host_permissions)) { |
| 1503 return false; |
| 1504 } |
| 1505 |
| 1506 // Initialize the optional permissions (optional). |
| 1507 ExtensionAPIPermissionSet optional_api_permissions; |
| 1508 URLPatternSet optional_host_permissions; |
| 1509 if (!ParsePermissions(&source, |
| 1510 keys::kOptionalPermissions, |
| 1511 flags, |
| 1512 error, |
| 1513 &optional_api_permissions, |
| 1514 &optional_host_permissions)) { |
| 1515 return false; |
| 1516 } |
| 1517 |
1499 // Initialize description (if present). | 1518 // Initialize description (if present). |
1500 if (source.HasKey(keys::kDescription)) { | 1519 if (source.HasKey(keys::kDescription)) { |
1501 if (!source.GetString(keys::kDescription, | 1520 if (!source.GetString(keys::kDescription, |
1502 &description_)) { | 1521 &description_)) { |
1503 *error = errors::kInvalidDescription; | 1522 *error = errors::kInvalidDescription; |
1504 return false; | 1523 return false; |
1505 } | 1524 } |
1506 } | 1525 } |
1507 | 1526 |
1508 // Initialize homepage url (if present). | 1527 // Initialize homepage url (if present). |
(...skipping 267 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1776 errors::kInvalidNaClModulesMIMEType, base::IntToString(i)); | 1795 errors::kInvalidNaClModulesMIMEType, base::IntToString(i)); |
1777 return false; | 1796 return false; |
1778 } | 1797 } |
1779 | 1798 |
1780 nacl_modules_.push_back(NaClModuleInfo()); | 1799 nacl_modules_.push_back(NaClModuleInfo()); |
1781 nacl_modules_.back().url = GetResourceURL(path_str); | 1800 nacl_modules_.back().url = GetResourceURL(path_str); |
1782 nacl_modules_.back().mime_type = mime_type; | 1801 nacl_modules_.back().mime_type = mime_type; |
1783 } | 1802 } |
1784 } | 1803 } |
1785 | 1804 |
1786 // Initialize toolstrips. This is deprecated for public use. | 1805 // Initialize toolstrips. |
1787 // NOTE(erikkay) Although deprecated, we intend to preserve this parsing | 1806 // TODO(aa): Remove this and all the related tests, docs, etc. |
1788 // code indefinitely. Please contact me or Joi for details as to why. | 1807 // See: crbug.com/100488. |
1789 if (CommandLine::ForCurrentProcess()->HasSwitch( | 1808 if (api_permissions.count(ExtensionAPIPermission::kExperimental) && |
1790 switches::kEnableExperimentalExtensionApis) && | |
1791 source.HasKey(keys::kToolstrips)) { | 1809 source.HasKey(keys::kToolstrips)) { |
1792 ListValue* list_value = NULL; | 1810 ListValue* list_value = NULL; |
1793 if (!source.GetList(keys::kToolstrips, &list_value)) { | 1811 if (!source.GetList(keys::kToolstrips, &list_value)) { |
1794 *error = errors::kInvalidToolstrips; | 1812 *error = errors::kInvalidToolstrips; |
1795 return false; | 1813 return false; |
1796 } | 1814 } |
1797 | 1815 |
1798 for (size_t i = 0; i < list_value->GetSize(); ++i) { | 1816 for (size_t i = 0; i < list_value->GetSize(); ++i) { |
1799 GURL toolstrip; | 1817 GURL toolstrip; |
1800 DictionaryValue* toolstrip_value = NULL; | 1818 DictionaryValue* toolstrip_value = NULL; |
(...skipping 115 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1916 } | 1934 } |
1917 | 1935 |
1918 // Load App settings. | 1936 // Load App settings. |
1919 if (!LoadIsApp(manifest_value_.get(), error) || | 1937 if (!LoadIsApp(manifest_value_.get(), error) || |
1920 !LoadExtent(manifest_value_.get(), keys::kWebURLs, | 1938 !LoadExtent(manifest_value_.get(), keys::kWebURLs, |
1921 &extent_, | 1939 &extent_, |
1922 errors::kInvalidWebURLs, errors::kInvalidWebURL, | 1940 errors::kInvalidWebURLs, errors::kInvalidWebURL, |
1923 parse_strictness, error) || | 1941 parse_strictness, error) || |
1924 !EnsureNotHybridApp(manifest_value_.get(), error) || | 1942 !EnsureNotHybridApp(manifest_value_.get(), error) || |
1925 !LoadLaunchURL(manifest_value_.get(), error) || | 1943 !LoadLaunchURL(manifest_value_.get(), error) || |
1926 !LoadLaunchContainer(manifest_value_.get(), error) || | 1944 !LoadLaunchContainer(manifest_value_.get(), error)) |
1927 !LoadAppIsolation(manifest_value_.get(), error)) { | |
1928 return false; | 1945 return false; |
| 1946 |
| 1947 if (api_permissions.count(ExtensionAPIPermission::kExperimental)) { |
| 1948 if (!LoadAppIsolation(manifest_value_.get(), error)) |
| 1949 return false; |
1929 } | 1950 } |
1930 | 1951 |
1931 // Initialize options page url (optional). | 1952 // Initialize options page url (optional). |
1932 // Function LoadIsApp() set is_app_ above. | 1953 // Function LoadIsApp() set is_app_ above. |
1933 if (source.HasKey(keys::kOptionsPage)) { | 1954 if (source.HasKey(keys::kOptionsPage)) { |
1934 std::string options_str; | 1955 std::string options_str; |
1935 if (!source.GetString(keys::kOptionsPage, &options_str)) { | 1956 if (!source.GetString(keys::kOptionsPage, &options_str)) { |
1936 *error = errors::kInvalidOptionsPage; | 1957 *error = errors::kInvalidOptionsPage; |
1937 return false; | 1958 return false; |
1938 } | 1959 } |
(...skipping 14 matching lines...) Expand all Loading... |
1953 return false; | 1974 return false; |
1954 } | 1975 } |
1955 options_url_ = GetResourceURL(options_str); | 1976 options_url_ = GetResourceURL(options_str); |
1956 if (!options_url_.is_valid()) { | 1977 if (!options_url_.is_valid()) { |
1957 *error = errors::kInvalidOptionsPage; | 1978 *error = errors::kInvalidOptionsPage; |
1958 return false; | 1979 return false; |
1959 } | 1980 } |
1960 } | 1981 } |
1961 } | 1982 } |
1962 | 1983 |
1963 // Initialize the permissions (optional). | |
1964 ExtensionAPIPermissionSet api_permissions; | |
1965 URLPatternSet host_permissions; | |
1966 if (!ParsePermissions(&source, | |
1967 keys::kPermissions, | |
1968 flags, | |
1969 error, | |
1970 &api_permissions, | |
1971 &host_permissions)) { | |
1972 return false; | |
1973 } | |
1974 | |
1975 // Initialize the optional permissions (optional). | |
1976 ExtensionAPIPermissionSet optional_api_permissions; | |
1977 URLPatternSet optional_host_permissions; | |
1978 if (!ParsePermissions(&source, | |
1979 keys::kOptionalPermissions, | |
1980 flags, | |
1981 error, | |
1982 &optional_api_permissions, | |
1983 &optional_host_permissions)) { | |
1984 return false; | |
1985 } | |
1986 | |
1987 // Initialize background url (optional). | 1984 // Initialize background url (optional). |
1988 if (source.HasKey(keys::kBackground)) { | 1985 if (source.HasKey(keys::kBackground)) { |
1989 std::string background_str; | 1986 std::string background_str; |
1990 if (!source.GetString(keys::kBackground, &background_str)) { | 1987 if (!source.GetString(keys::kBackground, &background_str)) { |
1991 *error = errors::kInvalidBackground; | 1988 *error = errors::kInvalidBackground; |
1992 return false; | 1989 return false; |
1993 } | 1990 } |
1994 | 1991 |
1995 if (is_hosted_app()) { | 1992 if (is_hosted_app()) { |
1996 // Make sure "background" permission is set. | 1993 // Make sure "background" permission is set. |
(...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2063 chrome_url_overrides_[page] = GetResourceURL(val); | 2060 chrome_url_overrides_[page] = GetResourceURL(val); |
2064 } | 2061 } |
2065 | 2062 |
2066 // An extension may override at most one page. | 2063 // An extension may override at most one page. |
2067 if (overrides->size() > 1) { | 2064 if (overrides->size() > 1) { |
2068 *error = errors::kMultipleOverrides; | 2065 *error = errors::kMultipleOverrides; |
2069 return false; | 2066 return false; |
2070 } | 2067 } |
2071 } | 2068 } |
2072 | 2069 |
2073 if (CommandLine::ForCurrentProcess()->HasSwitch( | 2070 if (api_permissions.count(ExtensionAPIPermission::kExperimental) && |
2074 switches::kEnableExperimentalExtensionApis) && | |
2075 source.HasKey(keys::kInputComponents)) { | 2071 source.HasKey(keys::kInputComponents)) { |
2076 ListValue* list_value = NULL; | 2072 ListValue* list_value = NULL; |
2077 if (!source.GetList(keys::kInputComponents, &list_value)) { | 2073 if (!source.GetList(keys::kInputComponents, &list_value)) { |
2078 *error = errors::kInvalidInputComponents; | 2074 *error = errors::kInvalidInputComponents; |
2079 return false; | 2075 return false; |
2080 } | 2076 } |
2081 | 2077 |
2082 for (size_t i = 0; i < list_value->GetSize(); ++i) { | 2078 for (size_t i = 0; i < list_value->GetSize(); ++i) { |
2083 DictionaryValue* module_value = NULL; | 2079 DictionaryValue* module_value = NULL; |
2084 std::string name_str; | 2080 std::string name_str; |
(...skipping 506 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2591 std::string permission_str; | 2587 std::string permission_str; |
2592 if (!permissions->GetString(i, &permission_str)) { | 2588 if (!permissions->GetString(i, &permission_str)) { |
2593 *error = ExtensionErrorUtils::FormatErrorMessage( | 2589 *error = ExtensionErrorUtils::FormatErrorMessage( |
2594 errors::kInvalidPermission, base::IntToString(i)); | 2590 errors::kInvalidPermission, base::IntToString(i)); |
2595 return false; | 2591 return false; |
2596 } | 2592 } |
2597 | 2593 |
2598 ExtensionAPIPermission* permission = | 2594 ExtensionAPIPermission* permission = |
2599 ExtensionPermissionsInfo::GetInstance()->GetByName(permission_str); | 2595 ExtensionPermissionsInfo::GetInstance()->GetByName(permission_str); |
2600 | 2596 |
2601 // Only COMPONENT extensions can use private APIs. | 2597 if (permission != NULL) { |
2602 // TODO(asargent) - We want a more general purpose mechanism for this, | 2598 if (CanSpecifyAPIPermission(permission, error)) |
2603 // and better error messages. (http://crbug.com/54013) | 2599 api_permissions->insert(permission->id()); |
2604 if (!IsComponentOnlyPermission(permission) | 2600 |
2605 #ifndef NDEBUG | 2601 // Sometimes when you can't specify an API permission we ignore it |
2606 && !CommandLine::ForCurrentProcess()->HasSwitch( | 2602 // silently. This seems like a bug. Specifically, crbug.com/100489. |
2607 switches::kExposePrivateExtensionApi) | 2603 if (!error->empty()) |
2608 #endif | 2604 return false; |
2609 ) { | 2605 |
2610 continue; | 2606 continue; |
2611 } | 2607 } |
2612 | 2608 |
2613 if (web_extent().is_empty() || location() == Extension::COMPONENT) { | |
2614 // Check if it's a module permission. If so, enable that permission. | |
2615 if (permission != NULL) { | |
2616 // Only allow the experimental API permission if the command line | |
2617 // flag is present, or if the extension is a component of Chrome. | |
2618 if (IsDisallowedExperimentalPermission(permission->id()) && | |
2619 location() != Extension::COMPONENT) { | |
2620 *error = errors::kExperimentalFlagRequired; | |
2621 return false; | |
2622 } | |
2623 api_permissions->insert(permission->id()); | |
2624 continue; | |
2625 } | |
2626 } else { | |
2627 // Hosted apps only get access to a subset of the valid permissions. | |
2628 if (permission != NULL && permission->is_hosted_app()) { | |
2629 if (IsDisallowedExperimentalPermission(permission->id())) { | |
2630 *error = errors::kExperimentalFlagRequired; | |
2631 return false; | |
2632 } | |
2633 api_permissions->insert(permission->id()); | |
2634 continue; | |
2635 } | |
2636 } | |
2637 | |
2638 // Check if it's a host pattern permission. | 2609 // Check if it's a host pattern permission. |
2639 URLPattern pattern = URLPattern(CanExecuteScriptEverywhere() ? | 2610 URLPattern pattern = URLPattern(CanExecuteScriptEverywhere() ? |
2640 URLPattern::SCHEME_ALL : kValidHostPermissionSchemes); | 2611 URLPattern::SCHEME_ALL : kValidHostPermissionSchemes); |
2641 | 2612 |
2642 URLPattern::ParseResult parse_result = pattern.Parse(permission_str, | 2613 URLPattern::ParseResult parse_result = pattern.Parse(permission_str, |
2643 parse_strictness); | 2614 parse_strictness); |
2644 if (parse_result == URLPattern::PARSE_SUCCESS) { | 2615 if (parse_result == URLPattern::PARSE_SUCCESS) { |
2645 if (!CanSpecifyHostPermission(pattern)) { | 2616 if (!CanSpecifyHostPermission(pattern)) { |
2646 *error = ExtensionErrorUtils::FormatErrorMessage( | 2617 *error = ExtensionErrorUtils::FormatErrorMessage( |
2647 errors::kInvalidPermissionScheme, base::IntToString(i)); | 2618 errors::kInvalidPermissionScheme, base::IntToString(i)); |
(...skipping 105 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2753 base::AutoLock auto_lock(runtime_data_lock_); | 2724 base::AutoLock auto_lock(runtime_data_lock_); |
2754 runtime_data_.SetActivePermissions(permissions); | 2725 runtime_data_.SetActivePermissions(permissions); |
2755 } | 2726 } |
2756 | 2727 |
2757 scoped_refptr<const ExtensionPermissionSet> | 2728 scoped_refptr<const ExtensionPermissionSet> |
2758 Extension::GetActivePermissions() const { | 2729 Extension::GetActivePermissions() const { |
2759 base::AutoLock auto_lock(runtime_data_lock_); | 2730 base::AutoLock auto_lock(runtime_data_lock_); |
2760 return runtime_data_.GetActivePermissions(); | 2731 return runtime_data_.GetActivePermissions(); |
2761 } | 2732 } |
2762 | 2733 |
2763 bool Extension::IsComponentOnlyPermission( | |
2764 const ExtensionAPIPermission* api) const { | |
2765 if (location() == Extension::COMPONENT) | |
2766 return true; | |
2767 | |
2768 if (api == NULL) | |
2769 return true; | |
2770 | |
2771 return !api->is_component_only(); | |
2772 } | |
2773 | |
2774 bool Extension::HasMultipleUISurfaces() const { | 2734 bool Extension::HasMultipleUISurfaces() const { |
2775 int num_surfaces = 0; | 2735 int num_surfaces = 0; |
2776 | 2736 |
2777 if (page_action()) | 2737 if (page_action()) |
2778 ++num_surfaces; | 2738 ++num_surfaces; |
2779 | 2739 |
2780 if (browser_action()) | 2740 if (browser_action()) |
2781 ++num_surfaces; | 2741 ++num_surfaces; |
2782 | 2742 |
2783 if (is_app()) | 2743 if (is_app()) |
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2835 return location() != Extension::COMPONENT; | 2795 return location() != Extension::COMPONENT; |
2836 } | 2796 } |
2837 | 2797 |
2838 bool Extension::ImplicitlyDelaysNetworkStartup() const { | 2798 bool Extension::ImplicitlyDelaysNetworkStartup() const { |
2839 // Network requests should be deferred until any extensions that might want | 2799 // Network requests should be deferred until any extensions that might want |
2840 // to observe or modify them are loaded. | 2800 // to observe or modify them are loaded. |
2841 return HasAPIPermission(ExtensionAPIPermission::kWebNavigation) || | 2801 return HasAPIPermission(ExtensionAPIPermission::kWebNavigation) || |
2842 HasAPIPermission(ExtensionAPIPermission::kWebRequest); | 2802 HasAPIPermission(ExtensionAPIPermission::kWebRequest); |
2843 } | 2803 } |
2844 | 2804 |
2845 bool Extension::IsDisallowedExperimentalPermission( | 2805 bool Extension::CanSpecifyAPIPermission( |
2846 ExtensionAPIPermission::ID permission) const { | 2806 const ExtensionAPIPermission* permission, |
2847 return permission == ExtensionAPIPermission::kExperimental && | 2807 std::string* error) const { |
2848 !CommandLine::ForCurrentProcess()->HasSwitch( | 2808 if (permission->is_component_only()) { |
2849 switches::kEnableExperimentalExtensionApis); | 2809 if (!CanSpecifyComponentOnlyPermission()) |
| 2810 return false; |
| 2811 } |
| 2812 |
| 2813 if (permission->id() == ExtensionAPIPermission::kExperimental) { |
| 2814 if (!CanSpecifyExperimentalPermission()) { |
| 2815 *error = errors::kExperimentalFlagRequired; |
| 2816 return false; |
| 2817 } |
| 2818 } |
| 2819 |
| 2820 if (is_hosted_app()) { |
| 2821 if (!CanSpecifyPermissionForHostedApp(permission)) |
| 2822 return false; |
| 2823 } |
| 2824 |
| 2825 return true; |
| 2826 } |
| 2827 |
| 2828 bool Extension::CanSpecifyComponentOnlyPermission() const { |
| 2829 // Only COMPONENT extensions can use private APIs. |
| 2830 // TODO(asargent) - We want a more general purpose mechanism for this, |
| 2831 // and better error messages. (http://crbug.com/54013) |
| 2832 if (location_ == Extension::COMPONENT) |
| 2833 return true; |
| 2834 |
| 2835 #ifndef NDEBUG |
| 2836 if (CommandLine::ForCurrentProcess()->HasSwitch( |
| 2837 switches::kExposePrivateExtensionApi)) { |
| 2838 return true; |
| 2839 } |
| 2840 #endif |
| 2841 |
| 2842 return false; |
| 2843 } |
| 2844 |
| 2845 bool Extension::CanSpecifyExperimentalPermission() const { |
| 2846 if (location_ == Extension::COMPONENT) |
| 2847 return true; |
| 2848 |
| 2849 if (CommandLine::ForCurrentProcess()->HasSwitch( |
| 2850 switches::kEnableExperimentalExtensionApis)) { |
| 2851 return true; |
| 2852 } |
| 2853 |
| 2854 // We rely on the webstore to check access to experimental. This way we can |
| 2855 // whitelist extensions to have access to experimental in just the store, and |
| 2856 // not have to push a new version of the client. |
| 2857 if (from_webstore()) |
| 2858 return true; |
| 2859 |
| 2860 return false; |
| 2861 } |
| 2862 |
| 2863 bool Extension::CanSpecifyPermissionForHostedApp( |
| 2864 const ExtensionAPIPermission* permission) const { |
| 2865 if (location_ == Extension::COMPONENT) |
| 2866 return true; |
| 2867 |
| 2868 if (permission->is_hosted_app()) |
| 2869 return true; |
| 2870 |
| 2871 return false; |
2850 } | 2872 } |
2851 | 2873 |
2852 bool Extension::CanExecuteScriptEverywhere() const { | 2874 bool Extension::CanExecuteScriptEverywhere() const { |
2853 if (location() == Extension::COMPONENT | 2875 if (location() == Extension::COMPONENT |
2854 #ifndef NDEBUG | 2876 #ifndef NDEBUG |
2855 || CommandLine::ForCurrentProcess()->HasSwitch( | 2877 || CommandLine::ForCurrentProcess()->HasSwitch( |
2856 switches::kExposePrivateExtensionApi) | 2878 switches::kExposePrivateExtensionApi) |
2857 #endif | 2879 #endif |
2858 ) | 2880 ) |
2859 return true; | 2881 return true; |
(...skipping 129 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2989 already_disabled(false), | 3011 already_disabled(false), |
2990 extension(extension) {} | 3012 extension(extension) {} |
2991 | 3013 |
2992 UpdatedExtensionPermissionsInfo::UpdatedExtensionPermissionsInfo( | 3014 UpdatedExtensionPermissionsInfo::UpdatedExtensionPermissionsInfo( |
2993 const Extension* extension, | 3015 const Extension* extension, |
2994 const ExtensionPermissionSet* permissions, | 3016 const ExtensionPermissionSet* permissions, |
2995 Reason reason) | 3017 Reason reason) |
2996 : reason(reason), | 3018 : reason(reason), |
2997 extension(extension), | 3019 extension(extension), |
2998 permissions(permissions) {} | 3020 permissions(permissions) {} |
OLD | NEW |