File: url_loader_factory_manager.cc

package info (click to toggle)
chromium 138.0.7204.183-1
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 6,071,908 kB
  • sloc: cpp: 34,937,088; ansic: 7,176,967; javascript: 4,110,704; python: 1,419,953; asm: 946,768; xml: 739,971; pascal: 187,324; sh: 89,623; perl: 88,663; objc: 79,944; sql: 50,304; cs: 41,786; fortran: 24,137; makefile: 21,806; php: 13,980; tcl: 13,166; yacc: 8,925; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (257 lines) | stat: -rw-r--r-- 9,862 bytes parent folder | download | duplicates (5)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "extensions/browser/url_loader_factory_manager.h"

#include <algorithm>
#include <utility>
#include <vector>

#include "base/feature_list.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/process_map.h"
#include "extensions/browser/script_injection_tracker.h"
#include "extensions/common/constants.h"
#include "extensions/common/cors_util.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_features.h"
#include "extensions/common/extension_set.h"
#include "extensions/common/manifest_handlers/permissions_parser.h"
#include "extensions/common/mojom/host_id.mojom.h"
#include "extensions/common/permissions/permissions_data.h"
#include "extensions/common/url_pattern.h"
#include "extensions/common/url_pattern_set.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "services/network/public/mojom/network_context.mojom.h"
#include "services/network/public/mojom/url_loader_factory.mojom.h"
#include "url/gurl.h"
#include "url/origin.h"
#include "url/scheme_host_port.h"
#include "url/url_constants.h"

