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

Unified 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 side-by-side diff with in-line comments
Download patch
Index: chrome/browser/policy/device_management_service.cc
diff --git a/chrome/browser/policy/device_management_service.cc b/chrome/browser/policy/device_management_service.cc
new file mode 100644
index 0000000000000000000000000000000000000000..632c68c657ec72f11331fdb5ee69228a85708bf9
--- /dev/null
+++ b/chrome/browser/policy/device_management_service.cc
@@ -0,0 +1,619 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/policy/device_management_service.h"
+
+#include <utility>
+#include <set>
+#include <vector>
+
+#include "base/command_line.h"
+#include "base/lazy_instance.h"
+#include "base/stl_util-inl.h"
+#include "base/stringprintf.h"
+#include "chrome/browser/browser_thread.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/net/url_request_context_getter.h"
+#include "net/base/cookie_monster.h"
+#include "net/base/escape.h"
+#include "net/base/host_resolver.h"
+#include "net/base/load_flags.h"
+#include "net/base/ssl_config_service_defaults.h"
+#include "net/http/http_auth_handler_factory.h"
+#include "net/http/http_network_layer.h"
+#include "net/proxy/proxy_service.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_status.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/io_thread.h"
+#include "chrome/browser/net/chrome_net_log.h"
+#include "chrome/browser/profile.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/chrome_version_info.h"
+
+namespace policy {
+
+namespace {
+
+// Name constants for URL query parameters.
+const char kServiceParamRequest[] = "request";
+const char kServiceParamDeviceType[] = "devicetype";
+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
+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.
+
+// String constants for the device type and agent we report to the service.
+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.
+const char kServiceValueAgent[] =
+ "%s enterprise management client version %s (%s)";
+
+const char kServiceTokenAuthHeader[] = "Authorization: GoogleLogin auth=";
+const char kDMTokenAuthHeader[] = "Authorization: GoogleDMToken token=";
+
+// Helper class for URL query parameter encoding/decoding.
+class URLQueryParameters {
+ public:
+ URLQueryParameters() {}
+
+ // Add a query parameter.
+ void Put(const std::string& name, const std::string& value);
+
+ // Produce the query string, taking care of properly encoding and assembling
+ // the names and values.
+ std::string Encode();
+
+ private:
+ typedef std::vector<std::pair<std::string, std::string> > ParameterMap;
+ ParameterMap params_;
+
+ DISALLOW_COPY_AND_ASSIGN(URLQueryParameters);
+};
+
+void URLQueryParameters::Put(const std::string& name,
+ const std::string& value) {
+ params_.push_back(std::make_pair(name, value));
+}
+
+std::string URLQueryParameters::Encode() {
+ std::string result;
+ for (ParameterMap::const_iterator entry(params_.begin());
+ entry != params_.end();
+ ++entry) {
+ if (entry != params_.begin())
+ result += '&';
+ result += EscapeUrlEncodedData(entry->first);
+ result += '=';
+ result += EscapeUrlEncodedData(entry->second);
+ }
+ return result;
+}
+
+// Custom request context implementation that allows to override the user agent,
+// amongst others. Wraps a baseline request context from which we reuse the
+// networking components.
+class DeviceManagementBackendRequestContext : public URLRequestContext {
+ public:
+ explicit DeviceManagementBackendRequestContext(
+ URLRequestContext* base_context);
+ virtual ~DeviceManagementBackendRequestContext();
+
+ private:
+ virtual const std::string& GetUserAgent(const GURL& url) const;
+
+ std::string user_agent_;
+};
+
+DeviceManagementBackendRequestContext::DeviceManagementBackendRequestContext(
+ URLRequestContext* base_context) {
+ // Share resolver, proxy service and ssl bits with the baseline context. This
+ // is important so we don't make redundant requests (e.g. when resolving proxy
+ // auto configuration).
+ net_log_ = base_context->net_log();
+ host_resolver_ = base_context->host_resolver();
+ proxy_service_ = base_context->proxy_service();
+ ssl_config_service_ = base_context->ssl_config_service();
+
+ // Share the http session.
+ http_transaction_factory_ = net::HttpNetworkLayer::CreateFactory(
+ base_context->http_transaction_factory()->GetSession());
+
+ // No cookies, please.
+ cookie_store_ = new net::CookieMonster(NULL, NULL);
+
+ // Initialize these to sane values for our purposes.
+ user_agent_ = DeviceManagementService::GetAgentString();
+ accept_language_ = "*";
+ accept_charset_ = "*";
+}
+
+DeviceManagementBackendRequestContext
+ ::~DeviceManagementBackendRequestContext() {
+ delete http_transaction_factory_;
+ delete http_auth_handler_factory_;
+}
+
+const std::string&
+DeviceManagementBackendRequestContext::GetUserAgent(const GURL& url) const {
+ return user_agent_;
+}
+
+// Request context holder.
+class DeviceManagementBackendRequestContextGetter
+ : public URLRequestContextGetter {
+ public:
+ DeviceManagementBackendRequestContextGetter(
+ URLRequestContextGetter* base_context_getter)
+ : base_context_getter_(base_context_getter) {}
+
+ // URLRequestContextGetter overrides.
+ virtual URLRequestContext* GetURLRequestContext();
+ virtual scoped_refptr<base::MessageLoopProxy> GetIOMessageLoopProxy() const;
+
+ private:
+ scoped_refptr<URLRequestContext> context_;
+ scoped_refptr<URLRequestContextGetter> base_context_getter_;
+};
+
+
+URLRequestContext*
+DeviceManagementBackendRequestContextGetter::GetURLRequestContext() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ if (!context_) {
+ context_ = new DeviceManagementBackendRequestContext(
+ base_context_getter_->GetURLRequestContext());
+ }
+
+ return context_.get();
+}
+
+scoped_refptr<base::MessageLoopProxy>
+DeviceManagementBackendRequestContextGetter::GetIOMessageLoopProxy() const {
+ return BrowserThread::GetMessageLoopProxyForThread(BrowserThread::IO);
+}
+
+} // namespace
+
+// 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.
+// DeviceManagementService so the consumer can cancel all pending requests by
+// destroying the backend object.
+class DeviceManagementBackendProxy : public DeviceManagementBackend {
+ public:
+ explicit DeviceManagementBackendProxy(DeviceManagementService* service);
+ virtual ~DeviceManagementBackendProxy();
+
+ // Called by the DeviceManagementJob dtor so we can clean up.
+ void JobDone(DeviceManagementJob* job);
+
+ private:
+ typedef std::set<DeviceManagementJob*> JobSet;
+
+ // Add a job to the pending job set and register it with the service (if
+ // available).
+ void AddJob(DeviceManagementJob* job);
+
+ // DeviceManagementBackend overrides.
+ virtual void ProcessRegisterRequest(
+ const std::string& auth_token,
+ const std::string& device_id,
+ const em::DeviceRegisterRequest& request,
+ DeviceRegisterResponseDelegate* response_delegate);
+ virtual void ProcessUnregisterRequest(
+ const std::string& device_management_token,
+ const em::DeviceUnregisterRequest& request,
+ DeviceUnregisterResponseDelegate* response_delegate);
+ virtual void ProcessPolicyRequest(
+ const std::string& device_management_token,
+ const em::DevicePolicyRequest& request,
+ DevicePolicyResponseDelegate* response_delegate);
+
+ // Keeps track of the jobs currently in flight.
+ JobSet pending_jobs_;
+
+ DeviceManagementService* service_;
+
+ DISALLOW_COPY_AND_ASSIGN(DeviceManagementBackendProxy);
+};
+
+// 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
+// subclasses provide custom code for actual register, unregister, and policy
+// jobs.
+class DeviceManagementJob {
+ public:
+ virtual ~DeviceManagementJob() {
+ proxy_->JobDone(this);
+ }
+
+ // Handles the URL request response.
+ void HandleResponse(const URLRequestStatus& status,
+ int response_code,
+ const ResponseCookies& cookies,
+ const std::string& data);
+
+ // Gets the URL to contact.
+ GURL GetURL(const std::string& server_url);
+
+ // Configures the fetcher, setting up payload and headers.
+ void ConfigureRequest(URLFetcher* fetcher);
+
+ protected:
+ // Constructs a device management job running for the given proxy.
+ DeviceManagementJob(DeviceManagementBackendProxy* proxy,
+ const std::string& request_type)
+ : proxy_(proxy) {
+ query_params_.Put(kServiceParamRequest, request_type);
+ 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.
+ query_params_.Put(kServiceParamAgent,
+ DeviceManagementService::GetAgentString());
+ }
+
+ void SetQueryParam(const std::string& name, const std::string& value) {
+ query_params_.Put(name, value);
+ }
+
+ void SetAuthToken(const std::string& auth_token) {
+ auth_token_ = auth_token;
+ }
+
+ void SetDeviceManagementToken(const std::string& device_management_token) {
+ device_management_token_ = device_management_token;
+ }
+
+ void SetDeviceID(const std::string& device_id) {
+ query_params_.Put(kServiceParamDeviceID, device_id);
+ }
+
+ void SetPayload(const em::DeviceManagementRequest& request) {
+ if (!request.SerializeToString(&payload_)) {
+ NOTREACHED();
+ LOG(ERROR) << "Failed to serialize request.";
+ }
+ }
+
+ private:
+ // Implemented by subclasses to handle decoded responses and errors.
+ virtual void ProcessResponse(
+ const em::DeviceManagementResponse& response) = 0;
+ virtual void ProcessError(DeviceManagementBackend::ErrorCode error) = 0;
+
+ // The proxy this job is handling a request for.
+ DeviceManagementBackendProxy* proxy_;
+
+ // Query parameters.
+ URLQueryParameters query_params_;
+
+ // Auth token (if applicaple).
+ std::string auth_token_;
+
+ // Device management token (if applicable).
+ std::string device_management_token_;
+
+ // The payload.
+ std::string payload_;
+
+ DISALLOW_COPY_AND_ASSIGN(DeviceManagementJob);
+};
+
+void DeviceManagementJob::HandleResponse(const URLRequestStatus& status,
+ int response_code,
+ const ResponseCookies& cookies,
+ const std::string& data) {
+ // Delete ourselves when this is done.
+ scoped_ptr<DeviceManagementJob> scoped_killer(this);
+
+ if (status.status() != URLRequestStatus::SUCCESS) {
+ ProcessError(DeviceManagementBackend::kErrorRequestFailed);
+ return;
+ }
+
+ if (response_code != 200) {
+ ProcessError(DeviceManagementBackend::kErrorHttpStatus);
+ return;
+ }
+
+ em::DeviceManagementResponse response;
+ if (!response.ParseFromString(data)) {
+ ProcessError(DeviceManagementBackend::kErrorResponseDecoding);
+ return;
+ }
+
+ // Check service error code.
+ switch (response.error()) {
+ case em::DeviceManagementResponse::SUCCESS:
+ break;
+ case em::DeviceManagementResponse::DEVICE_MANAGEMENT_NOT_SUPPORTED:
+ ProcessError(
+ DeviceManagementBackend::kErrorServiceManagementNotSupported);
+ return;
+ case em::DeviceManagementResponse::DEVICE_NOT_FOUND:
+ ProcessError(DeviceManagementBackend::kErrorServiceDeviceNotFound);
+ return;
+ case em::DeviceManagementResponse::DEVICE_MANAGEMENT_TOKEN_INVALID:
+ ProcessError(
+ DeviceManagementBackend::kErrorServiceManagementTokenInvalid);
+ return;
+ case em::DeviceManagementResponse::ACTIVATION_PENDING:
+ ProcessError(DeviceManagementBackend::kErrorServiceActivationPending);
+ return;
+ default:
+ // This should be caught by the protobuf decoder.
+ NOTREACHED();
+ ProcessError(DeviceManagementBackend::kErrorResponseDecoding);
+ return;
+ }
+
+ ProcessResponse(response);
+}
+
+GURL DeviceManagementJob::GetURL(const std::string& server_url) {
+ return GURL(server_url + '?' + query_params_.Encode());
+}
+
+void DeviceManagementJob::ConfigureRequest(URLFetcher* fetcher) {
+ fetcher->set_upload_data("application/octet-stream", payload_);
+ std::string extra_headers;
+ if (!auth_token_.empty())
+ extra_headers += kServiceTokenAuthHeader + auth_token_ + "\n";
+ if (!device_management_token_.empty())
+ extra_headers += kDMTokenAuthHeader + device_management_token_ + "\n";
+ fetcher->set_extra_request_headers(extra_headers);
+}
+
+// Handles device registration jobs.
+class DeviceManagementRegisterJob : public DeviceManagementJob {
+ public:
+ DeviceManagementRegisterJob(
+ DeviceManagementBackendProxy* proxy,
+ const std::string& auth_token,
+ const std::string& device_id,
+ const em::DeviceRegisterRequest& request,
+ DeviceManagementBackend::DeviceRegisterResponseDelegate* delegate);
+ virtual ~DeviceManagementRegisterJob() {}
+
+ private:
+ // DeviceManagementJob overrides.
+ 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.
+ delegate_->OnError(error);
+ }
+ virtual void ProcessResponse(const em::DeviceManagementResponse& response) {
+ delegate_->HandleRegisterResponse(response.register_response());
+ }
+
+ DeviceManagementBackend::DeviceRegisterResponseDelegate* delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(DeviceManagementRegisterJob);
+};
+
+DeviceManagementRegisterJob::DeviceManagementRegisterJob(
+ DeviceManagementBackendProxy* proxy,
+ const std::string& auth_token,
+ const std::string& device_id,
+ const em::DeviceRegisterRequest& request,
+ DeviceManagementBackend::DeviceRegisterResponseDelegate* delegate)
+ : DeviceManagementJob(proxy, "register"),
+ delegate_(delegate) {
+ SetDeviceID(device_id);
+ SetAuthToken(auth_token);
+ em::DeviceManagementRequest request_wrapper;
+ request_wrapper.mutable_register_request()->CopyFrom(request);
+ SetPayload(request_wrapper);
+}
+
+// Handles device unregistration jobs.
+class DeviceManagementUnregisterJob : public DeviceManagementJob {
+ public:
+ DeviceManagementUnregisterJob(
+ DeviceManagementBackendProxy* proxy,
+ const std::string& device_management_token,
+ const em::DeviceUnregisterRequest& request,
+ DeviceManagementBackend::DeviceUnregisterResponseDelegate* delegate);
+ virtual ~DeviceManagementUnregisterJob() {}
+
+ private:
+ // DeviceManagementJob overrides.
+ virtual void ProcessError(DeviceManagementBackend::ErrorCode error) {
+ delegate_->OnError(error);
+ }
+ virtual void ProcessResponse(const em::DeviceManagementResponse& response) {
+ delegate_->HandleUnregisterResponse(response.unregister_response());
+ }
+
+ DeviceManagementBackend::DeviceUnregisterResponseDelegate* delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(DeviceManagementUnregisterJob);
+};
+
+DeviceManagementUnregisterJob::DeviceManagementUnregisterJob(
+ DeviceManagementBackendProxy* proxy,
+ const std::string& device_management_token,
+ const em::DeviceUnregisterRequest& request,
+ DeviceManagementBackend::DeviceUnregisterResponseDelegate* delegate)
+ : DeviceManagementJob(proxy, "unregister"),
+ delegate_(delegate) {
+ SetDeviceManagementToken(device_management_token);
+ em::DeviceManagementRequest request_wrapper;
+ request_wrapper.mutable_unregister_request()->CopyFrom(request);
+ SetPayload(request_wrapper);
+}
+
+// Handles policy request jobs.
+class DeviceManagementPolicyJob : public DeviceManagementJob {
+ public:
+ DeviceManagementPolicyJob(
+ DeviceManagementBackendProxy* proxy,
+ const std::string& device_management_token,
+ const em::DevicePolicyRequest& request,
+ DeviceManagementBackend::DevicePolicyResponseDelegate* delegate);
+ virtual ~DeviceManagementPolicyJob() {}
+
+ private:
+ // DeviceManagementJob overrides.
+ virtual void ProcessError(DeviceManagementBackend::ErrorCode error) {
+ delegate_->OnError(error);
+ }
+ virtual void ProcessResponse(const em::DeviceManagementResponse& response) {
+ delegate_->HandlePolicyResponse(response.policy_response());
+ }
+
+ DeviceManagementBackend::DevicePolicyResponseDelegate* delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(DeviceManagementPolicyJob);
+};
+
+DeviceManagementPolicyJob::DeviceManagementPolicyJob(
+ DeviceManagementBackendProxy* proxy,
+ const std::string& device_management_token,
+ const em::DevicePolicyRequest& request,
+ DeviceManagementBackend::DevicePolicyResponseDelegate* delegate)
+ : DeviceManagementJob(proxy, "policy"),
+ delegate_(delegate) {
+ SetDeviceManagementToken(device_management_token);
+ em::DeviceManagementRequest request_wrapper;
+ request_wrapper.mutable_policy_request()->CopyFrom(request);
+ SetPayload(request_wrapper);
+}
+
+DeviceManagementBackendProxy::DeviceManagementBackendProxy(
+ DeviceManagementService* service)
+ : service_(service) {
+}
+
+DeviceManagementBackendProxy::~DeviceManagementBackendProxy() {
+ // Swap to a helper, so we don't interfere with the unregistration on delete.
+ JobSet to_be_deleted;
+ to_be_deleted.swap(pending_jobs_);
+ for (JobSet::iterator job(to_be_deleted.begin());
+ job != to_be_deleted.end();
+ ++job) {
+ service_->RemoveJob(*job);
+ delete *job;
+ }
+}
+
+void DeviceManagementBackendProxy::JobDone(DeviceManagementJob* job) {
+ pending_jobs_.erase(job);
+}
+
+void DeviceManagementBackendProxy::AddJob(DeviceManagementJob* job) {
+ pending_jobs_.insert(job);
+ service_->AddJob(job);
+}
+
+void DeviceManagementBackendProxy::ProcessRegisterRequest(
+ const std::string& auth_token,
+ const std::string& device_id,
+ const em::DeviceRegisterRequest& request,
+ DeviceRegisterResponseDelegate* delegate) {
+ AddJob(new DeviceManagementRegisterJob(this, auth_token, device_id, request,
+ delegate));
+}
+
+void DeviceManagementBackendProxy::ProcessUnregisterRequest(
+ const std::string& device_management_token,
+ const em::DeviceUnregisterRequest& request,
+ DeviceUnregisterResponseDelegate* delegate) {
+ AddJob(new DeviceManagementUnregisterJob(this, device_management_token,
+ request, delegate));
+}
+
+void DeviceManagementBackendProxy::ProcessPolicyRequest(
+ const std::string& device_management_token,
+ const em::DevicePolicyRequest& request,
+ DevicePolicyResponseDelegate* delegate) {
+ AddJob(new DeviceManagementPolicyJob(this, device_management_token, request,
+ delegate));
+}
+
+DeviceManagementService::~DeviceManagementService() {
+ // All running jobs should have been canceled by now. If not, there are proxy
+ // objects still around, which is an error.
+ DCHECK(pending_jobs_.empty());
+ DCHECK(queued_jobs_.empty());
+}
+
+DeviceManagementBackend* DeviceManagementService::CreateBackend() {
+ return new DeviceManagementBackendProxy(this);
+}
+
+void DeviceManagementService::Initialize(
+ URLRequestContextGetter* request_context_getter) {
+ DCHECK(!request_context_getter_);
+ request_context_getter_ = request_context_getter;
+ while (!queued_jobs_.empty()) {
+ StartJob(queued_jobs_.front());
+ queued_jobs_.pop_front();
+ }
+}
+
+void DeviceManagementService::Shutdown() {
+ for (JobFetcherMap::iterator job(pending_jobs_.begin());
+ job != pending_jobs_.end();
+ ++job) {
+ delete job->first;
+ queued_jobs_.push_back(job->second);
+ }
+}
+
+// static
+std::string DeviceManagementService::GetAgentString() {
+ chrome::VersionInfo version_info;
+ return base::StringPrintf(kServiceValueAgent,
+ version_info.Name().c_str(),
+ version_info.Version().c_str(),
+ version_info.LastChange().c_str());
+}
+
+DeviceManagementService::DeviceManagementService(
+ const std::string& server_url)
+ : server_url_(server_url) {
+}
+
+void DeviceManagementService::AddJob(DeviceManagementJob* job) {
+ if (request_context_getter_.get())
+ StartJob(job);
+ else
+ queued_jobs_.push_back(job);
+}
+
+void DeviceManagementService::RemoveJob(DeviceManagementJob* job) {
+ for (JobFetcherMap::iterator entry(pending_jobs_.begin());
+ entry != pending_jobs_.end();
+ ++entry) {
+ if (entry->second == job) {
+ delete entry->first;
+ pending_jobs_.erase(entry);
+ break;
+ }
+ }
+}
+
+void DeviceManagementService::StartJob(DeviceManagementJob* job) {
+ URLFetcher* fetcher = URLFetcher::Create(0, job->GetURL(server_url_),
+ URLFetcher::POST, this);
+ fetcher->set_load_flags(net::LOAD_DO_NOT_SEND_COOKIES |
+ net::LOAD_DO_NOT_SAVE_COOKIES |
+ net::LOAD_DISABLE_CACHE);
+ fetcher->set_request_context(request_context_getter_.get());
+ job->ConfigureRequest(fetcher);
+ pending_jobs_[fetcher] = job;
+ fetcher->Start();
+}
+
+void DeviceManagementService::OnURLFetchComplete(
+ const URLFetcher* source,
+ const GURL& url,
+ const URLRequestStatus& status,
+ int response_code,
+ const ResponseCookies& cookies,
+ const std::string& data) {
+ JobFetcherMap::iterator entry(pending_jobs_.find(source));
+ if (entry != pending_jobs_.end()) {
+ DeviceManagementJob* job = entry->second;
+ job->HandleResponse(status, response_code, cookies, data);
+ pending_jobs_.erase(entry);
+ } else {
+ NOTREACHED() << "Callback from foreign URL fetcher";
+ }
+ delete source;
+}
+
+} // namespace policy

Powered by Google App Engine
This is Rietveld 408576698