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

Side by Side Diff: chrome/browser/policy/device_management_service.cc

Issue 5153002: Use a service to create device management backends. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 10 years, 1 month 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 | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2010 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/browser/policy/device_management_service.h"
6
7 #include <utility>
8 #include <set>
9 #include <vector>
10
11 #include "base/command_line.h"
12 #include "base/lazy_instance.h"
13 #include "base/stl_util-inl.h"
14 #include "base/stringprintf.h"
15 #include "chrome/browser/browser_thread.h"
16 #include "chrome/common/chrome_switches.h"
17 #include "chrome/common/net/url_request_context_getter.h"
18 #include "net/base/cookie_monster.h"
19 #include "net/base/escape.h"
20 #include "net/base/host_resolver.h"
21 #include "net/base/load_flags.h"
22 #include "net/base/ssl_config_service_defaults.h"
23 #include "net/http/http_auth_handler_factory.h"
24 #include "net/http/http_network_layer.h"
25 #include "net/proxy/proxy_service.h"
26 #include "net/url_request/url_request_context.h"
27 #include "net/url_request/url_request_status.h"
28 #include "chrome/browser/browser_process.h"
29 #include "chrome/browser/io_thread.h"
30 #include "chrome/browser/net/chrome_net_log.h"
31 #include "chrome/browser/profile.h"
32 #include "chrome/common/chrome_switches.h"
33 #include "chrome/common/chrome_version_info.h"
34
35 namespace policy {
36
37 namespace {
38
39 // Name constants for URL query parameters.
40 const char kServiceParamRequest[] = "request";
41 const char kServiceParamDeviceType[] = "devicetype";
42 const char kServiceParamDeviceID[] = "deviceid";
markusheintz_ 2010/11/18 16:12:49 s/deviceid/device/
Mattias Nissler (ping if slow) 2010/11/19 16:03:56 Na, the parameter is really called deviceid.
markusheintz_ 2010/11/19 17:13:13 Well the doc says something different, but I trust
43 const char kServiceParamAgent[] = "agent";
markusheintz_ 2010/11/18 16:12:49 Please add: const char kServiceParamAppType[] = "
Mattias Nissler (ping if slow) 2010/11/19 16:03:56 Done.
44
45 // String constants for the device type and agent we report to the service.
46 const char kServiceValueDeviceType[] = "Chrome";
markusheintz_ 2010/11/18 16:12:49 Pls add: const char kServiceValueAppType[] = "Chr
Mattias Nissler (ping if slow) 2010/11/19 16:03:56 Done.
47 const char kServiceValueAgent[] =
48 "%s enterprise management client version %s (%s)";
49
50 const char kServiceTokenAuthHeader[] = "Authorization: GoogleLogin auth=";
51 const char kDMTokenAuthHeader[] = "Authorization: GoogleDMToken token=";
52
53 // Helper class for URL query parameter encoding/decoding.
54 class URLQueryParameters {
55 public:
56 URLQueryParameters() {}
57
58 // Add a query parameter.
59 void Put(const std::string& name, const std::string& value);
60
61 // Produce the query string, taking care of properly encoding and assembling
62 // the names and values.
63 std::string Encode();
64
65 private:
66 typedef std::vector<std::pair<std::string, std::string> > ParameterMap;
67 ParameterMap params_;
68
69 DISALLOW_COPY_AND_ASSIGN(URLQueryParameters);
70 };
71
72 void URLQueryParameters::Put(const std::string& name,
73 const std::string& value) {
74 params_.push_back(std::make_pair(name, value));
75 }
76
77 std::string URLQueryParameters::Encode() {
78 std::string result;
79 for (ParameterMap::const_iterator entry(params_.begin());
80 entry != params_.end();
81 ++entry) {
82 if (entry != params_.begin())
83 result += '&';
84 result += EscapeUrlEncodedData(entry->first);
85 result += '=';
86 result += EscapeUrlEncodedData(entry->second);
87 }
88 return result;
89 }
90
91 // Custom request context implementation that allows to override the user agent,
92 // amongst others. Wraps a baseline request context from which we reuse the
93 // networking components.
94 class DeviceManagementBackendRequestContext : public URLRequestContext {
95 public:
96 explicit DeviceManagementBackendRequestContext(
97 URLRequestContext* base_context);
98 virtual ~DeviceManagementBackendRequestContext();
99
100 private:
101 virtual const std::string& GetUserAgent(const GURL& url) const;
102
103 std::string user_agent_;
104 };
105
106 DeviceManagementBackendRequestContext::DeviceManagementBackendRequestContext(
107 URLRequestContext* base_context) {
108 // Share resolver, proxy service and ssl bits with the baseline context. This
109 // is important so we don't make redundant requests (e.g. when resolving proxy
110 // auto configuration).
111 net_log_ = base_context->net_log();
112 host_resolver_ = base_context->host_resolver();
113 proxy_service_ = base_context->proxy_service();
114 ssl_config_service_ = base_context->ssl_config_service();
115
116 // Share the http session.
117 http_transaction_factory_ = net::HttpNetworkLayer::CreateFactory(
118 base_context->http_transaction_factory()->GetSession());
119
120 // No cookies, please.
121 cookie_store_ = new net::CookieMonster(NULL, NULL);
122
123 // Initialize these to sane values for our purposes.
124 user_agent_ = DeviceManagementService::GetAgentString();
125 accept_language_ = "*";
126 accept_charset_ = "*";
127 }
128
129 DeviceManagementBackendRequestContext
130 ::~DeviceManagementBackendRequestContext() {
131 delete http_transaction_factory_;
132 delete http_auth_handler_factory_;
133 }
134
135 const std::string&
136 DeviceManagementBackendRequestContext::GetUserAgent(const GURL& url) const {
137 return user_agent_;
138 }
139
140 // Request context holder.
141 class DeviceManagementBackendRequestContextGetter
142 : public URLRequestContextGetter {
143 public:
144 DeviceManagementBackendRequestContextGetter(
145 URLRequestContextGetter* base_context_getter)
146 : base_context_getter_(base_context_getter) {}
147
148 // URLRequestContextGetter overrides.
149 virtual URLRequestContext* GetURLRequestContext();
150 virtual scoped_refptr<base::MessageLoopProxy> GetIOMessageLoopProxy() const;
151
152 private:
153 scoped_refptr<URLRequestContext> context_;
154 scoped_refptr<URLRequestContextGetter> base_context_getter_;
155 };
156
157
158 URLRequestContext*
159 DeviceManagementBackendRequestContextGetter::GetURLRequestContext() {
160 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
161 if (!context_) {
162 context_ = new DeviceManagementBackendRequestContext(
163 base_context_getter_->GetURLRequestContext());
164 }
165
166 return context_.get();
167 }
168
169 scoped_refptr<base::MessageLoopProxy>
170 DeviceManagementBackendRequestContextGetter::GetIOMessageLoopProxy() const {
171 return BrowserThread::GetMessageLoopProxyForThread(BrowserThread::IO);
172 }
173
174 } // namespace
175
176 // A wrapper that implements the actual backend interface. This is separate from
markusheintz_ 2010/11/18 16:12:49 This class could live in its own .h .cc file. Wdy
Mattias Nissler (ping if slow) 2010/11/19 16:03:56 Done. Also renamed to DeviceManagementBackendImpl.
177 // DeviceManagementService so the consumer can cancel all pending requests by
178 // destroying the backend object.
179 class DeviceManagementBackendProxy : public DeviceManagementBackend {
180 public:
181 explicit DeviceManagementBackendProxy(DeviceManagementService* service);
182 virtual ~DeviceManagementBackendProxy();
183
184 // Called by the DeviceManagementJob dtor so we can clean up.
185 void JobDone(DeviceManagementJob* job);
186
187 private:
188 typedef std::set<DeviceManagementJob*> JobSet;
189
190 // Add a job to the pending job set and register it with the service (if
191 // available).
192 void AddJob(DeviceManagementJob* job);
193
194 // DeviceManagementBackend overrides.
195 virtual void ProcessRegisterRequest(
196 const std::string& auth_token,
197 const std::string& device_id,
198 const em::DeviceRegisterRequest& request,
199 DeviceRegisterResponseDelegate* response_delegate);
200 virtual void ProcessUnregisterRequest(
201 const std::string& device_management_token,
202 const em::DeviceUnregisterRequest& request,
203 DeviceUnregisterResponseDelegate* response_delegate);
204 virtual void ProcessPolicyRequest(
205 const std::string& device_management_token,
206 const em::DevicePolicyRequest& request,
207 DevicePolicyResponseDelegate* response_delegate);
208
209 // Keeps track of the jobs currently in flight.
210 JobSet pending_jobs_;
211
212 DeviceManagementService* service_;
213
214 DISALLOW_COPY_AND_ASSIGN(DeviceManagementBackendProxy);
215 };
216
217 // Represents a job run by the service. This contains the common code,
markusheintz_ 2010/11/18 16:12:49 I somehow think the DeviceManagementJob classes sh
Mattias Nissler (ping if slow) 2010/11/19 16:03:56 These are really implementation details for the Ba
218 // subclasses provide custom code for actual register, unregister, and policy
219 // jobs.
220 class DeviceManagementJob {
221 public:
222 virtual ~DeviceManagementJob() {
223 proxy_->JobDone(this);
224 }
225
226 // Handles the URL request response.
227 void HandleResponse(const URLRequestStatus& status,
228 int response_code,
229 const ResponseCookies& cookies,
230 const std::string& data);
231
232 // Gets the URL to contact.
233 GURL GetURL(const std::string& server_url);
234
235 // Configures the fetcher, setting up payload and headers.
236 void ConfigureRequest(URLFetcher* fetcher);
237
238 protected:
239 // Constructs a device management job running for the given proxy.
240 DeviceManagementJob(DeviceManagementBackendProxy* proxy,
241 const std::string& request_type)
242 : proxy_(proxy) {
243 query_params_.Put(kServiceParamRequest, request_type);
244 query_params_.Put(kServiceParamDeviceType, kServiceValueDeviceType);
markusheintz_ 2010/11/18 16:12:49 Pls add: query_params_.Put(kServiceParamAppType,
Mattias Nissler (ping if slow) 2010/11/19 16:03:56 Done.
245 query_params_.Put(kServiceParamAgent,
246 DeviceManagementService::GetAgentString());
247 }
248
249 void SetQueryParam(const std::string& name, const std::string& value) {
250 query_params_.Put(name, value);
251 }
252
253 void SetAuthToken(const std::string& auth_token) {
254 auth_token_ = auth_token;
255 }
256
257 void SetDeviceManagementToken(const std::string& device_management_token) {
258 device_management_token_ = device_management_token;
259 }
260
261 void SetDeviceID(const std::string& device_id) {
262 query_params_.Put(kServiceParamDeviceID, device_id);
263 }
264
265 void SetPayload(const em::DeviceManagementRequest& request) {
266 if (!request.SerializeToString(&payload_)) {
267 NOTREACHED();
268 LOG(ERROR) << "Failed to serialize request.";
269 }
270 }
271
272 private:
273 // Implemented by subclasses to handle decoded responses and errors.
274 virtual void ProcessResponse(
275 const em::DeviceManagementResponse& response) = 0;
276 virtual void ProcessError(DeviceManagementBackend::ErrorCode error) = 0;
277
278 // The proxy this job is handling a request for.
279 DeviceManagementBackendProxy* proxy_;
280
281 // Query parameters.
282 URLQueryParameters query_params_;
283
284 // Auth token (if applicaple).
285 std::string auth_token_;
286
287 // Device management token (if applicable).
288 std::string device_management_token_;
289
290 // The payload.
291 std::string payload_;
292
293 DISALLOW_COPY_AND_ASSIGN(DeviceManagementJob);
294 };
295
296 void DeviceManagementJob::HandleResponse(const URLRequestStatus& status,
297 int response_code,
298 const ResponseCookies& cookies,
299 const std::string& data) {
300 // Delete ourselves when this is done.
301 scoped_ptr<DeviceManagementJob> scoped_killer(this);
302
303 if (status.status() != URLRequestStatus::SUCCESS) {
304 ProcessError(DeviceManagementBackend::kErrorRequestFailed);
305 return;
306 }
307
308 if (response_code != 200) {
309 ProcessError(DeviceManagementBackend::kErrorHttpStatus);
310 return;
311 }
312
313 em::DeviceManagementResponse response;
314 if (!response.ParseFromString(data)) {
315 ProcessError(DeviceManagementBackend::kErrorResponseDecoding);
316 return;
317 }
318
319 // Check service error code.
320 switch (response.error()) {
321 case em::DeviceManagementResponse::SUCCESS:
322 break;
323 case em::DeviceManagementResponse::DEVICE_MANAGEMENT_NOT_SUPPORTED:
324 ProcessError(
325 DeviceManagementBackend::kErrorServiceManagementNotSupported);
326 return;
327 case em::DeviceManagementResponse::DEVICE_NOT_FOUND:
328 ProcessError(DeviceManagementBackend::kErrorServiceDeviceNotFound);
329 return;
330 case em::DeviceManagementResponse::DEVICE_MANAGEMENT_TOKEN_INVALID:
331 ProcessError(
332 DeviceManagementBackend::kErrorServiceManagementTokenInvalid);
333 return;
334 case em::DeviceManagementResponse::ACTIVATION_PENDING:
335 ProcessError(DeviceManagementBackend::kErrorServiceActivationPending);
336 return;
337 default:
338 // This should be caught by the protobuf decoder.
339 NOTREACHED();
340 ProcessError(DeviceManagementBackend::kErrorResponseDecoding);
341 return;
342 }
343
344 ProcessResponse(response);
345 }
346
347 GURL DeviceManagementJob::GetURL(const std::string& server_url) {
348 return GURL(server_url + '?' + query_params_.Encode());
349 }
350
351 void DeviceManagementJob::ConfigureRequest(URLFetcher* fetcher) {
352 fetcher->set_upload_data("application/octet-stream", payload_);
353 std::string extra_headers;
354 if (!auth_token_.empty())
355 extra_headers += kServiceTokenAuthHeader + auth_token_ + "\n";
356 if (!device_management_token_.empty())
357 extra_headers += kDMTokenAuthHeader + device_management_token_ + "\n";
358 fetcher->set_extra_request_headers(extra_headers);
359 }
360
361 // Handles device registration jobs.
362 class DeviceManagementRegisterJob : public DeviceManagementJob {
363 public:
364 DeviceManagementRegisterJob(
365 DeviceManagementBackendProxy* proxy,
366 const std::string& auth_token,
367 const std::string& device_id,
368 const em::DeviceRegisterRequest& request,
369 DeviceManagementBackend::DeviceRegisterResponseDelegate* delegate);
370 virtual ~DeviceManagementRegisterJob() {}
371
372 private:
373 // DeviceManagementJob overrides.
374 virtual void ProcessError(DeviceManagementBackend::ErrorCode error) {
markusheintz_ 2010/11/18 16:12:49 Why naming ProcessError if the delegate's method i
Mattias Nissler (ping if slow) 2010/11/19 16:03:56 Done.
375 delegate_->OnError(error);
376 }
377 virtual void ProcessResponse(const em::DeviceManagementResponse& response) {
378 delegate_->HandleRegisterResponse(response.register_response());
379 }
380
381 DeviceManagementBackend::DeviceRegisterResponseDelegate* delegate_;
382
383 DISALLOW_COPY_AND_ASSIGN(DeviceManagementRegisterJob);
384 };
385
386 DeviceManagementRegisterJob::DeviceManagementRegisterJob(
387 DeviceManagementBackendProxy* proxy,
388 const std::string& auth_token,
389 const std::string& device_id,
390 const em::DeviceRegisterRequest& request,
391 DeviceManagementBackend::DeviceRegisterResponseDelegate* delegate)
392 : DeviceManagementJob(proxy, "register"),
393 delegate_(delegate) {
394 SetDeviceID(device_id);
395 SetAuthToken(auth_token);
396 em::DeviceManagementRequest request_wrapper;
397 request_wrapper.mutable_register_request()->CopyFrom(request);
398 SetPayload(request_wrapper);
399 }
400
401 // Handles device unregistration jobs.
402 class DeviceManagementUnregisterJob : public DeviceManagementJob {
403 public:
404 DeviceManagementUnregisterJob(
405 DeviceManagementBackendProxy* proxy,
406 const std::string& device_management_token,
407 const em::DeviceUnregisterRequest& request,
408 DeviceManagementBackend::DeviceUnregisterResponseDelegate* delegate);
409 virtual ~DeviceManagementUnregisterJob() {}
410
411 private:
412 // DeviceManagementJob overrides.
413 virtual void ProcessError(DeviceManagementBackend::ErrorCode error) {
414 delegate_->OnError(error);
415 }
416 virtual void ProcessResponse(const em::DeviceManagementResponse& response) {
417 delegate_->HandleUnregisterResponse(response.unregister_response());
418 }
419
420 DeviceManagementBackend::DeviceUnregisterResponseDelegate* delegate_;
421
422 DISALLOW_COPY_AND_ASSIGN(DeviceManagementUnregisterJob);
423 };
424
425 DeviceManagementUnregisterJob::DeviceManagementUnregisterJob(
426 DeviceManagementBackendProxy* proxy,
427 const std::string& device_management_token,
428 const em::DeviceUnregisterRequest& request,
429 DeviceManagementBackend::DeviceUnregisterResponseDelegate* delegate)
430 : DeviceManagementJob(proxy, "unregister"),
431 delegate_(delegate) {
432 SetDeviceManagementToken(device_management_token);
433 em::DeviceManagementRequest request_wrapper;
434 request_wrapper.mutable_unregister_request()->CopyFrom(request);
435 SetPayload(request_wrapper);
436 }
437
438 // Handles policy request jobs.
439 class DeviceManagementPolicyJob : public DeviceManagementJob {
440 public:
441 DeviceManagementPolicyJob(
442 DeviceManagementBackendProxy* proxy,
443 const std::string& device_management_token,
444 const em::DevicePolicyRequest& request,
445 DeviceManagementBackend::DevicePolicyResponseDelegate* delegate);
446 virtual ~DeviceManagementPolicyJob() {}
447
448 private:
449 // DeviceManagementJob overrides.
450 virtual void ProcessError(DeviceManagementBackend::ErrorCode error) {
451 delegate_->OnError(error);
452 }
453 virtual void ProcessResponse(const em::DeviceManagementResponse& response) {
454 delegate_->HandlePolicyResponse(response.policy_response());
455 }
456
457 DeviceManagementBackend::DevicePolicyResponseDelegate* delegate_;
458
459 DISALLOW_COPY_AND_ASSIGN(DeviceManagementPolicyJob);
460 };
461
462 DeviceManagementPolicyJob::DeviceManagementPolicyJob(
463 DeviceManagementBackendProxy* proxy,
464 const std::string& device_management_token,
465 const em::DevicePolicyRequest& request,
466 DeviceManagementBackend::DevicePolicyResponseDelegate* delegate)
467 : DeviceManagementJob(proxy, "policy"),
468 delegate_(delegate) {
469 SetDeviceManagementToken(device_management_token);
470 em::DeviceManagementRequest request_wrapper;
471 request_wrapper.mutable_policy_request()->CopyFrom(request);
472 SetPayload(request_wrapper);
473 }
474
475 DeviceManagementBackendProxy::DeviceManagementBackendProxy(
476 DeviceManagementService* service)
477 : service_(service) {
478 }
479
480 DeviceManagementBackendProxy::~DeviceManagementBackendProxy() {
481 // Swap to a helper, so we don't interfere with the unregistration on delete.
482 JobSet to_be_deleted;
483 to_be_deleted.swap(pending_jobs_);
484 for (JobSet::iterator job(to_be_deleted.begin());
485 job != to_be_deleted.end();
486 ++job) {
487 service_->RemoveJob(*job);
488 delete *job;
489 }
490 }
491
492 void DeviceManagementBackendProxy::JobDone(DeviceManagementJob* job) {
493 pending_jobs_.erase(job);
494 }
495
496 void DeviceManagementBackendProxy::AddJob(DeviceManagementJob* job) {
497 pending_jobs_.insert(job);
498 service_->AddJob(job);
499 }
500
501 void DeviceManagementBackendProxy::ProcessRegisterRequest(
502 const std::string& auth_token,
503 const std::string& device_id,
504 const em::DeviceRegisterRequest& request,
505 DeviceRegisterResponseDelegate* delegate) {
506 AddJob(new DeviceManagementRegisterJob(this, auth_token, device_id, request,
507 delegate));
508 }
509
510 void DeviceManagementBackendProxy::ProcessUnregisterRequest(
511 const std::string& device_management_token,
512 const em::DeviceUnregisterRequest& request,
513 DeviceUnregisterResponseDelegate* delegate) {
514 AddJob(new DeviceManagementUnregisterJob(this, device_management_token,
515 request, delegate));
516 }
517
518 void DeviceManagementBackendProxy::ProcessPolicyRequest(
519 const std::string& device_management_token,
520 const em::DevicePolicyRequest& request,
521 DevicePolicyResponseDelegate* delegate) {
522 AddJob(new DeviceManagementPolicyJob(this, device_management_token, request,
523 delegate));
524 }
525
526 DeviceManagementService::~DeviceManagementService() {
527 // All running jobs should have been canceled by now. If not, there are proxy
528 // objects still around, which is an error.
529 DCHECK(pending_jobs_.empty());
530 DCHECK(queued_jobs_.empty());
531 }
532
533 DeviceManagementBackend* DeviceManagementService::CreateBackend() {
534 return new DeviceManagementBackendProxy(this);
535 }
536
537 void DeviceManagementService::Initialize(
538 URLRequestContextGetter* request_context_getter) {
539 DCHECK(!request_context_getter_);
540 request_context_getter_ = request_context_getter;
541 while (!queued_jobs_.empty()) {
542 StartJob(queued_jobs_.front());
543 queued_jobs_.pop_front();
544 }
545 }
546
547 void DeviceManagementService::Shutdown() {
548 for (JobFetcherMap::iterator job(pending_jobs_.begin());
549 job != pending_jobs_.end();
550 ++job) {
551 delete job->first;
552 queued_jobs_.push_back(job->second);
553 }
554 }
555
556 // static
557 std::string DeviceManagementService::GetAgentString() {
558 chrome::VersionInfo version_info;
559 return base::StringPrintf(kServiceValueAgent,
560 version_info.Name().c_str(),
561 version_info.Version().c_str(),
562 version_info.LastChange().c_str());
563 }
564
565 DeviceManagementService::DeviceManagementService(
566 const std::string& server_url)
567 : server_url_(server_url) {
568 }
569
570 void DeviceManagementService::AddJob(DeviceManagementJob* job) {
571 if (request_context_getter_.get())
572 StartJob(job);
573 else
574 queued_jobs_.push_back(job);
575 }
576
577 void DeviceManagementService::RemoveJob(DeviceManagementJob* job) {
578 for (JobFetcherMap::iterator entry(pending_jobs_.begin());
579 entry != pending_jobs_.end();
580 ++entry) {
581 if (entry->second == job) {
582 delete entry->first;
583 pending_jobs_.erase(entry);
584 break;
585 }
586 }
587 }
588
589 void DeviceManagementService::StartJob(DeviceManagementJob* job) {
590 URLFetcher* fetcher = URLFetcher::Create(0, job->GetURL(server_url_),
591 URLFetcher::POST, this);
592 fetcher->set_load_flags(net::LOAD_DO_NOT_SEND_COOKIES |
593 net::LOAD_DO_NOT_SAVE_COOKIES |
594 net::LOAD_DISABLE_CACHE);
595 fetcher->set_request_context(request_context_getter_.get());
596 job->ConfigureRequest(fetcher);
597 pending_jobs_[fetcher] = job;
598 fetcher->Start();
599 }
600
601 void DeviceManagementService::OnURLFetchComplete(
602 const URLFetcher* source,
603 const GURL& url,
604 const URLRequestStatus& status,
605 int response_code,
606 const ResponseCookies& cookies,
607 const std::string& data) {
608 JobFetcherMap::iterator entry(pending_jobs_.find(source));
609 if (entry != pending_jobs_.end()) {
610 DeviceManagementJob* job = entry->second;
611 job->HandleResponse(status, response_code, cookies, data);
612 pending_jobs_.erase(entry);
613 } else {
614 NOTREACHED() << "Callback from foreign URL fetcher";
615 }
616 delete source;
617 }
618
619 } // namespace policy
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698