namespace extensions {

namespace {

enum class FactoryUser {
  kContentScript,
  kExtensionProcess,
};

bool DoContentScriptsDependOnRelaxedOrbOrCors(const Extension& extension) {
  // Content scripts injected by Chrome Apps (e.g. into <webview> tag) need to
  // run with relaxed ORB.
  //
  // TODO(crbug.com/40158699): Remove this exception once Chrome Platform
  // Apps are gone.
  if (extension.is_platform_app()) {
    return true;
  }

  // Content scripts are not granted an ability to relax ORB and/or CORS.
  return false;
}

bool DoExtensionPermissionsCoverHttpOrHttpsOrigins(
    const PermissionSet& permissions) {
  // Looking at explicit (rather than effective) hosts results in stricter
  // checks that better match ORB/CORS behavior.
  return std::ranges::any_of(
      permissions.explicit_hosts(), [](const URLPattern& permission) {
        return permission.MatchesScheme(url::kHttpScheme) ||
               permission.MatchesScheme(url::kHttpsScheme);
      });
}

bool DoExtensionPermissionsCoverHttpOrHttpsOrigins(const Extension& extension) {
  // Extension with an ActiveTab permission can later gain permission to access
  // any http origin (once the ActiveTab permission is activated).
  const PermissionsData* permissions = extension.permissions_data();
  if (permissions->HasAPIPermission(mojom::APIPermissionID::kActiveTab)) {
    return true;
  }

  // Optional extension permissions to http origins may be granted later.
  //
  // TODO(lukasza): Consider only handing out ORB/CORS-disabled
  // URLLoaderFactory after the optional permission is *actually* granted.  Care
  // might need to be take to make sure that updating the URLLoaderFactory is
  // robust in presence of races (the new factory should reach the all [?]
  // extension frames/contexts *before* the ack/response about the newly granted
  // permission).
  if (DoExtensionPermissionsCoverHttpOrHttpsOrigins(
          PermissionsParser::GetOptionalPermissions(&extension))) {
    return true;
  }

  // Check required extension permissions.  Note that this is broader than
  // `permissions->GetEffectiveHostPermissions()` to account for policy that may
  // change at runtime.
  if (DoExtensionPermissionsCoverHttpOrHttpsOrigins(
          PermissionsParser::GetRequiredPermissions(&extension))) {
    return true;
  }

  // Otherwise, report that the `extension` will never get HTTP permissions.
  return false;
}

// Returns whether to allow bypassing CORS (by disabling ORB, and paying
// attention to the `isolated_world_origin` from content scripts, and using
// SecFetchSiteValue::kNoOrigin from extensions).
bool ShouldRelaxCors(const Extension& extension, FactoryUser factory_user) {
  if (!DoExtensionPermissionsCoverHttpOrHttpsOrigins(extension)) {
    return false;
  }

  switch (factory_user) {
    case FactoryUser::kContentScript:
      return DoContentScriptsDependOnRelaxedOrbOrCors(extension);
    case FactoryUser::kExtensionProcess:
      return true;
  }
}

bool ShouldCreateSeparateFactoryForContentScripts(const Extension& extension) {
  return ShouldRelaxCors(extension, FactoryUser::kContentScript);
}

void OverrideFactoryParams(const Extension& extension,
                           bool is_for_service_worker,
                           FactoryUser factory_user,
                           network::mojom::URLLoaderFactoryParams* params) {
  if (is_for_service_worker &&
      base::FeatureList::IsEnabled(
          extensions_features::kSkipResetServiceWorkerURLLoaderFactories)) {
    CHECK_EQ(factory_user, FactoryUser::kExtensionProcess);
    params->ignore_factory_reset = true;
  }

  if (ShouldRelaxCors(extension, factory_user)) {
    params->is_orb_enabled = false;
    switch (factory_user) {
      case FactoryUser::kContentScript:
        // Requests from content scripts set
        // network::ResourceRequest::isolated_world_origin to the origin of the
        // extension.  This field of ResourceRequest is normally ignored, but by
        // setting `ignore_isolated_world_origin` to false below, we ensure that
        // OOR-CORS will use the extension origin when checking if content
        // script requests should bypass CORS.
        params->ignore_isolated_world_origin = false;
        break;
      case FactoryUser::kExtensionProcess:
        params->unsafe_non_webby_initiator = true;
        break;
    }
  }
}

void MarkIsolatedWorldsAsRequiringSeparateURLLoaderFactory(
    content::RenderFrameHost* frame,
    const std::vector<url::Origin>& request_initiators,
    bool push_to_renderer_now) {
  DCHECK(!request_initiators.empty());
  frame->MarkIsolatedWorldsAsRequiringSeparateURLLoaderFactory(
      request_initiators, push_to_renderer_now);
}

}  // namespace

// static
void URLLoaderFactoryManager::WillInjectContentScriptsWhenNavigationCommits(
    base::PassKey<ScriptInjectionTracker> pass_key,
    content::NavigationHandle* navigation,
    const std::vector<const Extension*>& extensions) {
  // Same-document navigations do not send URLLoaderFactories to the renderer
  // process.
  if (navigation->IsSameDocument()) {
    return;
  }

  std::vector<url::Origin> initiators_requiring_separate_factory;
  for (const Extension* extension : extensions) {
    if (!ShouldCreateSeparateFactoryForContentScripts(*extension)) {
      continue;
    }

    initiators_requiring_separate_factory.push_back(extension->origin());
  }

  if (!initiators_requiring_separate_factory.empty()) {
    // At ReadyToCommitNavigation time there is no need to trigger an explicit
    // push of URLLoaderFactoryBundle to the renderer - it is sufficient if the
    // factories are pushed slightly later - during the commit.
    constexpr bool kPushToRendererNow = false;

    MarkIsolatedWorldsAsRequiringSeparateURLLoaderFactory(
        navigation->GetRenderFrameHost(), initiators_requiring_separate_factory,
        kPushToRendererNow);
  }
}

// static
void URLLoaderFactoryManager::WillProgrammaticallyInjectContentScript(
    base::PassKey<ScriptInjectionTracker> pass_key,
    content::RenderFrameHost* frame,
    const Extension& extension) {
  if (!ShouldCreateSeparateFactoryForContentScripts(extension)) {
    return;
  }

  // When WillExecuteCode runs, the frame already received the initial
  // URLLoaderFactoryBundle - therefore we need to request a separate push
  // below.  This doesn't race with the ExecuteCode mojo message,
  // because the URLLoaderFactoryBundle is sent to the renderer over
  // content.mojom.Frame interface which is associated with the
  // extensions.mojom.LocalFrame (raciness will be introduced if that ever
  // changes).
  constexpr bool kPushToRendererNow = true;

  MarkIsolatedWorldsAsRequiringSeparateURLLoaderFactory(
      frame, {extension.origin()}, kPushToRendererNow);
}

// static
void URLLoaderFactoryManager::OverrideURLLoaderFactoryParams(
    content::BrowserContext* browser_context,
    const url::Origin& origin,
    bool is_for_isolated_world,
    bool is_for_service_worker,
    network::mojom::URLLoaderFactoryParams* factory_params) {
  const ExtensionRegistry* registry = ExtensionRegistry::Get(browser_context);
  DCHECK(registry);  // CreateFactory shouldn't happen during shutdown.

  // Opaque origins normally don't inherit security properties of their
  // precursor origins, but here opaque origins (e.g. think data: URIs) created
  // by an extension should inherit CORS/ORB treatment of the extension.
  url::SchemeHostPort precursor_origin =
      origin.GetTupleOrPrecursorTupleIfOpaque();

  // Don't change factory params for something that is not an extension.
  if (precursor_origin.scheme() != kExtensionScheme) {
    return;
  }

  // Find the |extension| associated with |initiator_origin|.
  const Extension* extension =
      registry->enabled_extensions().GetByID(precursor_origin.host());
  if (!extension) {
    // This may happen if an extension gets disabled between the time
    // RenderFrameHost::MarkIsolatedWorldAsRequiringSeparateURLLoaderFactory is
    // called and the time
    // ContentBrowserClient::OverrideURLLoaderFactoryParams is called.
    return;
  }

  // Identify and set |factory_params| that need to be overridden.
  FactoryUser factory_user = is_for_isolated_world
                                 ? FactoryUser::kContentScript
                                 : FactoryUser::kExtensionProcess;
  OverrideFactoryParams(*extension, is_for_service_worker, factory_user,
                        factory_params);
}

}  // namespace extensions