Changed the update protocol for component updater from v2 to v3.
BUG=314521

Review URL: https://codereview.chromium.org/74893002

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@236727 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/chrome/browser/component_updater/component_updater_configurator.cc b/chrome/browser/component_updater/component_updater_configurator.cc
index 8f870a8..c325810 100644
--- a/chrome/browser/component_updater/component_updater_configurator.cc
+++ b/chrome/browser/component_updater/component_updater_configurator.cc
@@ -40,13 +40,10 @@
 // Sets the URL for updates.
 const char kSwitchUrlSource[] = "url-source";
 
-// The default url from which an update manifest can be fetched. Can be
+// The default url for the v3 protocol service endpoint. Can be
 // overridden with --component-updater=url-source=someurl.
 const char kDefaultUrlSource[] =
-    "http://clients2.google.com/service/update2/crx";
-
-// The url to send the pings to.
-const char kPingUrl[] = "http://tools.google.com/service/update2";
+    "http://clients2.google.com/service/update2";
 
 #if defined(OS_WIN)
 // Disables differential updates.
@@ -179,7 +176,7 @@
 }
 
 GURL ChromeConfigurator::PingUrl() {
-  return pings_enabled_ ? GURL(kPingUrl) : GURL();
+  return pings_enabled_ ? UpdateUrl() : GURL();
 }
 
 const char* ChromeConfigurator::ExtraRequestParams() {
diff --git a/chrome/browser/component_updater/component_updater_ping_manager.cc b/chrome/browser/component_updater/component_updater_ping_manager.cc
index d11f4f34..a5c5a14 100644
--- a/chrome/browser/component_updater/component_updater_ping_manager.cc
+++ b/chrome/browser/component_updater/component_updater_ping_manager.cc
@@ -5,18 +5,13 @@
 #include "chrome/browser/component_updater/component_updater_ping_manager.h"
 #include "base/guid.h"
 #include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
 #include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
-#include "base/sys_info.h"
-#include "base/win/windows_version.h"
+#include "chrome/browser/component_updater/component_updater_utils.h"
 #include "chrome/browser/component_updater/crx_update_item.h"
-#include "chrome/common/chrome_version_info.h"
-#include "chrome/common/omaha_query_params/omaha_query_params.h"
-#include "net/base/load_flags.h"
-#include "net/base/net_errors.h"
 #include "net/url_request/url_fetcher.h"
 #include "net/url_request/url_fetcher_delegate.h"
-#include "net/url_request/url_request_status.h"
 
 namespace {
 
@@ -69,45 +64,26 @@
   if (!ping_url.is_valid())
     return;
 
-  url_fetcher_.reset(net::URLFetcher::Create(0,
-                                             ping_url,
-                                             net::URLFetcher::POST,
-                                             this));
-
-  url_fetcher_->SetUploadData("application/xml", BuildPing(item));
-  url_fetcher_->SetRequestContext(url_request_context_getter);
-  url_fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES |
-                             net::LOAD_DO_NOT_SAVE_COOKIES |
-                             net::LOAD_DISABLE_CACHE);
-  url_fetcher_->SetAutomaticallyRetryOn5xx(false);
-  url_fetcher_->Start();
+  url_fetcher_.reset(SendProtocolRequest(ping_url,
+                                         BuildPing(item),
+                                         this,
+                                         url_request_context_getter));
 }
 
 // Builds a ping message for the specified update item.
 std::string PingSender::BuildPing(const CrxUpdateItem* item) {
-  const std::string prod_id(chrome::OmahaQueryParams::GetProdIdString(
-      chrome::OmahaQueryParams::CHROME));
-
-  const char request_format[] =
-      "<o:gupdate xmlns:o=\"http://www.google.com/update2/request\" "
-      "protocol=\"2.0\" version=\"%s-%s\" requestid=\"{%s}\" "
-      "updaterchannel=\"%s\"> "
-      "<o:os platform=\"%s\" version=\"%s\"/> "
-      "<o:app appid=\"%s\" version=\"%s\">"
+  const char app_element_format[] =
+      "<app appid=\"%s\" version=\"%s\" nextversion=\"%s\">"
       "%s"
-      "</o:app></o:gupdate>";
-  const std::string request(
-      base::StringPrintf(request_format,
-                         prod_id.c_str(),
-                         chrome::VersionInfo().Version().c_str(),
-                         base::GenerateGUID().c_str(),
-                         chrome::OmahaQueryParams::GetChannelString(),
-                         chrome::VersionInfo().OSType().c_str(),
-                         base::SysInfo().OperatingSystemVersion().c_str(),
-                         item->id.c_str(),
-                         item->component.version.GetString().c_str(),
-                         BuildPingEventElement(item).c_str()));
-  return request;
+      "</app>";
+  const std::string app_element(base::StringPrintf(
+      app_element_format,
+      item->id.c_str(),                               // "appid"
+      item->previous_version.GetString().c_str(),     // "version"
+      item->next_version.GetString().c_str(),         // "nextversion"
+      BuildPingEventElement(item).c_str()));
+
+  return BuildProtocolRequest(app_element);
 }
 
 // Returns a string representing one ping event xml element for an update item.
@@ -117,13 +93,9 @@
 
   using base::StringAppendF;
 
-  std::string ping_event("<o:event eventtype=\"3\"");
+  std::string ping_event("<event eventtype=\"3\"");
   const int event_result = item->status == CrxUpdateItem::kUpdated;
   StringAppendF(&ping_event, " eventresult=\"%d\"", event_result);
-  StringAppendF(&ping_event, " previousversion=\"%s\"",
-                item->previous_version.GetString().c_str());
-  StringAppendF(&ping_event, " nextversion=\"%s\"",
-                item->next_version.GetString().c_str());
   if (item->error_category)
     StringAppendF(&ping_event, " errorcat=\"%d\"", item->error_category);
   if (item->error_code)
diff --git a/chrome/browser/component_updater/component_updater_service.cc b/chrome/browser/component_updater/component_updater_service.cc
index 472525b..beee14a 100644
--- a/chrome/browser/component_updater/component_updater_service.cc
+++ b/chrome/browser/component_updater/component_updater_service.cc
@@ -27,9 +27,10 @@
 #include "chrome/browser/component_updater/component_patcher.h"
 #include "chrome/browser/component_updater/component_unpacker.h"
 #include "chrome/browser/component_updater/component_updater_ping_manager.h"
+#include "chrome/browser/component_updater/component_updater_utils.h"
 #include "chrome/browser/component_updater/crx_update_item.h"
+#include "chrome/browser/component_updater/update_manifest.h"
 #include "chrome/common/chrome_version_info.h"
-#include "chrome/common/extensions/update_manifest.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/resource_controller.h"
 #include "content/public/browser/resource_throttle.h"
@@ -44,57 +45,18 @@
 #include "url/gurl.h"
 
 using content::BrowserThread;
-using extensions::Extension;
 
 // The component updater is designed to live until process shutdown, so
 // base::Bind() calls are not refcounted.
 
 namespace {
 
-// Extends an omaha compatible update check url |query| string. Does
-// not mutate the string if it would be longer than |limit| chars.
-bool AddQueryString(const std::string& id,
-                    const std::string& version,
-                    const std::string& fingerprint,
-                    bool ondemand,
-                    size_t limit,
-                    std::string* query) {
-  std::string additional =
-      base::StringPrintf("id=%s&v=%s&fp=%s&uc%s",
-                         id.c_str(),
-                         version.c_str(),
-                         fingerprint.c_str(),
-                         ondemand ? "&installsource=ondemand" : "");
-  additional = "x=" + net::EscapeQueryParamValue(additional, true);
-  if ((additional.size() + query->size() + 1) > limit)
-    return false;
-  if (!query->empty())
-    query->append(1, '&');
-  query->append(additional);
-  return true;
-}
-
-// Create the final omaha compatible query. The |extra| is optional and can
-// be null. It should contain top level (non-escaped) parameters.
-std::string MakeFinalQuery(const std::string& host,
-                           const std::string& query,
-                           const char* extra) {
-  std::string request(host);
-  request.append(1, '?');
-  if (extra) {
-    request.append(extra);
-    request.append(1, '&');
-  }
-  request.append(query);
-  return request;
-}
-
 // Produces an extension-like friendly |id|. This might be removed in the
 // future if we roll our on packing tools.
 static std::string HexStringToID(const std::string& hexstr) {
   std::string id;
   for (size_t i = 0; i < hexstr.size(); ++i) {
-    int val;
+    int val(0);
     if (base::HexStringToInt(base::StringPiece(hexstr.begin() + i,
                                                hexstr.begin() + i + 1),
                              &val)) {
@@ -103,16 +65,14 @@
       id.append(1, 'a');
     }
   }
-  DCHECK(Extension::IdIsValid(id));
+  DCHECK(extensions::Extension::IdIsValid(id));
   return id;
 }
 
-// Helper to do version check for components.
+// Returns true if the |proposed| version is newer than |current| version.
 bool IsVersionNewer(const Version& current, const std::string& proposed) {
   Version proposed_ver(proposed);
-  if (!proposed_ver.IsValid())
-    return false;
-  return (current.CompareTo(proposed_ver) < 0);
+  return proposed_ver.IsValid() && current.CompareTo(proposed_ver) < 0;
 }
 
 // Helper template class that allows our main class to have separate
@@ -250,6 +210,7 @@
  public:
   explicit CUResourceThrottle(const net::URLRequest* request);
   virtual ~CUResourceThrottle();
+
   // Overriden from ResourceThrottle.
   virtual void WillStartRequest(bool* defer) OVERRIDE;
   virtual void WillRedirectRequest(const GURL& new_url, bool* defer) OVERRIDE;
@@ -260,13 +221,13 @@
   typedef std::vector<base::WeakPtr<CUResourceThrottle> > WeakPtrVector;
 
  private:
-   enum State {
-     NEW,
-     BLOCKED,
-     UNBLOCKED
-   };
+  enum State {
+    NEW,
+    BLOCKED,
+    UNBLOCKED
+  };
 
-   State state_;
+  State state_;
 };
 
 void UnblockResourceThrottle(base::WeakPtr<CUResourceThrottle> rt) {
@@ -299,7 +260,6 @@
 class CrxUpdateService : public ComponentUpdateService {
  public:
   explicit CrxUpdateService(ComponentUpdateService::Configurator* config);
-
   virtual ~CrxUpdateService();
 
   // Overrides for ComponentUpdateService.
@@ -347,13 +307,10 @@
     kStepDelayLong,
   };
 
-
-  void OnParseUpdateManifestSucceeded(const UpdateManifest::Results& results);
-
+  void OnParseUpdateManifestSucceeded(
+      const component_updater::UpdateManifest::Results& results);
   void OnParseUpdateManifestFailed(const std::string& error_message);
 
-  bool AddItemToUpdateCheck(CrxUpdateItem* item, std::string* query);
-
   Status OnDemandUpdateInternal(CrxUpdateItem* item);
 
   void ProcessPendingItems();
@@ -362,9 +319,12 @@
 
   void UpdateComponent(CrxUpdateItem* workitem);
 
-  void AddUpdateCheckItems(std::string* query);
+  void AddItemToUpdateCheck(CrxUpdateItem* item,
+                            std::string* update_check_items);
 
-  void DoUpdateCheck(const std::string& query);
+  void AddUpdateCheckItems(std::string* update_check_items);
+
+  void DoUpdateCheck(const std::string& update_check_items);
 
   void ScheduleNextRun(StepDelayInterval step_delay);
 
@@ -636,36 +596,6 @@
   return kOk;
 }
 
-// Sets a component to be checked for updates.
-// The component to add is |item| and the |query| string is modified with the
-// required omaha compatible query. Returns false when the query string is
-// longer than specified by UrlSizeLimit().
-bool CrxUpdateService::AddItemToUpdateCheck(CrxUpdateItem* item,
-                                            std::string* query) {
-  if (!AddQueryString(item->id,
-                      item->component.version.GetString(),
-                      item->component.fingerprint,
-                      item->on_demand,
-                      config_->UrlSizeLimit(),
-                      query))
-    return false;
-
-  ChangeItemState(item, CrxUpdateItem::kChecking);
-  item->last_check = base::Time::Now();
-  item->previous_version = item->component.version;
-  item->next_version = Version();
-  item->previous_fp = item->component.fingerprint;
-  item->next_fp.clear();
-  item->diff_update_failed = false;
-  item->error_category = 0;
-  item->error_code = 0;
-  item->extra_code1 = 0;
-  item->diff_error_category = 0;
-  item->diff_error_code = 0;
-  item->diff_extra_code1 = 0;
-  return true;
-}
-
 // Start the process of checking for an update, for a particular component
 // that was previously registered.
 // |component_id| is a value returned from GetCrxComponentID().
@@ -678,6 +608,7 @@
     CrxUpdateItem* uit) {
   if (!uit)
     return kError;
+
   // Check if the request is too soon.
   base::TimeDelta delta = base::Time::Now() - uit->last_check;
   if (delta < base::TimeDelta::FromSeconds(config_->OnDemandDelay()))
@@ -739,10 +670,10 @@
     UpdateComponent(ready_upgrade);
     return;
   }
-  std::string query;
-  AddUpdateCheckItems(&query);
-  if (!query.empty()) {
-    DoUpdateCheck(query);
+  std::string update_check_items;
+  AddUpdateCheckItems(&update_check_items);
+  if (!update_check_items.empty()) {
+    DoUpdateCheck(update_check_items);
     return;
   }
   // No components to update. The next check will be after a long sleep.
@@ -793,16 +724,75 @@
              blocking_task_runner_);
 }
 
-// Given that our |work_items_| list is expected to contain relatively few
-// items, we simply loop several times.
-void CrxUpdateService::AddUpdateCheckItems(std::string* query){
+// Sets the state of the component to be checked for updates. After the
+// function is called, the <app> element corresponding to the |item| parameter
+// is appended to the |update_check_items|.
+void CrxUpdateService::AddItemToUpdateCheck(CrxUpdateItem* item,
+                                            std::string* update_check_items) {
+  // The app element corresponding to an update items looks like this:
+  //    <app appid="hnimpnehoodheedghdeeijklkeaacbdc"
+  //         version="0.1.2.3" installsource="ondemand">
+  //    <updatecheck />
+  //    <packages>
+  //        <package fp="abcd" />
+  //    </packages>
+  //    </app>
+  std::string app_attributes;
+  base::StringAppendF(&app_attributes,
+                      "appid=\"%s\" version=\"%s\"",
+                      item->id.c_str(),
+                      item->component.version.GetString().c_str());
+  if (item->on_demand)
+    base::StringAppendF(&app_attributes, " installsource=\"ondemand\"");
+
+  std::string app;
+  if (item->component.fingerprint.empty())
+    base::StringAppendF(&app,
+                        "<app %s>"
+                        "<updatecheck />"
+                        "</app>",
+                        app_attributes.c_str());
+  else
+    base::StringAppendF(&app,
+                        "<app %s>"
+                        "<updatecheck />"
+                        "<packages>"
+                        "<package fp=\"%s\"/>"
+                        "</packages>"
+                        "</app>",
+                        app_attributes.c_str(),
+                        item->component.fingerprint.c_str());
+
+  update_check_items->append(app);
+
+  ChangeItemState(item, CrxUpdateItem::kChecking);
+  item->last_check = base::Time::Now();
+  item->crx_url = GURL();
+  item->diff_crx_url = GURL();
+  item->previous_version = item->component.version;
+  item->next_version = Version();
+  item->previous_fp = item->component.fingerprint;
+  item->next_fp.clear();
+  item->diff_update_failed = false;
+  item->error_category = 0;
+  item->error_code = 0;
+  item->extra_code1 = 0;
+  item->diff_error_category = 0;
+  item->diff_error_code = 0;
+  item->diff_extra_code1 = 0;
+}
+
+// Builds the sequence of <app> elements in the update check and returns it
+// in the |update_check_items| parameter.
+void CrxUpdateService::AddUpdateCheckItems(std::string* update_check_items) {
+  // Given that our |work_items_| list is expected to contain relatively few
+  // items, we simply loop several times.
   for (UpdateItems::const_iterator it = work_items_.begin();
        it != work_items_.end(); ++it) {
     CrxUpdateItem* item = *it;
     if (item->status != CrxUpdateItem::kNew)
       continue;
-    if (!AddItemToUpdateCheck(item, query))
-      break;
+    AddItemToUpdateCheck(item, update_check_items);
   }
 
   // Next we can go back to components we already checked, here
@@ -820,8 +810,7 @@
     base::TimeDelta delta = base::Time::Now() - item->last_check;
     if (delta < min_delta_time)
       continue;
-    if (!AddItemToUpdateCheck(item, query))
-      break;
+    AddItemToUpdateCheck(item, update_check_items);
   }
 
   // Finally, we check components that we already updated as long as
@@ -834,24 +823,19 @@
     base::TimeDelta delta = base::Time::Now() - item->last_check;
     if (delta < min_delta_time)
       continue;
-    if (!AddItemToUpdateCheck(item, query))
-      break;
+    AddItemToUpdateCheck(item, update_check_items);
   }
 }
 
-void CrxUpdateService::DoUpdateCheck(const std::string& query) {
-  const std::string full_query =
-      MakeFinalQuery(config_->UpdateUrl().spec(),
-                     query,
-                     config_->ExtraRequestParams());
-
-  url_fetcher_.reset(net::URLFetcher::Create(
-      0, GURL(full_query), net::URLFetcher::GET,
-      MakeContextDelegate(this, new UpdateContext())));
-  StartFetch(url_fetcher_.get(),
-             config_->RequestContext(),
-             false,
-             blocking_task_runner_);
+// Sends an update request. The |update_check_items| parameter
+// contains the sequence of <app> xml elements of the update check.
+void CrxUpdateService::DoUpdateCheck(const std::string& update_check_items) {
+  using component_updater::BuildProtocolRequest;
+  url_fetcher_.reset(component_updater::SendProtocolRequest(
+      config_->UpdateUrl(),
+      BuildProtocolRequest(update_check_items),
+      MakeContextDelegate(this, new UpdateContext()),
+      config_->RequestContext()));
 }
 
 // Called when we got a response from the update server. It consists of an xml
@@ -871,16 +855,13 @@
   delete context;
 }
 
-// Parsing the manifest is either done right now for tests or in a sandboxed
-// process for the production environment. This mitigates the case where an
-// attacker was able to feed us a malicious xml string.
 void CrxUpdateService::ParseManifest(const std::string& xml) {
   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
-  UpdateManifest manifest;
-  if (!manifest.Parse(xml))
-     CrxUpdateService::OnParseUpdateManifestFailed(manifest.errors());
+  component_updater::UpdateManifest manifest;
+  if (manifest.Parse(xml))
+    CrxUpdateService::OnParseUpdateManifestSucceeded(manifest.results());
   else
-     CrxUpdateService::OnParseUpdateManifestSucceeded(manifest.results());
+    CrxUpdateService::OnParseUpdateManifestFailed(manifest.errors());
 }
 
 // A valid Omaha update check has arrived, from only the list of components that
@@ -888,52 +869,72 @@
 // version is newer, if so we queue them for an upgrade. The next time we call
 // ProcessPendingItems() one of them will be drafted for the upgrade process.
 void CrxUpdateService::OnParseUpdateManifestSucceeded(
-    const UpdateManifest::Results& results) {
+    const component_updater::UpdateManifest::Results& results) {
+  size_t num_updates_pending = 0;
   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
-  size_t update_pending = 0;
-  std::vector<UpdateManifest::Result>::const_iterator it;
+  std::vector<component_updater::UpdateManifest::Result>::const_iterator it;
   for (it = results.list.begin(); it != results.list.end(); ++it) {
     CrxUpdateItem* crx = FindUpdateItemById(it->extension_id);
     if (!crx)
       continue;
 
-    if (crx->status != CrxUpdateItem::kChecking)
+    if (crx->status != CrxUpdateItem::kChecking) {
+      NOTREACHED();
       continue;  // Not updating this component now.
+    }
 
-    if (it->version.empty()) {
+    if (it->manifest.version.empty()) {
       // No version means no update available.
       ChangeItemState(crx, CrxUpdateItem::kNoUpdate);
       continue;
     }
-    if (!IsVersionNewer(crx->component.version, it->version)) {
-      // Our component is up to date.
+
+    if (!IsVersionNewer(crx->component.version, it->manifest.version)) {
+      // The component is up to date.
       ChangeItemState(crx, CrxUpdateItem::kUpToDate);
       continue;
     }
-    if (!it->browser_min_version.empty()) {
-      if (IsVersionNewer(chrome_version_, it->browser_min_version)) {
-        // Does not apply for this chrome version.
+
+    if (!it->manifest.browser_min_version.empty()) {
+      if (IsVersionNewer(chrome_version_, it->manifest.browser_min_version)) {
+        // The component is not compatible with this Chrome version.
         ChangeItemState(crx, CrxUpdateItem::kNoUpdate);
         continue;
       }
     }
-    // All test passed. Queue an upgrade for this component and fire the
-    // notifications.
-    crx->crx_url = it->crx_url;
-    crx->diff_crx_url = it->diff_crx_url;
+
+    if (it->manifest.packages.size() != 1) {
+      // Assume one and only one package per component.
+      ChangeItemState(crx, CrxUpdateItem::kNoUpdate);
+      continue;
+    }
+
+    // Parse the members of the result and queue an upgrade for this component.
+    crx->next_version = Version(it->manifest.version);
+
+    typedef component_updater::
+        UpdateManifest::Result::Manifest::Package Package;
+    const Package& package(it->manifest.packages[0]);
+    crx->next_fp = package.fingerprint;
+
+    // Select the first url from the list of urls until support for
+    // fall back urls is implemented.
+    if (!it->crx_urls.empty())
+      crx->crx_url = it->crx_urls[0].Resolve(package.name);
+    if (!it->crx_diffurls.empty())
+      crx->diff_crx_url = it->crx_diffurls[0].Resolve(package.namediff);
+
     ChangeItemState(crx, CrxUpdateItem::kCanUpdate);
-    crx->next_version = Version(it->version);
-    crx->next_fp = it->package_fingerprint;
-    ++update_pending;
+    ++num_updates_pending;
   }
 
-  // All the components that are not mentioned in the manifest we
-  // consider them up to date.
+  // All components that are not included in the update response are
+  // considered up to date.
   ChangeItemStatus(CrxUpdateItem::kChecking, CrxUpdateItem::kUpToDate);
 
   // If there are updates pending we do a short wait, otherwise we take
   // a longer delay until we check the components again.
-  ScheduleNextRun(update_pending > 0 ? kStepDelayShort : kStepDelayMedium);
+  ScheduleNextRun(num_updates_pending > 0 ? kStepDelayShort : kStepDelayMedium);
 }
 
 void CrxUpdateService::OnParseUpdateManifestFailed(
@@ -1164,3 +1165,4 @@
   DCHECK(config);
   return new CrxUpdateService(config);
 }
+
diff --git a/chrome/browser/component_updater/component_updater_utils.cc b/chrome/browser/component_updater/component_updater_utils.cc
new file mode 100644
index 0000000..377611d
--- /dev/null
+++ b/chrome/browser/component_updater/component_updater_utils.cc
@@ -0,0 +1,73 @@
+// Copyright 2013 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/component_updater/component_updater_utils.h"
+#include "base/guid.h"
+#include "base/strings/stringprintf.h"
+#include "base/sys_info.h"
+#include "base/win/windows_version.h"
+#include "chrome/common/chrome_version_info.h"
+#include "chrome/common/omaha_query_params/omaha_query_params.h"
+#include "net/base/load_flags.h"
+#include "net/url_request/url_fetcher.h"
+#include "net/url_request/url_request_context_getter.h"
+
+namespace component_updater {
+
+std::string BuildProtocolRequest(const std::string& request_body) {
+  const char request_format[] =
+      "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
+      "<request protocol=\"3.0\" version=\"%s-%s\" prodversion=\"%s\" "
+      "requestid=\"{%s}\" updaterchannel=\"%s\" arch=\"%s\" nacl_arch=\"%s\">"
+      "<os platform=\"%s\" version=\"%s\" arch=\"%s\"/>"
+      "%s"
+      "</request>";
+
+  const std::string prod_id(chrome::OmahaQueryParams::GetProdIdString(
+      chrome::OmahaQueryParams::CHROME));
+  const std::string chrome_version(chrome::VersionInfo().Version().c_str());
+
+  const std::string request(base::StringPrintf(request_format,
+      // Chrome version and platform information.
+      prod_id.c_str(), chrome_version.c_str(),        // "version"
+      chrome_version.c_str(),                         // "prodversion"
+      base::GenerateGUID().c_str(),                   // "requestid"
+      chrome::OmahaQueryParams::GetChannelString(),   // "updaterchannel"
+      chrome::OmahaQueryParams::getArch(),            // "arch"
+      chrome::OmahaQueryParams::getNaclArch(),        // "nacl_arch"
+
+      // OS version and platform information.
+      chrome::VersionInfo().OSType().c_str(),                  // "platform"
+      base::SysInfo().OperatingSystemVersion().c_str(),        // "version"
+      base::SysInfo().OperatingSystemArchitecture().c_str(),   // "arch"
+
+      request_body.c_str()));   // The actual payload of the request.
+
+  return request;
+}
+
+net::URLFetcher* SendProtocolRequest(
+    const GURL& url,
+    const std::string& protocol_request,
+    net::URLFetcherDelegate* url_fetcher_delegate,
+    net::URLRequestContextGetter* url_request_context_getter) {
+  net::URLFetcher* url_fetcher(
+      net::URLFetcher::Create(0,
+                              url,
+                              net::URLFetcher::POST,
+                              url_fetcher_delegate));
+
+  url_fetcher->SetUploadData("application/xml", protocol_request);
+  url_fetcher->SetRequestContext(url_request_context_getter);
+  url_fetcher->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES |
+                            net::LOAD_DO_NOT_SAVE_COOKIES |
+                            net::LOAD_DISABLE_CACHE);
+  url_fetcher->SetAutomaticallyRetryOn5xx(false);
+  url_fetcher->Start();
+
+  return url_fetcher;
+}
+
+}  // namespace component_updater
+
diff --git a/chrome/browser/component_updater/component_updater_utils.h b/chrome/browser/component_updater/component_updater_utils.h
new file mode 100644
index 0000000..4db61fb
--- /dev/null
+++ b/chrome/browser/component_updater/component_updater_utils.h
@@ -0,0 +1,49 @@
+// Copyright 2013 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.
+
+#ifndef CHROME_BROWSER_COMPONENT_UPDATER_COMPONENT_UPDATER_UTILS_H_
+#define CHROME_BROWSER_COMPONENT_UPDATER_COMPONENT_UPDATER_UTILS_H_
+
+#include <string>
+
+class GURL;
+
+namespace net {
+class URLFetcher;
+class URLFetcherDelegate;
+class URLRequestContextGetter;
+}
+
+namespace component_updater {
+
+// An update protocol request starts with a common preamble which includes
+// version and platform information for Chrome and the operating system,
+// followed by a request body, which is the actual payload of the request.
+// For example:
+//
+// <?xml version="1.0" encoding="UTF-8"?>
+// <request protocol="3.0" version="chrome-32.0.1.0"  prodversion="32.0.1.0"
+//        requestid="{7383396D-B4DD-46E1-9104-AAC6B918E792}"
+//        updaterchannel="canary" arch="x86" nacl_arch="x86-64">
+//   <os platform="win" version="6.1" arch="x86"/>
+//   ... REQUEST BODY ...
+// </request>
+
+// Builds a protocol request string by creating the outer envelope for
+// the request and including the request body specified as a parameter.
+std::string BuildProtocolRequest(const std::string& request_body);
+
+// Sends a protocol request to the the service endpoint specified by |url|.
+// The body of the request is provided by |protocol_request| and it is
+// expected to contain XML data. The caller owns the returned object.
+net::URLFetcher* SendProtocolRequest(
+    const GURL& url,
+    const std::string& protocol_request,
+    net::URLFetcherDelegate* url_fetcher_delegate,
+    net::URLRequestContextGetter* url_request_context_getter);
+
+}  // namespace component_updater
+
+#endif  // CHROME_BROWSER_COMPONENT_UPDATER_COMPONENT_UPDATER_UTILS_H_
+
diff --git a/chrome/browser/component_updater/test/component_updater_service_unittest.cc b/chrome/browser/component_updater/test/component_updater_service_unittest.cc
index 693e3e5..de1adf4 100644
--- a/chrome/browser/component_updater/test/component_updater_service_unittest.cc
+++ b/chrome/browser/component_updater/test/component_updater_service_unittest.cc
@@ -12,7 +12,6 @@
 #include "chrome/browser/component_updater/test/test_installer.h"
 #include "chrome/common/chrome_paths.h"
 #include "content/public/browser/browser_thread.h"
-#include "content/test/net/url_request_prepackaged_interceptor.h"
 #include "libxml/globals.h"
 #include "net/base/upload_bytes_element_reader.h"
 #include "net/url_request/url_fetcher.h"
@@ -26,6 +25,10 @@
 
 namespace component_updater {
 
+#define POST_INTERCEPT_SCHEME    "http"
+#define POST_INTERCEPT_HOSTNAME  "localhost2"
+#define POST_INTERCEPT_PATH      "/update2"
+
 MockComponentObserver::MockComponentObserver() {
 }
 
@@ -75,11 +78,12 @@
 }
 
 GURL TestConfigurator::UpdateUrl() {
-  return GURL("http://localhost/upd");
+  return GURL(POST_INTERCEPT_SCHEME "://"
+              POST_INTERCEPT_HOSTNAME POST_INTERCEPT_PATH);
 }
 
 GURL TestConfigurator::PingUrl() {
-  return GURL("http://localhost2/update2");
+  return UpdateUrl();
 }
 
 const char* TestConfigurator::ExtraRequestParams() { return "extra=foo"; }
@@ -122,12 +126,14 @@
 
 
 InterceptorFactory::InterceptorFactory()
-    : URLRequestPostInterceptorFactory("http", "localhost2") {}
+    : URLRequestPostInterceptorFactory(POST_INTERCEPT_SCHEME,
+                                       POST_INTERCEPT_HOSTNAME) {}
 
 InterceptorFactory::~InterceptorFactory() {}
 
 URLRequestPostInterceptor* InterceptorFactory::CreateInterceptor() {
-  return URLRequestPostInterceptorFactory::CreateInterceptor("/update2");
+  return URLRequestPostInterceptorFactory::CreateInterceptor(
+    base::FilePath::FromUTF8Unsafe(POST_INTERCEPT_PATH));
 }
 
 class PartialMatch : public URLRequestPostInterceptor::RequestMatcher {
@@ -163,11 +169,15 @@
 }
 
 void ComponentUpdaterTest::SetUp() {
+  get_interceptor_.reset(new GetInterceptor);
   interceptor_factory_.reset(new InterceptorFactory);
+  post_interceptor_ = interceptor_factory_->CreateInterceptor();
+  EXPECT_TRUE(post_interceptor_);
 }
 
 void ComponentUpdaterTest::TearDown() {
   interceptor_factory_.reset();
+  get_interceptor_.reset();
   xmlCleanupGlobals();
 }
 
@@ -208,6 +218,12 @@
   base::RunLoop runloop;
   test_configurator()->SetQuitClosure(runloop.QuitClosure());
   runloop.Run();
+
+  // Since some tests need to drain currently enqueued tasks such as network
+  // intercepts on the IO thread, run the threads until they are
+  // idle. The component updater service won't loop again until the loop count
+  // is set and the service is started.
+  RunThreadsUntilIdle();
 }
 
 void ComponentUpdaterTest::RunThreadsUntilIdle() {
@@ -237,16 +253,23 @@
 // the COMPONENT_UPDATER_STARTED and COMPONENT_UPDATER_SLEEPING notifications
 // are generated. No pings are sent.
 TEST_F(ComponentUpdaterTest, CheckCrxSleep) {
-  URLRequestPostInterceptor* post_interceptor(
-      interceptor_factory_->CreateInterceptor());
-  EXPECT_TRUE(post_interceptor != NULL);
-  EXPECT_TRUE(post_interceptor->ExpectRequest(new PartialMatch(
-    "event eventtype")));
-
-  content::URLLocalHostRequestPrepackagedInterceptor interceptor;
-
   MockComponentObserver observer;
 
+  EXPECT_CALL(observer,
+              OnEvent(ComponentObserver::COMPONENT_UPDATER_STARTED, 0))
+              .Times(1);
+  EXPECT_CALL(observer,
+              OnEvent(ComponentObserver::COMPONENT_UPDATER_SLEEPING, 0))
+              .Times(2);
+  EXPECT_CALL(observer,
+              OnEvent(ComponentObserver::COMPONENT_NOT_UPDATED, 0))
+              .Times(2);
+
+  EXPECT_TRUE(post_interceptor_->ExpectRequest(new PartialMatch(
+      "updatecheck"), test_file("updatecheck_reply_1.xml")));
+  EXPECT_TRUE(post_interceptor_->ExpectRequest(new PartialMatch(
+      "updatecheck"), test_file("updatecheck_reply_1.xml")));
+
   TestInstaller installer;
   CrxComponent com;
   com.observer = &observer;
@@ -256,68 +279,69 @@
                               Version("1.1"),
                               &installer));
 
-  const GURL expected_update_url(
-      "http://localhost/upd?extra=foo"
-      "&x=id%3Dabagagagagagagagagagagagagagagag%26v%3D1.1%26fp%3D%26uc");
-
-  interceptor.SetResponse(expected_update_url,
-                          test_file("updatecheck_reply_1.xml"));
-
   // We loop twice, but there are no updates so we expect two sleep messages.
   test_configurator()->SetLoopCount(2);
-
-  EXPECT_CALL(observer,
-              OnEvent(ComponentObserver::COMPONENT_UPDATER_STARTED, 0))
-              .Times(1);
   component_updater()->Start();
-
-  EXPECT_CALL(observer,
-              OnEvent(ComponentObserver::COMPONENT_UPDATER_SLEEPING, 0))
-              .Times(2);
-
-  EXPECT_CALL(observer,
-              OnEvent(ComponentObserver::COMPONENT_NOT_UPDATED, 0))
-              .Times(2);
   RunThreads();
 
-  EXPECT_EQ(2, interceptor.GetHitCount());
-
   EXPECT_EQ(0, static_cast<TestInstaller*>(com.installer)->error());
   EXPECT_EQ(0, static_cast<TestInstaller*>(com.installer)->install_count());
 
+  // Expect to see the two update check requests and no other requests,
+  // including pings.
+  EXPECT_EQ(2, post_interceptor_->GetHitCount())
+      << post_interceptor_->GetRequestsAsString();
+  EXPECT_EQ(2, post_interceptor_->GetCount())
+      << post_interceptor_->GetRequestsAsString();
+  EXPECT_NE(string::npos, post_interceptor_->GetRequests()[0].find(
+      "<app appid=\"abagagagagagagagagagagagagagagag\" version=\"1.1\">"
+      "<updatecheck /></app>"))
+      << post_interceptor_->GetRequestsAsString();
+  EXPECT_NE(string::npos, post_interceptor_->GetRequests()[1].find(
+      "<app appid=\"abagagagagagagagagagagagagagagag\" version=\"1.1\">"
+      "<updatecheck /></app>"))
+      << post_interceptor_->GetRequestsAsString();
+
   component_updater()->Stop();
 
   // Loop twice again but this case we simulate a server error by returning
-  // an empty file.
-
-  interceptor.SetResponse(expected_update_url,
-                          test_file("updatecheck_reply_empty"));
-
-  test_configurator()->SetLoopCount(2);
-
+  // an empty file. Expect the behavior of the service to be the same as before.
   EXPECT_CALL(observer,
               OnEvent(ComponentObserver::COMPONENT_UPDATER_STARTED, 0))
               .Times(1);
-  component_updater()->Start();
-
   EXPECT_CALL(observer,
               OnEvent(ComponentObserver::COMPONENT_UPDATER_SLEEPING, 0))
               .Times(2);
   EXPECT_CALL(observer,
               OnEvent(ComponentObserver::COMPONENT_NOT_UPDATED, 0))
               .Times(2);
+
+  post_interceptor_->Reset();
+  EXPECT_TRUE(post_interceptor_->ExpectRequest(new PartialMatch(
+      "updatecheck"), test_file("updatecheck_reply_empty")));
+  EXPECT_TRUE(post_interceptor_->ExpectRequest(new PartialMatch(
+      "updatecheck"), test_file("updatecheck_reply_empty")));
+
+  test_configurator()->SetLoopCount(2);
+  component_updater()->Start();
   RunThreads();
 
-  EXPECT_EQ(0, post_interceptor->GetHitCount())
-      << post_interceptor->GetRequestsAsString();
-  EXPECT_EQ(0, post_interceptor->GetMissCount())
-      << post_interceptor->GetRequestsAsString();
-
-  EXPECT_EQ(4, interceptor.GetHitCount());
-
   EXPECT_EQ(0, static_cast<TestInstaller*>(com.installer)->error());
   EXPECT_EQ(0, static_cast<TestInstaller*>(com.installer)->install_count());
 
+  EXPECT_EQ(2, post_interceptor_->GetHitCount())
+      << post_interceptor_->GetRequestsAsString();
+  EXPECT_EQ(2, post_interceptor_->GetCount())
+      << post_interceptor_->GetRequestsAsString();
+  EXPECT_NE(string::npos, post_interceptor_->GetRequests()[0].find(
+      "<app appid=\"abagagagagagagagagagagagagagagag\" version=\"1.1\">"
+      "<updatecheck /></app>"))
+      << post_interceptor_->GetRequestsAsString();
+  EXPECT_NE(string::npos, post_interceptor_->GetRequests()[1].find(
+      "<app appid=\"abagagagagagagagagagagagagagagag\" version=\"1.1\">"
+      "<updatecheck /></app>"))
+      << post_interceptor_->GetRequestsAsString();
+
   component_updater()->Stop();
 }
 
@@ -325,22 +349,12 @@
 // the notifications above COMPONENT_UPDATE_FOUND and COMPONENT_UPDATE_READY
 // should have been fired. We do two loops so the second time around there
 // should be nothing left to do.
-// We also check that only 3 non-ping network requests are issued:
+// We also check that the following network requests are issued:
 // 1- manifest check
 // 2- download crx
-// 3- second manifest check.
-// Only one ping is sent.
+// 3- ping
+// 4- second manifest check.
 TEST_F(ComponentUpdaterTest, InstallCrx) {
-  URLRequestPostInterceptor* post_interceptor(
-      interceptor_factory_->CreateInterceptor());
-  EXPECT_TRUE(post_interceptor != NULL);
-  EXPECT_TRUE(post_interceptor->ExpectRequest(new PartialMatch(
-      "<o:app appid=\"jebgalgnebhfojomionfpkfelancnnkf\" version=\"1.0\">"
-      "<o:event eventtype=\"3\" eventresult=\"1\" "
-      "previousversion=\"0.9\" nextversion=\"1.0\"/></o:app>")));
-
-  content::URLLocalHostRequestPrepackagedInterceptor interceptor;
-
   MockComponentObserver observer1;
   {
     InSequence seq;
@@ -387,6 +401,16 @@
                 .Times(1);
   }
 
+  EXPECT_TRUE(post_interceptor_->ExpectRequest(new PartialMatch(
+      "updatecheck"), test_file("updatecheck_reply_1.xml")));
+  EXPECT_TRUE(post_interceptor_->ExpectRequest(new PartialMatch("event")));
+  EXPECT_TRUE(post_interceptor_->ExpectRequest(new PartialMatch(
+      "updatecheck"), test_file("updatecheck_reply_1.xml")));
+
+  get_interceptor_->SetResponse(
+      GURL(expected_crx_url),
+      test_file("jebgalgnebhfojomionfpkfelancnnkf.crx"));
+
   TestInstaller installer1;
   CrxComponent com1;
   com1.observer = &observer1;
@@ -396,25 +420,7 @@
   com2.observer = &observer2;
   RegisterComponent(&com2, kTestComponent_abag, Version("2.2"), &installer2);
 
-  const GURL expected_update_url_1(
-      "http://localhost/upd?extra=foo"
-      "&x=id%3Djebgalgnebhfojomionfpkfelancnnkf%26v%3D0.9%26fp%3D%26uc"
-      "&x=id%3Dabagagagagagagagagagagagagagagag%26v%3D2.2%26fp%3D%26uc");
-
-  const GURL expected_update_url_2(
-      "http://localhost/upd?extra=foo"
-      "&x=id%3Dabagagagagagagagagagagagagagagag%26v%3D2.2%26fp%3D%26uc"
-      "&x=id%3Djebgalgnebhfojomionfpkfelancnnkf%26v%3D1.0%26fp%3D%26uc");
-
-  interceptor.SetResponse(expected_update_url_1,
-                          test_file("updatecheck_reply_1.xml"));
-  interceptor.SetResponse(expected_update_url_2,
-                          test_file("updatecheck_reply_1.xml"));
-  interceptor.SetResponse(GURL(expected_crx_url),
-                          test_file("jebgalgnebhfojomionfpkfelancnnkf.crx"));
-
   test_configurator()->SetLoopCount(2);
-
   component_updater()->Start();
   RunThreads();
 
@@ -423,12 +429,37 @@
   EXPECT_EQ(0, static_cast<TestInstaller*>(com2.installer)->error());
   EXPECT_EQ(0, static_cast<TestInstaller*>(com2.installer)->install_count());
 
-  EXPECT_EQ(3, interceptor.GetHitCount());
+  // Expect three request in total: two update checks and one ping.
+  EXPECT_EQ(3, post_interceptor_->GetHitCount())
+      << post_interceptor_->GetRequestsAsString();
+  EXPECT_EQ(3, post_interceptor_->GetCount())
+      << post_interceptor_->GetRequestsAsString();
 
-  EXPECT_EQ(1, post_interceptor->GetHitCount())
-      << post_interceptor->GetRequestsAsString();
-  EXPECT_EQ(0, post_interceptor->GetMissCount())
-      << post_interceptor->GetRequestsAsString();
+  // Expect one component download.
+  EXPECT_EQ(1, get_interceptor_->GetHitCount());
+
+  EXPECT_NE(string::npos, post_interceptor_->GetRequests()[0].find(
+      "<app appid=\"jebgalgnebhfojomionfpkfelancnnkf\" version=\"0.9\">"
+      "<updatecheck /></app>"))
+      << post_interceptor_->GetRequestsAsString();
+  EXPECT_NE(string::npos, post_interceptor_->GetRequests()[0].find(
+      "<app appid=\"abagagagagagagagagagagagagagagag\" version=\"2.2\">"
+      "<updatecheck /></app>"))
+      << post_interceptor_->GetRequestsAsString();
+
+  EXPECT_NE(string::npos, post_interceptor_->GetRequests()[1].find(
+      "<app appid=\"jebgalgnebhfojomionfpkfelancnnkf\" "
+      "version=\"0.9\" nextversion=\"1.0\">"
+      "<event eventtype=\"3\" eventresult=\"1\"/></app>"))
+      << post_interceptor_->GetRequestsAsString();
+
+  EXPECT_NE(string::npos, post_interceptor_->GetRequests()[2].find(
+      "<app appid=\"jebgalgnebhfojomionfpkfelancnnkf\" version=\"1.0\">"
+      "<updatecheck /></app>"));
+  EXPECT_NE(string::npos, post_interceptor_->GetRequests()[2].find(
+      "<app appid=\"abagagagagagagagagagagagagagagag\" version=\"2.2\">"
+      "<updatecheck /></app>"))
+      << post_interceptor_->GetRequestsAsString();
 
   component_updater()->Stop();
 }
@@ -437,34 +468,29 @@
 // particular there should not be an install because the minimum product
 // version is much higher than of chrome.
 TEST_F(ComponentUpdaterTest, ProdVersionCheck) {
-  URLRequestPostInterceptor* post_interceptor(
-      interceptor_factory_->CreateInterceptor());
-  EXPECT_TRUE(post_interceptor != NULL);
-  EXPECT_TRUE(post_interceptor->ExpectRequest(new PartialMatch(
-      "event eventtype")));
+  EXPECT_TRUE(post_interceptor_->ExpectRequest(new PartialMatch(
+      "updatecheck"), test_file("updatecheck_reply_2.xml")));
 
-  content::URLLocalHostRequestPrepackagedInterceptor interceptor;
+  get_interceptor_->SetResponse(
+      GURL(expected_crx_url),
+      test_file("jebgalgnebhfojomionfpkfelancnnkf.crx"));
 
   TestInstaller installer;
   CrxComponent com;
   RegisterComponent(&com, kTestComponent_jebg, Version("0.9"), &installer);
 
-  const GURL expected_update_url(
-      "http://localhost/upd?extra=foo&x=id%3D"
-      "jebgalgnebhfojomionfpkfelancnnkf%26v%3D0.9%26fp%3D%26uc");
-
-  interceptor.SetResponse(expected_update_url,
-                          test_file("updatecheck_reply_2.xml"));
-  interceptor.SetResponse(GURL(expected_crx_url),
-                          test_file("jebgalgnebhfojomionfpkfelancnnkf.crx"));
-
   test_configurator()->SetLoopCount(1);
   component_updater()->Start();
   RunThreads();
 
-  EXPECT_EQ(0, post_interceptor->GetHitCount())
-      << post_interceptor->GetRequestsAsString();
-  EXPECT_EQ(1, interceptor.GetHitCount());
+  // Expect one update check and no ping.
+  EXPECT_EQ(1, post_interceptor_->GetHitCount())
+      << post_interceptor_->GetRequestsAsString();
+  EXPECT_EQ(1, post_interceptor_->GetCount())
+      << post_interceptor_->GetRequestsAsString();
+
+  // Expect no download to occur.
+  EXPECT_EQ(0, get_interceptor_->GetHitCount());
 
   EXPECT_EQ(0, static_cast<TestInstaller*>(com.installer)->error());
   EXPECT_EQ(0, static_cast<TestInstaller*>(com.installer)->install_count());
@@ -479,16 +505,6 @@
 //  - We make an on demand call.
 //  - This triggers a second loop, which has a reply that triggers an install.
 TEST_F(ComponentUpdaterTest, OnDemandUpdate) {
-  URLRequestPostInterceptor* post_interceptor(
-      interceptor_factory_->CreateInterceptor());
-  EXPECT_TRUE(post_interceptor != NULL);
-  EXPECT_TRUE(post_interceptor->ExpectRequest(new PartialMatch(
-      "<o:app appid=\"jebgalgnebhfojomionfpkfelancnnkf\" version=\"1.0\">"
-      "<o:event eventtype=\"3\" eventresult=\"1\" "
-      "previousversion=\"0.9\" nextversion=\"1.0\"/></o:app>")));
-
-  content::URLLocalHostRequestPrepackagedInterceptor interceptor;
-
   MockComponentObserver observer1;
   {
     InSequence seq;
@@ -541,6 +557,13 @@
                 .Times(1);
   }
 
+  EXPECT_TRUE(post_interceptor_->ExpectRequest(new PartialMatch(
+      "updatecheck"), test_file("updatecheck_reply_empty")));
+
+  get_interceptor_->SetResponse(
+      GURL(expected_crx_url),
+      test_file("jebgalgnebhfojomionfpkfelancnnkf.crx"));
+
   TestInstaller installer1;
   CrxComponent com1;
   com1.observer = &observer1;
@@ -550,30 +573,25 @@
   com2.observer = &observer2;
   RegisterComponent(&com2, kTestComponent_jebg, Version("0.9"), &installer2);
 
-  const GURL expected_update_url_1(
-      "http://localhost/upd?extra=foo"
-      "&x=id%3Dabagagagagagagagagagagagagagagag%26v%3D2.2%26fp%3D%26uc"
-      "&x=id%3Djebgalgnebhfojomionfpkfelancnnkf%26v%3D0.9%26fp%3D%26uc");
-
-  const GURL expected_update_url_2(
-      "http://localhost/upd?extra=foo"
-      "&x=id%3Djebgalgnebhfojomionfpkfelancnnkf%26v%3D0.9%26fp%3D%26uc"
-      "%26installsource%3Dondemand"
-      "&x=id%3Dabagagagagagagagagagagagagagagag%26v%3D2.2%26fp%3D%26uc");
-
-  interceptor.SetResponse(expected_update_url_1,
-                          test_file("updatecheck_reply_empty"));
-  interceptor.SetResponse(expected_update_url_2,
-                          test_file("updatecheck_reply_1.xml"));
-  interceptor.SetResponse(GURL(expected_crx_url),
-                          test_file("jebgalgnebhfojomionfpkfelancnnkf.crx"));
   // No update normally.
   test_configurator()->SetLoopCount(1);
   component_updater()->Start();
   RunThreads();
   component_updater()->Stop();
 
+  EXPECT_EQ(1, post_interceptor_->GetHitCount())
+      << post_interceptor_->GetRequestsAsString();
+  EXPECT_EQ(1, post_interceptor_->GetCount())
+      << post_interceptor_->GetRequestsAsString();
+
+  EXPECT_EQ(0, get_interceptor_->GetHitCount());
+
   // Update after an on-demand check is issued.
+  post_interceptor_->Reset();
+  EXPECT_TRUE(post_interceptor_->ExpectRequest(new PartialMatch(
+      "updatecheck"), test_file("updatecheck_reply_1.xml")));
+  EXPECT_TRUE(post_interceptor_->ExpectRequest(new PartialMatch("event")));
+
   EXPECT_EQ(ComponentUpdateService::kOk,
             OnDemandTester::OnDemand(component_updater(),
                                      GetCrxComponentID(com2)));
@@ -586,7 +604,28 @@
   EXPECT_EQ(0, static_cast<TestInstaller*>(com2.installer)->error());
   EXPECT_EQ(1, static_cast<TestInstaller*>(com2.installer)->install_count());
 
-  EXPECT_EQ(3, interceptor.GetHitCount());
+  EXPECT_EQ(2, post_interceptor_->GetHitCount())
+      << post_interceptor_->GetRequestsAsString();
+  EXPECT_EQ(2, post_interceptor_->GetCount())
+      << post_interceptor_->GetRequestsAsString();
+
+  EXPECT_EQ(1, get_interceptor_->GetHitCount());
+
+  // Expect the update check to contain an "ondemand" request for the
+  // second component (com2) and a normal request for the other component.
+  EXPECT_NE(string::npos, post_interceptor_->GetRequests()[0].find(
+      "<app appid=\"abagagagagagagagagagagagagagagag\" "
+      "version=\"2.2\"><updatecheck /></app>"))
+      << post_interceptor_->GetRequestsAsString();
+  EXPECT_NE(string::npos, post_interceptor_->GetRequests()[0].find(
+      "<app appid=\"jebgalgnebhfojomionfpkfelancnnkf\" "
+      "version=\"0.9\" installsource=\"ondemand\"><updatecheck /></app>"))
+      << post_interceptor_->GetRequestsAsString();
+  EXPECT_NE(string::npos, post_interceptor_->GetRequests()[1].find(
+      "<app appid=\"jebgalgnebhfojomionfpkfelancnnkf\" "
+      "version=\"0.9\" nextversion=\"1.0\">"
+      "<event eventtype=\"3\" eventresult=\"1\"/></app>"))
+      << post_interceptor_->GetRequestsAsString();
 
   // Also check what happens if previous check too soon.
   test_configurator()->SetOnDemandTime(60 * 60);
@@ -626,24 +665,24 @@
                 .Times(1);
   }
 
-  const GURL expected_update_url_3(
-      "http://localhost/upd?extra=foo"
-      "&x=id%3Djebgalgnebhfojomionfpkfelancnnkf%26v%3D1.0%26fp%3D%26uc"
-      "&x=id%3Dabagagagagagagagagagagagagagagag%26v%3D2.2%26fp%3D%26uc");
-
   // No update: error from no server response
-  interceptor.SetResponse(expected_update_url_3,
-                          test_file("updatecheck_reply_empty"));
+  post_interceptor_->Reset();
+  EXPECT_TRUE(post_interceptor_->ExpectRequest(new PartialMatch(
+      "updatecheck"), test_file("updatecheck_reply_empty")));
+
   test_configurator()->SetLoopCount(1);
   component_updater()->Start();
   EXPECT_EQ(ComponentUpdateService::kOk,
             OnDemandTester::OnDemand(component_updater(),
                                      GetCrxComponentID(com2)));
-
   RunThreads();
-
   component_updater()->Stop();
 
+  EXPECT_EQ(1, post_interceptor_->GetHitCount())
+      << post_interceptor_->GetRequestsAsString();
+  EXPECT_EQ(1, post_interceptor_->GetCount())
+      << post_interceptor_->GetRequestsAsString();
+
   // No update: already updated to 1.0 so nothing new
   EXPECT_TRUE(Mock::VerifyAndClearExpectations(&observer1));
   {
@@ -672,20 +711,21 @@
                 .Times(1);
   }
 
-  interceptor.SetResponse(expected_update_url_3,
-                          test_file("updatecheck_reply_1.xml"));
+  post_interceptor_->Reset();
+  EXPECT_TRUE(post_interceptor_->ExpectRequest(new PartialMatch(
+      "updatecheck"), test_file("updatecheck_reply_1.xml")));
+
   test_configurator()->SetLoopCount(1);
   component_updater()->Start();
   EXPECT_EQ(ComponentUpdateService::kOk,
             OnDemandTester::OnDemand(component_updater(),
                                      GetCrxComponentID(com2)));
-
   RunThreads();
 
-  EXPECT_EQ(1, post_interceptor->GetHitCount())
-      << post_interceptor->GetRequestsAsString();
-  EXPECT_EQ(0, post_interceptor->GetMissCount())
-      << post_interceptor->GetRequestsAsString();
+  EXPECT_EQ(1, post_interceptor_->GetHitCount())
+      << post_interceptor_->GetRequestsAsString();
+  EXPECT_EQ(1, post_interceptor_->GetCount())
+      << post_interceptor_->GetRequestsAsString();
 
   component_updater()->Stop();
 }
@@ -693,16 +733,6 @@
 // Verify that a previously registered component can get re-registered
 // with a different version.
 TEST_F(ComponentUpdaterTest, CheckReRegistration) {
-  URLRequestPostInterceptor* post_interceptor(
-      interceptor_factory_->CreateInterceptor());
-  EXPECT_TRUE(post_interceptor != NULL);
-  EXPECT_TRUE(post_interceptor->ExpectRequest(new PartialMatch(
-      "<o:app appid=\"jebgalgnebhfojomionfpkfelancnnkf\" version=\"1.0\">"
-      "<o:event eventtype=\"3\" eventresult=\"1\" "
-      "previousversion=\"0.9\" nextversion=\"1.0\"/></o:app>")));
-
-  content::URLLocalHostRequestPrepackagedInterceptor interceptor;
-
   MockComponentObserver observer1;
   {
     InSequence seq;
@@ -749,6 +779,16 @@
                 .Times(1);
   }
 
+  EXPECT_TRUE(post_interceptor_->ExpectRequest(new PartialMatch(
+      "updatecheck"), test_file("updatecheck_reply_1.xml")));
+  EXPECT_TRUE(post_interceptor_->ExpectRequest(new PartialMatch("event")));
+  EXPECT_TRUE(post_interceptor_->ExpectRequest(new PartialMatch(
+      "updatecheck"), test_file("updatecheck_reply_1.xml")));
+
+  get_interceptor_->SetResponse(
+      GURL(expected_crx_url),
+      test_file("jebgalgnebhfojomionfpkfelancnnkf.crx"));
+
   TestInstaller installer1;
   CrxComponent com1;
   com1.observer = &observer1;
@@ -758,28 +798,9 @@
   com2.observer = &observer2;
   RegisterComponent(&com2, kTestComponent_abag, Version("2.2"), &installer2);
 
-  // Start with 0.9, and update to 1.0
-  const GURL expected_update_url_1(
-      "http://localhost/upd?extra=foo"
-      "&x=id%3Djebgalgnebhfojomionfpkfelancnnkf%26v%3D0.9%26fp%3D%26uc"
-      "&x=id%3Dabagagagagagagagagagagagagagagag%26v%3D2.2%26fp%3D%26uc");
-
-  const GURL expected_update_url_2(
-      "http://localhost/upd?extra=foo"
-      "&x=id%3Dabagagagagagagagagagagagagagagag%26v%3D2.2%26fp%3D%26uc"
-      "&x=id%3Djebgalgnebhfojomionfpkfelancnnkf%26v%3D1.0%26fp%3D%26uc");
-
-  interceptor.SetResponse(expected_update_url_1,
-                          test_file("updatecheck_reply_1.xml"));
-  interceptor.SetResponse(expected_update_url_2,
-                          test_file("updatecheck_reply_1.xml"));
-  interceptor.SetResponse(GURL(expected_crx_url),
-                          test_file("jebgalgnebhfojomionfpkfelancnnkf.crx"));
-
-  // Loop twice to issue two checks: (1) with original 0.9 version
-  // and (2) with the updated 1.0 version.
+  // Loop twice to issue two checks: (1) with original 0.9 version, update to
+  // 1.0, and do the second check (2) with the updated 1.0 version.
   test_configurator()->SetLoopCount(2);
-
   component_updater()->Start();
   RunThreads();
 
@@ -788,12 +809,23 @@
   EXPECT_EQ(0, static_cast<TestInstaller*>(com2.installer)->error());
   EXPECT_EQ(0, static_cast<TestInstaller*>(com2.installer)->install_count());
 
-  EXPECT_EQ(1, post_interceptor->GetHitCount())
-        << post_interceptor->GetRequestsAsString();
-  EXPECT_EQ(0, post_interceptor->GetMissCount())
-        << post_interceptor->GetRequestsAsString();
+  EXPECT_EQ(3, post_interceptor_->GetHitCount())
+        << post_interceptor_->GetRequestsAsString();
+  EXPECT_EQ(1, get_interceptor_->GetHitCount());
 
-  EXPECT_EQ(3, interceptor.GetHitCount());
+  EXPECT_NE(string::npos, post_interceptor_->GetRequests()[0].find(
+      "<app appid=\"jebgalgnebhfojomionfpkfelancnnkf\" version=\"0.9\">"
+      "<updatecheck /></app>"))
+      << post_interceptor_->GetRequestsAsString();
+  EXPECT_NE(string::npos, post_interceptor_->GetRequests()[1].find(
+      "<app appid=\"jebgalgnebhfojomionfpkfelancnnkf\" "
+      "version=\"0.9\" nextversion=\"1.0\">"
+      "<event eventtype=\"3\" eventresult=\"1\"/></app>"))
+      << post_interceptor_->GetRequestsAsString();
+  EXPECT_NE(string::npos, post_interceptor_->GetRequests()[2].find(
+      "<app appid=\"jebgalgnebhfojomionfpkfelancnnkf\" version=\"1.0\">"
+      "<updatecheck /></app>"))
+      << post_interceptor_->GetRequestsAsString();
 
   component_updater()->Stop();
 
@@ -826,6 +858,10 @@
                 .Times(1);
   }
 
+  post_interceptor_->Reset();
+  EXPECT_TRUE(post_interceptor_->ExpectRequest(new PartialMatch(
+      "updatecheck"), test_file("updatecheck_reply_1.xml")));
+
   TestInstaller installer3;
   EXPECT_EQ(ComponentUpdateService::kReplaced,
             RegisterComponent(&com1,
@@ -833,35 +869,27 @@
                               Version("2.2"),
                               &installer3));
 
-  // Check that we send out 2.2 as our version.
-  // Interceptor's hit count should go up by 1.
-  const GURL expected_update_url_3(
-      "http://localhost/upd?extra=foo"
-      "&x=id%3Djebgalgnebhfojomionfpkfelancnnkf%26v%3D2.2%26fp%3D%26uc"
-      "&x=id%3Dabagagagagagagagagagagagagagagag%26v%3D2.2%26fp%3D%26uc");
-
-  interceptor.SetResponse(expected_update_url_3,
-                          test_file("updatecheck_reply_1.xml"));
-
   // Loop once just to notice the check happening with the re-register version.
   test_configurator()->SetLoopCount(1);
   component_updater()->Start();
   RunThreads();
 
-  EXPECT_EQ(4, interceptor.GetHitCount());
-
-  // No additional pings are expected.
-  EXPECT_EQ(1, post_interceptor->GetHitCount())
-        << post_interceptor->GetRequestsAsString();
-  EXPECT_EQ(0, post_interceptor->GetMissCount())
-        << post_interceptor->GetRequestsAsString();
-
   // We created a new installer, so the counts go back to 0.
   EXPECT_EQ(0, static_cast<TestInstaller*>(com1.installer)->error());
   EXPECT_EQ(0, static_cast<TestInstaller*>(com1.installer)->install_count());
   EXPECT_EQ(0, static_cast<TestInstaller*>(com2.installer)->error());
   EXPECT_EQ(0, static_cast<TestInstaller*>(com2.installer)->install_count());
 
+  // One update check and no additional pings are expected.
+  EXPECT_EQ(1, post_interceptor_->GetHitCount())
+        << post_interceptor_->GetRequestsAsString();
+  EXPECT_EQ(1, post_interceptor_->GetCount())
+        << post_interceptor_->GetRequestsAsString();
+
+  EXPECT_NE(string::npos, post_interceptor_->GetRequests()[0].find(
+      "<app appid=\"jebgalgnebhfojomionfpkfelancnnkf\" version=\"2.2\">"
+      "<updatecheck /></app>"));
+
   component_updater()->Stop();
 }
 
@@ -877,67 +905,63 @@
 // There should be two pings, one for each update. The second will bear a
 // diffresult=1, while the first will not.
 TEST_F(ComponentUpdaterTest, DifferentialUpdate) {
-  URLRequestPostInterceptor* post_interceptor(
-      interceptor_factory_->CreateInterceptor());
-  EXPECT_TRUE(post_interceptor != NULL);
-  EXPECT_TRUE(post_interceptor->ExpectRequest(new PartialMatch(
-      "<o:app appid=\"ihfokbkgjpifnbbojhneepfflplebdkc\" version=\"1.0\">"
-      "<o:event eventtype=\"3\" eventresult=\"1\" "
-      "previousversion=\"0.0\" nextversion=\"1.0\" nextfp=\"1\"/></o:app>")));
-  EXPECT_TRUE(post_interceptor->ExpectRequest(new PartialMatch(
-      "<o:app appid=\"ihfokbkgjpifnbbojhneepfflplebdkc\" version=\"2.0\">"
-      "<o:event eventtype=\"3\" eventresult=\"1\" "
-      "previousversion=\"1.0\" nextversion=\"2.0\" "
-      "diffresult=\"1\" previousfp=\"1\" nextfp=\"f22\"/></o:app>")));
+  EXPECT_TRUE(post_interceptor_->ExpectRequest(new PartialMatch(
+      "updatecheck"), test_file("updatecheck_diff_reply_1.xml")));
+  EXPECT_TRUE(post_interceptor_->ExpectRequest(new PartialMatch("event")));
+  EXPECT_TRUE(post_interceptor_->ExpectRequest(new PartialMatch(
+      "updatecheck"), test_file("updatecheck_diff_reply_2.xml")));
+  EXPECT_TRUE(post_interceptor_->ExpectRequest(new PartialMatch("event")));
+  EXPECT_TRUE(post_interceptor_->ExpectRequest(new PartialMatch(
+      "updatecheck"), test_file("updatecheck_diff_reply_3.xml")));
 
-  content::URLLocalHostRequestPrepackagedInterceptor interceptor;
+  get_interceptor_->SetResponse(
+      GURL("http://localhost/download/ihfokbkgjpifnbbojhneepfflplebdkc_1.crx"),
+      test_file("ihfokbkgjpifnbbojhneepfflplebdkc_1.crx"));
+  get_interceptor_->SetResponse(
+      GURL("http://localhost/download/"
+           "ihfokbkgjpifnbbojhneepfflplebdkc_1to2.crx"),
+      test_file("ihfokbkgjpifnbbojhneepfflplebdkc_1to2.crx"));
 
   VersionedTestInstaller installer;
   CrxComponent com;
   RegisterComponent(&com, kTestComponent_ihfo, Version("0.0"), &installer);
 
-  const GURL expected_update_url_0(
-      "http://localhost/upd?extra=foo"
-      "&x=id%3Dihfokbkgjpifnbbojhneepfflplebdkc%26v%3D0.0%26fp%3D%26uc");
-  const GURL expected_update_url_1(
-      "http://localhost/upd?extra=foo"
-      "&x=id%3Dihfokbkgjpifnbbojhneepfflplebdkc%26v%3D1.0%26fp%3D1%26uc");
-  const GURL expected_update_url_2(
-      "http://localhost/upd?extra=foo"
-      "&x=id%3Dihfokbkgjpifnbbojhneepfflplebdkc%26v%3D2.0%26fp%3Df22%26uc");
-  const GURL expected_crx_url_1(
-      "http://localhost/download/ihfokbkgjpifnbbojhneepfflplebdkc_1.crx");
-  const GURL expected_crx_url_1_diff_2(
-      "http://localhost/download/ihfokbkgjpifnbbojhneepfflplebdkc_1to2.crx");
-
-  interceptor.SetResponse(expected_update_url_0,
-                          test_file("updatecheck_diff_reply_1.xml"));
-  interceptor.SetResponse(expected_update_url_1,
-                          test_file("updatecheck_diff_reply_2.xml"));
-  interceptor.SetResponse(expected_update_url_2,
-                          test_file("updatecheck_diff_reply_3.xml"));
-  interceptor.SetResponse(expected_crx_url_1,
-                          test_file("ihfokbkgjpifnbbojhneepfflplebdkc_1.crx"));
-  interceptor.SetResponse(
-      expected_crx_url_1_diff_2,
-      test_file("ihfokbkgjpifnbbojhneepfflplebdkc_1to2.crx"));
-
   test_configurator()->SetLoopCount(3);
-
   component_updater()->Start();
   RunThreads();
 
   EXPECT_EQ(0, static_cast<TestInstaller*>(com.installer)->error());
   EXPECT_EQ(2, static_cast<TestInstaller*>(com.installer)->install_count());
 
-  // One ping has the diffresult=1, the other does not.
-  EXPECT_EQ(2, post_interceptor->GetHitCount())
-      << post_interceptor->GetRequestsAsString();
-  EXPECT_EQ(0, post_interceptor->GetMissCount())
-      << post_interceptor->GetRequestsAsString();
+  EXPECT_EQ(5, post_interceptor_->GetHitCount())
+      << post_interceptor_->GetRequestsAsString();
+  EXPECT_EQ(5, post_interceptor_->GetCount())
+      << post_interceptor_->GetRequestsAsString();
+  EXPECT_EQ(2, get_interceptor_->GetHitCount());
 
-  EXPECT_EQ(5, interceptor.GetHitCount());
-
+  EXPECT_NE(string::npos, post_interceptor_->GetRequests()[0].find(
+      "<app appid=\"ihfokbkgjpifnbbojhneepfflplebdkc\" version=\"0.0\">"
+      "<updatecheck /></app>"))
+      << post_interceptor_->GetRequestsAsString();
+  EXPECT_NE(string::npos, post_interceptor_->GetRequests()[1].find(
+      "<app appid=\"ihfokbkgjpifnbbojhneepfflplebdkc\" "
+      "version=\"0.0\" nextversion=\"1.0\">"
+      "<event eventtype=\"3\" eventresult=\"1\" nextfp=\"1\"/></app>"))
+      << post_interceptor_->GetRequestsAsString();
+  EXPECT_NE(string::npos, post_interceptor_->GetRequests()[2].find(
+      "<app appid=\"ihfokbkgjpifnbbojhneepfflplebdkc\" version=\"1.0\">"
+      "<updatecheck /><packages><package fp=\"1\"/></packages></app>"))
+      << post_interceptor_->GetRequestsAsString();
+  EXPECT_NE(string::npos, post_interceptor_->GetRequests()[3].find(
+      "<app appid=\"ihfokbkgjpifnbbojhneepfflplebdkc\" "
+      "version=\"1.0\" nextversion=\"2.0\">"
+      "<event eventtype=\"3\" eventresult=\"1\" diffresult=\"1\" "
+      "previousfp=\"1\" nextfp=\"22\"/></app>"))
+      << post_interceptor_->GetRequestsAsString();
+  EXPECT_NE(string::npos, post_interceptor_->GetRequests()[4].find(
+      "<app appid=\"ihfokbkgjpifnbbojhneepfflplebdkc\" version=\"2.0\">"
+      "<updatecheck /><packages><package fp=\"22\"/></packages></app>"))
+      << post_interceptor_->GetRequestsAsString();
   component_updater()->Stop();
 }
 
@@ -952,49 +976,28 @@
 // 4- update check (loop 2 - no update available)
 // There should be one ping for the first attempted update.
 TEST_F(ComponentUpdaterTest, DifferentialUpdateFails) {
-  URLRequestPostInterceptor* post_interceptor(
-      interceptor_factory_->CreateInterceptor());
-  EXPECT_TRUE(post_interceptor != NULL);
-  EXPECT_TRUE(post_interceptor->ExpectRequest(new PartialMatch(
-      "<o:app appid=\"ihfokbkgjpifnbbojhneepfflplebdkc\" version=\"2.0\">"
-      "<o:event eventtype=\"3\" eventresult=\"1\" "
-      "previousversion=\"1.0\" nextversion=\"2.0\" "
-      "diffresult=\"0\" differrorcat=\"2\" differrorcode=\"16\" "
-      "nextfp=\"f22\"/></o:app>")));
+  EXPECT_TRUE(post_interceptor_->ExpectRequest(new PartialMatch(
+      "updatecheck"), test_file("updatecheck_diff_reply_2.xml")));
+  EXPECT_TRUE(post_interceptor_->ExpectRequest(new PartialMatch("event")));
+  EXPECT_TRUE(post_interceptor_->ExpectRequest(new PartialMatch(
+      "updatecheck"), test_file("updatecheck_diff_reply_3.xml")));
 
-  content::URLLocalHostRequestPrepackagedInterceptor interceptor;
+  get_interceptor_->SetResponse(
+      GURL("http://localhost/download/ihfokbkgjpifnbbojhneepfflplebdkc_1.crx"),
+      test_file("ihfokbkgjpifnbbojhneepfflplebdkc_1.crx"));
+  get_interceptor_->SetResponse(
+      GURL("http://localhost/download/"
+           "ihfokbkgjpifnbbojhneepfflplebdkc_1to2.crx"),
+      test_file("ihfokbkgjpifnbbojhneepfflplebdkc_1to2.crx"));
+  get_interceptor_->SetResponse(
+      GURL("http://localhost/download/ihfokbkgjpifnbbojhneepfflplebdkc_2.crx"),
+      test_file("ihfokbkgjpifnbbojhneepfflplebdkc_2.crx"));
 
   TestInstaller installer;
   CrxComponent com;
   RegisterComponent(&com, kTestComponent_ihfo, Version("1.0"), &installer);
 
-  const GURL expected_update_url_1(
-      "http://localhost/upd?extra=foo"
-      "&x=id%3Dihfokbkgjpifnbbojhneepfflplebdkc%26v%3D1.0%26fp%3D%26uc");
-  const GURL expected_update_url_2(
-      "http://localhost/upd?extra=foo"
-      "&x=id%3Dihfokbkgjpifnbbojhneepfflplebdkc%26v%3D2.0%26fp%3Df22%26uc");
-  const GURL expected_crx_url_1(
-      "http://localhost/download/ihfokbkgjpifnbbojhneepfflplebdkc_1.crx");
-  const GURL expected_crx_url_1_diff_2(
-      "http://localhost/download/ihfokbkgjpifnbbojhneepfflplebdkc_1to2.crx");
-  const GURL expected_crx_url_2(
-      "http://localhost/download/ihfokbkgjpifnbbojhneepfflplebdkc_2.crx");
-
-  interceptor.SetResponse(expected_update_url_1,
-                          test_file("updatecheck_diff_reply_2.xml"));
-  interceptor.SetResponse(expected_update_url_2,
-                          test_file("updatecheck_diff_reply_3.xml"));
-  interceptor.SetResponse(expected_crx_url_1,
-                          test_file("ihfokbkgjpifnbbojhneepfflplebdkc_1.crx"));
-  interceptor.SetResponse(
-      expected_crx_url_1_diff_2,
-      test_file("ihfokbkgjpifnbbojhneepfflplebdkc_1to2.crx"));
-  interceptor.SetResponse(expected_crx_url_2,
-                          test_file("ihfokbkgjpifnbbojhneepfflplebdkc_2.crx"));
-
   test_configurator()->SetLoopCount(2);
-
   component_updater()->Start();
   RunThreads();
 
@@ -1002,34 +1005,32 @@
   EXPECT_EQ(0, static_cast<TestInstaller*>(com.installer)->error());
   EXPECT_EQ(1, static_cast<TestInstaller*>(com.installer)->install_count());
 
-  EXPECT_EQ(1, post_interceptor->GetHitCount())
-      << post_interceptor->GetRequestsAsString();
-  EXPECT_EQ(0, post_interceptor->GetMissCount())
-      << post_interceptor->GetRequestsAsString();
+  EXPECT_EQ(3, post_interceptor_->GetHitCount())
+      << post_interceptor_->GetRequestsAsString();
+  EXPECT_EQ(3, post_interceptor_->GetCount())
+      << post_interceptor_->GetRequestsAsString();
+  EXPECT_EQ(2, get_interceptor_->GetHitCount());
 
-  EXPECT_EQ(4, interceptor.GetHitCount());
+  EXPECT_NE(string::npos, post_interceptor_->GetRequests()[0].find(
+      "<app appid=\"ihfokbkgjpifnbbojhneepfflplebdkc\" version=\"1.0\">"
+      "<updatecheck /></app>"))
+      << post_interceptor_->GetRequestsAsString();
+  EXPECT_NE(string::npos, post_interceptor_->GetRequests()[1].find(
+      "<app appid=\"ihfokbkgjpifnbbojhneepfflplebdkc\" "
+      "version=\"1.0\" nextversion=\"2.0\">"
+      "<event eventtype=\"3\" eventresult=\"1\" diffresult=\"0\" "
+      "differrorcat=\"2\" differrorcode=\"16\" nextfp=\"22\"/></app>"))
+      << post_interceptor_->GetRequestsAsString();
+  EXPECT_NE(string::npos, post_interceptor_->GetRequests()[2].find(
+      "<app appid=\"ihfokbkgjpifnbbojhneepfflplebdkc\" version=\"2.0\">"
+      "<updatecheck /><packages><package fp=\"22\"/></packages></app>"))
+      << post_interceptor_->GetRequestsAsString();
 
   component_updater()->Stop();
 }
 
 // Verify that a failed installation causes an install failure ping.
 TEST_F(ComponentUpdaterTest, CheckFailedInstallPing) {
-  URLRequestPostInterceptor* post_interceptor(
-      interceptor_factory_->CreateInterceptor());
-  EXPECT_TRUE(post_interceptor != NULL);
-  EXPECT_TRUE(post_interceptor->ExpectRequest(new PartialMatch(
-      "<o:app appid=\"jebgalgnebhfojomionfpkfelancnnkf\" version=\"0.9\">"
-      "<o:event eventtype=\"3\" eventresult=\"0\" "
-      "previousversion=\"0.9\" nextversion=\"1.0\" "
-      "errorcat=\"3\" errorcode=\"9\"/></o:app>")));
-  EXPECT_TRUE(post_interceptor->ExpectRequest(new PartialMatch(
-    "<o:app appid=\"jebgalgnebhfojomionfpkfelancnnkf\" version=\"0.9\">"
-    "<o:event eventtype=\"3\" eventresult=\"0\" "
-    "previousversion=\"0.9\" nextversion=\"1.0\" "
-    "errorcat=\"3\" errorcode=\"9\"/></o:app>")));
-
-  content::URLLocalHostRequestPrepackagedInterceptor interceptor;
-
   // This test installer reports installation failure.
   class : public TestInstaller {
     virtual bool Install(const base::DictionaryValue& manifest,
@@ -1040,30 +1041,58 @@
     }
   } installer;
 
+  EXPECT_TRUE(post_interceptor_->ExpectRequest(new PartialMatch(
+      "updatecheck"), test_file("updatecheck_reply_1.xml")));
+  EXPECT_TRUE(post_interceptor_->ExpectRequest(new PartialMatch("event")));
+  EXPECT_TRUE(post_interceptor_->ExpectRequest(new PartialMatch(
+      "updatecheck"), test_file("updatecheck_reply_1.xml")));
+  EXPECT_TRUE(post_interceptor_->ExpectRequest(new PartialMatch("event")));
+  get_interceptor_->SetResponse(
+      GURL(expected_crx_url),
+      test_file("jebgalgnebhfojomionfpkfelancnnkf.crx"));
+
+  // Start with 0.9, and attempt update to 1.0.
+  // Loop twice to issue two checks: (1) with original 0.9 version
+  // and (2), which should retry with 0.9.
   CrxComponent com;
   RegisterComponent(&com, kTestComponent_jebg, Version("0.9"), &installer);
 
-  // Start with 0.9, and attempt update to 1.0
-  const GURL expected_update_url_1(
-      "http://localhost/upd?extra=foo"
-      "&x=id%3Djebgalgnebhfojomionfpkfelancnnkf%26v%3D0.9%26fp%3D%26uc");
-
-  interceptor.SetResponse(expected_update_url_1,
-                          test_file("updatecheck_reply_1.xml"));
-  interceptor.SetResponse(GURL(expected_crx_url),
-                          test_file("jebgalgnebhfojomionfpkfelancnnkf.crx"));
-
-  // Loop twice to issue two checks: (1) with original 0.9 version
-  // and (2), which should retry with 0.9.
   test_configurator()->SetLoopCount(2);
   component_updater()->Start();
   RunThreads();
 
+  EXPECT_EQ(4, post_interceptor_->GetHitCount())
+      << post_interceptor_->GetRequestsAsString();
+  EXPECT_EQ(2, get_interceptor_->GetHitCount());
+
+  EXPECT_NE(string::npos, post_interceptor_->GetRequests()[0].find(
+      "<app appid=\"jebgalgnebhfojomionfpkfelancnnkf\" version=\"0.9\">"
+      "<updatecheck /></app>"))
+      << post_interceptor_->GetRequestsAsString();
+  EXPECT_NE(string::npos, post_interceptor_->GetRequests()[1].find(
+      "<app appid=\"jebgalgnebhfojomionfpkfelancnnkf\" "
+      "version=\"0.9\" nextversion=\"1.0\">"
+      "<event eventtype=\"3\" eventresult=\"0\" "
+      "errorcat=\"3\" errorcode=\"9\"/></app>"))
+      << post_interceptor_->GetRequestsAsString();
+  EXPECT_NE(string::npos, post_interceptor_->GetRequests()[2].find(
+      "<app appid=\"jebgalgnebhfojomionfpkfelancnnkf\" version=\"0.9\">"
+      "<updatecheck /></app>"))
+      << post_interceptor_->GetRequestsAsString();
+  EXPECT_NE(string::npos, post_interceptor_->GetRequests()[3].find(
+      "<app appid=\"jebgalgnebhfojomionfpkfelancnnkf\" "
+      "version=\"0.9\" nextversion=\"1.0\">"
+      "<event eventtype=\"3\" eventresult=\"0\" "
+      "errorcat=\"3\" errorcode=\"9\"/></app>"))
+      << post_interceptor_->GetRequestsAsString();
+
   // Loop once more, but expect no ping because a noupdate response is issued.
   // This is necessary to clear out the fire-and-forget ping from the previous
   // iteration.
-  interceptor.SetResponse(expected_update_url_1,
-                          test_file("updatecheck_reply_noupdate.xml"));
+  post_interceptor_->Reset();
+  EXPECT_TRUE(post_interceptor_->ExpectRequest(new PartialMatch(
+      "updatecheck"), test_file("updatecheck_reply_noupdate.xml")));
+
   test_configurator()->SetLoopCount(1);
   component_updater()->Start();
   RunThreads();
@@ -1071,12 +1100,15 @@
   EXPECT_EQ(0, static_cast<TestInstaller*>(com.installer)->error());
   EXPECT_EQ(2, static_cast<TestInstaller*>(com.installer)->install_count());
 
-  EXPECT_EQ(2, post_interceptor->GetHitCount())
-      << post_interceptor->GetRequestsAsString();
-  EXPECT_EQ(0, post_interceptor->GetMissCount())
-      << post_interceptor->GetRequestsAsString();
+  EXPECT_EQ(1, post_interceptor_->GetHitCount())
+      << post_interceptor_->GetRequestsAsString();
+  EXPECT_EQ(1, post_interceptor_->GetCount())
+      << post_interceptor_->GetRequestsAsString();
 
-  EXPECT_EQ(5, interceptor.GetHitCount());
+  EXPECT_NE(string::npos, post_interceptor_->GetRequests()[0].find(
+      "<app appid=\"jebgalgnebhfojomionfpkfelancnnkf\" version=\"0.9\">"
+      "<updatecheck /></app>"))
+      << post_interceptor_->GetRequestsAsString();
 
   component_updater()->Stop();
 }
@@ -1085,74 +1117,73 @@
 // ihfokbkgjpifnbbojhneepfflplebdkc_1to2_bad.crx contains an incorrect
 // patching instruction that should fail.
 TEST_F(ComponentUpdaterTest, DifferentialUpdateFailErrorcode) {
-  URLRequestPostInterceptor* post_interceptor(
-      interceptor_factory_->CreateInterceptor());
-  EXPECT_TRUE(post_interceptor != NULL);
-  EXPECT_TRUE(post_interceptor->ExpectRequest(new PartialMatch(
-      "<o:app appid=\"ihfokbkgjpifnbbojhneepfflplebdkc\" version=\"1.0\">"
-      "<o:event eventtype=\"3\" eventresult=\"1\" "
-      "previousversion=\"0.0\" nextversion=\"1.0\" nextfp=\"1\"/></o:app>")));
-  EXPECT_TRUE(post_interceptor->ExpectRequest(new PartialMatch(
-      "<o:app appid=\"ihfokbkgjpifnbbojhneepfflplebdkc\" version=\"2.0\">"
-      "<o:event eventtype=\"3\" eventresult=\"1\" "
-      "previousversion=\"1.0\" nextversion=\"2.0\" "
-      "diffresult=\"0\" differrorcat=\"2\" "
-      "differrorcode=\"14\" diffextracode1=\"305\" "
-      "previousfp=\"1\" nextfp=\"f22\"/></o:app>")));
+  EXPECT_TRUE(post_interceptor_->ExpectRequest(new PartialMatch(
+      "updatecheck"), test_file("updatecheck_diff_reply_1.xml")));
+  EXPECT_TRUE(post_interceptor_->ExpectRequest(new PartialMatch("event")));
+  EXPECT_TRUE(post_interceptor_->ExpectRequest(new PartialMatch(
+      "updatecheck"), test_file("updatecheck_diff_reply_2.xml")));
+  EXPECT_TRUE(post_interceptor_->ExpectRequest(new PartialMatch("event")));
+  EXPECT_TRUE(post_interceptor_->ExpectRequest(new PartialMatch(
+      "updatecheck"), test_file("updatecheck_diff_reply_3.xml")));
 
-  content::URLLocalHostRequestPrepackagedInterceptor interceptor;
+  get_interceptor_->SetResponse(
+      GURL("http://localhost/download/ihfokbkgjpifnbbojhneepfflplebdkc_1.crx"),
+      test_file("ihfokbkgjpifnbbojhneepfflplebdkc_1.crx"));
+  // This intercept returns a different file than what is specified in the
+  // update check response and requested in the download. The file that is
+  // actually dowloaded contains a patching error, an therefore, an error
+  // is injected at the time of patching.
+  get_interceptor_->SetResponse(
+      GURL("http://localhost/download/"
+           "ihfokbkgjpifnbbojhneepfflplebdkc_1to2.crx"),
+      test_file("ihfokbkgjpifnbbojhneepfflplebdkc_1to2_bad.crx"));
+  get_interceptor_->SetResponse(
+      GURL("http://localhost/download/ihfokbkgjpifnbbojhneepfflplebdkc_2.crx"),
+      test_file("ihfokbkgjpifnbbojhneepfflplebdkc_2.crx"));
 
   VersionedTestInstaller installer;
   CrxComponent com;
   RegisterComponent(&com, kTestComponent_ihfo, Version("0.0"), &installer);
 
-  const GURL expected_update_url_0(
-      "http://localhost/upd?extra=foo"
-      "&x=id%3Dihfokbkgjpifnbbojhneepfflplebdkc%26v%3D0.0%26fp%3D%26uc");
-  const GURL expected_update_url_1(
-      "http://localhost/upd?extra=foo"
-      "&x=id%3Dihfokbkgjpifnbbojhneepfflplebdkc%26v%3D1.0%26fp%3D1%26uc");
-  const GURL expected_update_url_2(
-      "http://localhost/upd?extra=foo"
-      "&x=id%3Dihfokbkgjpifnbbojhneepfflplebdkc%26v%3D2.0%26fp%3Df22%26uc");
-  const GURL expected_crx_url_1(
-      "http://localhost/download/ihfokbkgjpifnbbojhneepfflplebdkc_1.crx");
-  const GURL expected_crx_url_1_diff_2(
-      "http://localhost/download/ihfokbkgjpifnbbojhneepfflplebdkc_1to2.crx");
-  const GURL expected_crx_url_2(
-      "http://localhost/download/ihfokbkgjpifnbbojhneepfflplebdkc_2.crx");
-
-  interceptor.SetResponse(expected_update_url_0,
-                          test_file("updatecheck_diff_reply_1.xml"));
-  interceptor.SetResponse(expected_update_url_1,
-                          test_file("updatecheck_diff_reply_2.xml"));
-  interceptor.SetResponse(expected_update_url_2,
-                          test_file("updatecheck_diff_reply_3.xml"));
-  interceptor.SetResponse(expected_crx_url_1,
-                          test_file("ihfokbkgjpifnbbojhneepfflplebdkc_1.crx"));
-  interceptor.SetResponse(
-      expected_crx_url_1_diff_2,
-      test_file("ihfokbkgjpifnbbojhneepfflplebdkc_1to2_bad.crx"));
-  interceptor.SetResponse(expected_crx_url_2,
-                          test_file("ihfokbkgjpifnbbojhneepfflplebdkc_2.crx"));
-
-  test_configurator()->SetLoopCount(2);
-
+  test_configurator()->SetLoopCount(3);
   component_updater()->Start();
   RunThreads();
   component_updater()->Stop();
-  // There may still be pings in the queue.
-  RunThreadsUntilIdle();
 
   EXPECT_EQ(0, static_cast<TestInstaller*>(com.installer)->error());
   EXPECT_EQ(2, static_cast<TestInstaller*>(com.installer)->install_count());
 
-  EXPECT_EQ(2, post_interceptor->GetHitCount())
-      << post_interceptor->GetRequestsAsString();
-  EXPECT_EQ(0, post_interceptor->GetMissCount())
-      << post_interceptor->GetRequestsAsString();
+  EXPECT_EQ(5, post_interceptor_->GetHitCount())
+      << post_interceptor_->GetRequestsAsString();
+  EXPECT_EQ(5, post_interceptor_->GetCount())
+      << post_interceptor_->GetRequestsAsString();
+  EXPECT_EQ(3, get_interceptor_->GetHitCount());
 
-  EXPECT_EQ(5, interceptor.GetHitCount());
+  EXPECT_NE(string::npos, post_interceptor_->GetRequests()[0].find(
+      "<app appid=\"ihfokbkgjpifnbbojhneepfflplebdkc\" version=\"0.0\">"
+      "<updatecheck /></app>"))
+      << post_interceptor_->GetRequestsAsString();
+  EXPECT_NE(string::npos, post_interceptor_->GetRequests()[1].find(
+      "<app appid=\"ihfokbkgjpifnbbojhneepfflplebdkc\" "
+      "version=\"0.0\" nextversion=\"1.0\">"
+      "<event eventtype=\"3\" eventresult=\"1\" nextfp=\"1\"/></app>"))
+      << post_interceptor_->GetRequestsAsString();
+  EXPECT_NE(string::npos, post_interceptor_->GetRequests()[2].find(
+      "<app appid=\"ihfokbkgjpifnbbojhneepfflplebdkc\" version=\"1.0\">"
+      "<updatecheck /><packages><package fp=\"1\"/></packages></app>"))
+      << post_interceptor_->GetRequestsAsString();
+  EXPECT_NE(string::npos, post_interceptor_->GetRequests()[3].find(
+      "<app appid=\"ihfokbkgjpifnbbojhneepfflplebdkc\" "
+      "version=\"1.0\" nextversion=\"2.0\">"
+      "<event eventtype=\"3\" eventresult=\"1\" "
+      "diffresult=\"0\" differrorcat=\"2\" "
+      "differrorcode=\"14\" diffextracode1=\"305\" "
+      "previousfp=\"1\" nextfp=\"22\"/></app>"))
+      << post_interceptor_->GetRequestsAsString();
+  EXPECT_NE(string::npos, post_interceptor_->GetRequests()[4].find(
+      "<app appid=\"ihfokbkgjpifnbbojhneepfflplebdkc\" version=\"2.0\">"
+      "<updatecheck /><packages><package fp=\"22\"/></packages></app>"))
+      << post_interceptor_->GetRequestsAsString();
 }
 
 }  // namespace component_updater
diff --git a/chrome/browser/component_updater/test/component_updater_service_unittest.h b/chrome/browser/component_updater/test/component_updater_service_unittest.h
index 6409c45..d7d4946 100644
--- a/chrome/browser/component_updater/test/component_updater_service_unittest.h
+++ b/chrome/browser/component_updater/test/component_updater_service_unittest.h
@@ -19,6 +19,7 @@
 #include "chrome/browser/component_updater/test/component_patcher_mock.h"
 #include "chrome/browser/component_updater/test/url_request_post_interceptor.h"
 #include "content/public/test/test_browser_thread_bundle.h"
+#include "content/test/net/url_request_prepackaged_interceptor.h"
 #include "net/url_request/url_request_test_util.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -27,7 +28,10 @@
 
 namespace component_updater {
 
-// Intercepts HTTP POST requests sent to |localhost2|.
+// Intercepts HTTP GET requests sent to "localhost".
+typedef content::URLLocalHostRequestPrepackagedInterceptor GetInterceptor;
+
+// Intercepts HTTP POST requests sent to "localhost2".
 class InterceptorFactory : public URLRequestPostInterceptorFactory {
  public:
   InterceptorFactory();
@@ -148,7 +152,9 @@
   void RunThreadsUntilIdle();
 
   scoped_ptr<component_updater::InterceptorFactory> interceptor_factory_;
+  URLRequestPostInterceptor* post_interceptor_;   // Owned by the factory.
 
+  scoped_ptr<GetInterceptor> get_interceptor_;
  private:
   TestConfigurator* test_config_;
   base::FilePath test_data_dir_;
diff --git a/chrome/browser/component_updater/test/update_manifest_unittest.cc b/chrome/browser/component_updater/test/update_manifest_unittest.cc
new file mode 100644
index 0000000..1583c49
--- /dev/null
+++ b/chrome/browser/component_updater/test/update_manifest_unittest.cc
@@ -0,0 +1,281 @@
+// Copyright 2013 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 "base/memory/scoped_vector.h"
+#include "chrome/browser/component_updater/update_manifest.h"
+#include "libxml/globals.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace component_updater {
+
+const char* kValidXml =
+"<?xml version='1.0' encoding='UTF-8'?>"
+"<response protocol='3.0'>"
+" <app appid='12345'>"
+"   <updatecheck status='ok'>"
+"     <urls>"
+"       <url codebase='http://example.com/'/>"
+"       <url codebasediff='http://diff.example.com/'/>"
+"     </urls>"
+"     <manifest version='1.2.3.4' prodversionmin='2.0.143.0'>"
+"       <packages>"
+"         <package name='extension_1_2_3_4.crx'/>"
+"       </packages>"
+"     </manifest>"
+"   </updatecheck>"
+" </app>"
+"</response>";
+
+const char* valid_xml_with_hash =
+"<?xml version='1.0' encoding='UTF-8'?>"
+"<response protocol='3.0'>"
+" <app appid='12345'>"
+"   <updatecheck status='ok'>"
+"     <urls>"
+"       <url codebase='http://example.com/'/>"
+"     </urls>"
+"     <manifest version='1.2.3.4' prodversionmin='2.0.143.0'>"
+"       <packages>"
+"         <package name='extension_1_2_3_4.crx' hash_sha256='1234'/>"
+"       </packages>"
+"     </manifest>"
+"   </updatecheck>"
+" </app>"
+"</response>";
+
+const char* valid_xml_with_invalid_sizes =
+"<?xml version='1.0' encoding='UTF-8'?>"
+"<response protocol='3.0'>"
+" <app appid='12345'>"
+"   <updatecheck status='ok'>"
+"     <urls>"
+"       <url codebase='http://example.com/'/>"
+"     </urls>"
+"     <manifest version='1.2.3.4' prodversionmin='2.0.143.0'>"
+"       <packages>"
+"         <package name='1' size='1234'/>"
+"         <package name='2' size='-1234'/>"
+"         <package name='3' />"
+"         <package name='4' size='-a'/>"
+"         <package name='5' size='-123467890123456789'/>"
+"         <package name='6' size='123467890123456789'/>"
+"       </packages>"
+"     </manifest>"
+"   </updatecheck>"
+" </app>"
+"</response>";
+
+const char* kMissingAppId =
+"<?xml version='1.0'?>"
+"<response protocol='3.0'>"
+" <app>"
+"  <updatecheck codebase='http://example.com/extension_1.2.3.4.crx'"
+"               version='1.2.3.4' />"
+" </app>"
+"</response>";
+
+const char* kInvalidCodebase =
+"<?xml version='1.0'?>"
+"<response protocol='3.0'>"
+" <app appid='12345' status='ok'>"
+"  <updatecheck codebase='example.com/extension_1.2.3.4.crx'"
+"               version='1.2.3.4' />"
+" </app>"
+"</response>";
+
+const char* kMissingVersion =
+"<?xml version='1.0'?>"
+"<response protocol='3.0'>"
+" <app appid='12345' status='ok'>"
+"  <updatecheck codebase='http://example.com/extension_1.2.3.4.crx' />"
+" </app>"
+"</response>";
+
+const char* kInvalidVersion =
+"<?xml version='1.0'?>"
+"<response protocol='3.0'>"
+" <app appid='12345' status='ok'>"
+"  <updatecheck codebase='http://example.com/extension_1.2.3.4.crx' "
+"               version='1.2.3.a'/>"
+" </app>"
+"</response>";
+
+// The v3 version of the protocol is not using namespaces. However, the parser
+// must be able to parse responses that include namespaces.
+const char* kUsesNamespacePrefix =
+"<?xml version='1.0' encoding='UTF-8'?>"
+"<g:response xmlns='http://www.google.com/update2/response' protocol='3.0'>"
+" <g:app appid='12345'>"
+"   <g:updatecheck status='ok'>"
+"     <g:urls>"
+"       <g:url codebase='http://example.com/'/>"
+"     </g:urls>"
+"     <g:manifest version='1.2.3.4' prodversionmin='2.0.143.0'>"
+"       <g:packages>"
+"         <g:package name='extension_1_2_3_4.crx'/>"
+"       </g:packages>"
+"     </g:manifest>"
+"   </g:updatecheck>"
+" </g:app>"
+"</g:response>";
+
+// Includes unrelated <app> tags from other xml namespaces - this should
+// not cause problems.
+const char* kSimilarTagnames =
+"<?xml version='1.0' encoding='UTF-8'?>"
+"<response xmlns:a='http://a' protocol='3.0'>"
+" <a:app appid='12345'>"
+"   <updatecheck status='ok'>"
+"     <urls>"
+"       <url codebase='http://example.com/'/>"
+"     </urls>"
+"     <manifest version='1.2.3.4' prodversionmin='2.0.143.0'>"
+"       <packages>"
+"         <package name='extension_1_2_3_4.crx'/>"
+"       </packages>"
+"     </manifest>"
+"   </updatecheck>"
+" </a:app>"
+" <b:app appid='xyz' xmlns:b='http://b'>"
+"   <updatecheck status='noupdate'/>"
+" </b:app>"
+"</response>";
+
+// Includes a <daystart> tag.
+const char* kWithDaystart =
+"<?xml version='1.0' encoding='UTF-8'?>"
+"<response protocol='3.0'>"
+" <daystart elapsed_seconds='456' />"
+" <app appid='12345'>"
+"   <updatecheck status='ok'>"
+"     <urls>"
+"       <url codebase='http://example.com/'/>"
+"     </urls>"
+"     <manifest version='1.2.3.4' prodversionmin='2.0.143.0'>"
+"       <packages>"
+"         <package name='extension_1_2_3_4.crx'/>"
+"       </packages>"
+"     </manifest>"
+"   </updatecheck>"
+" </app>"
+"</response>";
+
+// Indicates no updates available - this should not be a parse error.
+const char* kNoUpdate =
+"<?xml version='1.0' encoding='UTF-8'?>"
+"<response protocol='3.0'>"
+" <app appid='12345'>"
+"  <updatecheck status='noupdate' />"
+" </app>"
+"</response>";
+
+// Includes two <app> tags, one with an error.
+const char* kTwoAppsOneError =
+"<?xml version='1.0' encoding='UTF-8'?>"
+"<response protocol='3.0'>"
+" <app appid='aaaaaaaa' status='error-unknownApplication'>"
+"  <updatecheck status='error-unknownapplication'/>"
+" </app>"
+" <app appid='bbbbbbbb'>"
+"   <updatecheck status='ok'>"
+"     <urls>"
+"       <url codebase='http://example.com/'/>"
+"     </urls>"
+"     <manifest version='1.2.3.4' prodversionmin='2.0.143.0'>"
+"       <packages>"
+"         <package name='extension_1_2_3_4.crx'/>"
+"       </packages>"
+"     </manifest>"
+"   </updatecheck>"
+" </app>"
+"</response>";
+
+TEST(ComponentUpdaterManifestTest, TestUpdateManifest) {
+  UpdateManifest parser;
+
+  // Test parsing of a number of invalid xml cases
+  EXPECT_FALSE(parser.Parse(std::string()));
+  EXPECT_FALSE(parser.errors().empty());
+
+  EXPECT_TRUE(parser.Parse(kMissingAppId));
+  EXPECT_TRUE(parser.results().list.empty());
+  EXPECT_FALSE(parser.errors().empty());
+
+  EXPECT_TRUE(parser.Parse(kInvalidCodebase));
+  EXPECT_TRUE(parser.results().list.empty());
+  EXPECT_FALSE(parser.errors().empty());
+
+  EXPECT_TRUE(parser.Parse(kMissingVersion));
+  EXPECT_TRUE(parser.results().list.empty());
+  EXPECT_FALSE(parser.errors().empty());
+
+  EXPECT_TRUE(parser.Parse(kInvalidVersion));
+  EXPECT_TRUE(parser.results().list.empty());
+  EXPECT_FALSE(parser.errors().empty());
+
+  // Parse some valid XML, and check that all params came out as expected
+  EXPECT_TRUE(parser.Parse(kValidXml));
+  EXPECT_TRUE(parser.errors().empty());
+  EXPECT_EQ(1u, parser.results().list.size());
+  const UpdateManifest::Result* firstResult = &parser.results().list[0];
+  EXPECT_EQ(1u, firstResult->crx_urls.size());
+  EXPECT_EQ(GURL("http://example.com/"), firstResult->crx_urls[0]);
+  EXPECT_EQ(GURL("http://diff.example.com/"), firstResult->crx_diffurls[0]);
+  EXPECT_EQ("1.2.3.4", firstResult->manifest.version);
+  EXPECT_EQ("2.0.143.0", firstResult->manifest.browser_min_version);
+  EXPECT_EQ(1u, firstResult->manifest.packages.size());
+  EXPECT_EQ("extension_1_2_3_4.crx", firstResult->manifest.packages[0].name);
+
+  // Parse some xml that uses namespace prefixes.
+  EXPECT_TRUE(parser.Parse(kUsesNamespacePrefix));
+  EXPECT_TRUE(parser.errors().empty());
+  EXPECT_TRUE(parser.Parse(kSimilarTagnames));
+  EXPECT_TRUE(parser.errors().empty());
+  xmlCleanupGlobals();
+
+  // Parse xml with hash value
+  EXPECT_TRUE(parser.Parse(valid_xml_with_hash));
+  EXPECT_TRUE(parser.errors().empty());
+  EXPECT_FALSE(parser.results().list.empty());
+  firstResult = &parser.results().list[0];
+  EXPECT_FALSE(firstResult->manifest.packages.empty());
+  EXPECT_EQ("1234", firstResult->manifest.packages[0].hash_sha256);
+
+  // Parse xml with package size value
+  EXPECT_TRUE(parser.Parse(valid_xml_with_invalid_sizes));
+  EXPECT_TRUE(parser.errors().empty());
+  EXPECT_FALSE(parser.results().list.empty());
+  firstResult = &parser.results().list[0];
+  EXPECT_FALSE(firstResult->manifest.packages.empty());
+  EXPECT_EQ(1234, firstResult->manifest.packages[0].size);
+  EXPECT_EQ(-1234, firstResult->manifest.packages[1].size);
+  EXPECT_EQ(0, firstResult->manifest.packages[2].size);
+  EXPECT_EQ(0, firstResult->manifest.packages[3].size);
+  EXPECT_EQ(0, firstResult->manifest.packages[4].size);
+  EXPECT_EQ(0, firstResult->manifest.packages[5].size);
+
+  // Parse xml with a <daystart> element.
+  EXPECT_TRUE(parser.Parse(kWithDaystart));
+  EXPECT_TRUE(parser.errors().empty());
+  EXPECT_FALSE(parser.results().list.empty());
+  EXPECT_EQ(parser.results().daystart_elapsed_seconds, 456);
+
+  // Parse a no-update response.
+  EXPECT_TRUE(parser.Parse(kNoUpdate));
+  EXPECT_TRUE(parser.errors().empty());
+  EXPECT_FALSE(parser.results().list.empty());
+  firstResult = &parser.results().list[0];
+  EXPECT_EQ(firstResult->extension_id, "12345");
+  EXPECT_EQ(firstResult->manifest.version, "");
+
+  // Parse xml with one error and one success <app> tag.
+  EXPECT_TRUE(parser.Parse(kTwoAppsOneError));
+  EXPECT_FALSE(parser.errors().empty());
+  EXPECT_EQ(1u, parser.results().list.size());
+  firstResult = &parser.results().list[0];
+  EXPECT_EQ(firstResult->extension_id, "bbbbbbbb");
+}
+
+}  // namespace component_updater
+
diff --git a/chrome/browser/component_updater/test/url_request_post_interceptor.cc b/chrome/browser/component_updater/test/url_request_post_interceptor.cc
index 3a511f1..2f0f9926 100644
--- a/chrome/browser/component_updater/test/url_request_post_interceptor.cc
+++ b/chrome/browser/component_updater/test/url_request_post_interceptor.cc
@@ -18,6 +18,7 @@
 
 namespace component_updater {
 
+// Returns a canned response.
 class URLRequestMockJob : public net::URLRequestSimpleJob {
  public:
   URLRequestMockJob(net::URLRequest* request,
@@ -27,6 +28,10 @@
         response_(response) {}
 
  protected:
+  virtual int GetResponseCode() const OVERRIDE {
+    return 200;
+  }
+
   virtual int GetData(std::string* mime_type,
                       std::string* charset,
                       std::string* data,
@@ -45,9 +50,14 @@
 };
 
 URLRequestPostInterceptor::URLRequestPostInterceptor(const GURL& url)
-    : url_(url), hit_count_(0), miss_count_(0) {}
+    : url_(url), hit_count_(0) {}
 
 URLRequestPostInterceptor::~URLRequestPostInterceptor() {
+  CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+  ClearExpectations();
+}
+
+void URLRequestPostInterceptor::ClearExpectations() {
   while (!expectations_.empty()) {
     Expectation expectation(expectations_.front());
     delete expectation.first;
@@ -67,10 +77,9 @@
 
 bool URLRequestPostInterceptor::ExpectRequest(
     class RequestMatcher* request_matcher,
-    const std::string& filepath) {
+    const base::FilePath& filepath) {
   std::string response;
-  const base::FilePath path(base::FilePath().AppendASCII(filepath));
-  if (filepath.empty() || !base::ReadFileToString(path, &response))
+  if (filepath.empty() || !base::ReadFileToString(filepath, &response))
     return false;
   expectations_.push(std::make_pair(request_matcher, response));
   return true;
@@ -81,9 +90,9 @@
   return hit_count_;
 }
 
-int URLRequestPostInterceptor::GetMissCount() const {
+int URLRequestPostInterceptor::GetCount() const {
   base::AutoLock auto_lock(interceptor_lock_);
-  return requests_.size() - hit_count_;
+  return static_cast<int>(requests_.size());
 }
 
 std::vector<std::string>
@@ -106,6 +115,13 @@
   return s;
 }
 
+void URLRequestPostInterceptor::Reset() {
+  base::AutoLock auto_lock(interceptor_lock_);
+  hit_count_ = 0;
+  requests_.clear();
+  ClearExpectations();
+}
+
 
 class URLRequestPostInterceptor::Delegate
     : public net::URLRequestJobFactory::ProtocolHandler {
@@ -180,13 +196,12 @@
       const URLRequestPostInterceptor::Expectation& expectation(
           interceptor->expectations_.front());
       if (expectation.first->Match(request_body)) {
+        const std::string response(expectation.second);
         delete expectation.first;
         interceptor->expectations_.pop();
         ++interceptor->hit_count_;
 
-        return new URLRequestMockJob(request,
-                                     network_delegate,
-                                     expectation.second);
+        return new URLRequestMockJob(request, network_delegate, response);
       }
     }
 
@@ -222,11 +237,11 @@
 }
 
 URLRequestPostInterceptor* URLRequestPostInterceptorFactory::CreateInterceptor(
-    const std::string& file_path) {
+    const base::FilePath& filepath) {
   const GURL base_url(base::StringPrintf("%s://%s",
                                          scheme_.c_str(),
                                          hostname_.c_str()));
-  GURL absolute_url(base_url.Resolve(file_path));
+  GURL absolute_url(base_url.Resolve(filepath.MaybeAsASCII()));
   URLRequestPostInterceptor* interceptor(
       new URLRequestPostInterceptor(absolute_url));
   bool res = BrowserThread::PostTask(
diff --git a/chrome/browser/component_updater/test/url_request_post_interceptor.h b/chrome/browser/component_updater/test/url_request_post_interceptor.h
index ca0f2d15..c7b4b2a 100644
--- a/chrome/browser/component_updater/test/url_request_post_interceptor.h
+++ b/chrome/browser/component_updater/test/url_request_post_interceptor.h
@@ -11,10 +11,13 @@
 #include <utility>
 #include <vector>
 #include "base/basictypes.h"
-#include "base/path_service.h"
 #include "base/synchronization/lock.h"
 #include "url/gurl.h"
 
+namespace base {
+class FilePath;
+}
+
 namespace net {
 class URLRequest;
 }
@@ -45,15 +48,14 @@
   // |request_matcher| object. Returns |true| if the expectation was set.
   bool ExpectRequest(class RequestMatcher* request_matcher);
   bool ExpectRequest(class RequestMatcher* request_matcher,
-                     const std::string& filepath);
+                     const base::FilePath& filepath);
 
   // Returns how many requests have been intercepted and matched by
   // an expectation. One expectation can only be matched by one request.
   int GetHitCount() const;
 
-  // Returns how many requests have been intercepted but not matched by
-  // any expectation.
-  int GetMissCount() const;
+  // Returns how many requests in total have been captured by the interceptor.
+  int GetCount() const;
 
   // Returns all requests that have been intercepted, matched or not.
   std::vector<std::string> GetRequests() const;
@@ -61,6 +63,9 @@
   // Returns all requests as a string for debugging purposes.
   std::string GetRequestsAsString() const;
 
+  // Resets the state of the interceptor so that new expectations can be set.
+  void Reset();
+
   class Delegate;
 
  private:
@@ -70,11 +75,11 @@
   explicit URLRequestPostInterceptor(const GURL& url);
   ~URLRequestPostInterceptor();
 
+  void ClearExpectations();
   const GURL url_;
 
   mutable base::Lock interceptor_lock_;
   mutable int hit_count_;
-  mutable int miss_count_;
   mutable std::vector<std::string> requests_;
   mutable std::queue<Expectation> expectations_;
 
@@ -90,7 +95,7 @@
   // Creates an interceptor object for the specified url path. Returns NULL
   // in case of errors or a valid interceptor object otherwise. The caller
   // does not own the returned object.
-  URLRequestPostInterceptor* CreateInterceptor(const std::string& file_path);
+  URLRequestPostInterceptor* CreateInterceptor(const base::FilePath& filepath);
 
  private:
   const std::string scheme_;
diff --git a/chrome/browser/component_updater/update_manifest.cc b/chrome/browser/component_updater/update_manifest.cc
new file mode 100644
index 0000000..f9e1316
--- /dev/null
+++ b/chrome/browser/component_updater/update_manifest.cc
@@ -0,0 +1,338 @@
+// Copyright 2013 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/component_updater/update_manifest.h"
+#include <algorithm>
+#include "base/memory/scoped_ptr.h"
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/version.h"
+#include "libxml/tree.h"
+#include "third_party/libxml/chromium/libxml_utils.h"
+
+namespace component_updater {
+
+static const char* kExpectedResponseProtocol = "3.0";
+
+UpdateManifest::UpdateManifest() {}
+UpdateManifest::~UpdateManifest() {}
+
+UpdateManifest::Results::Results() : daystart_elapsed_seconds(kNoDaystart) {}
+UpdateManifest::Results::~Results() {}
+
+UpdateManifest::Result::Result() {}
+
+UpdateManifest::Result::~Result() {}
+
+UpdateManifest::Result::Manifest::Manifest() {}
+UpdateManifest::Result::Manifest::~Manifest() {}
+
+UpdateManifest::Result::Manifest::Package::Package() : size(0), sizediff(0) {}
+UpdateManifest::Result::Manifest::Package::~Package() {}
+
+void UpdateManifest::ParseError(const char* details, ...) {
+  va_list args;
+  va_start(args, details);
+
+  if (!errors_.empty()) {
+    errors_ += "\r\n";
+  }
+
+  base::StringAppendV(&errors_, details, args);
+  va_end(args);
+}
+
+// Checks whether a given node's name matches |expected_name|.
+static bool TagNameEquals(const xmlNode* node, const char* expected_name) {
+  return 0 == strcmp(expected_name, reinterpret_cast<const char*>(node->name));
+}
+
+// Returns child nodes of |root| with name |name|.
+static std::vector<xmlNode*> GetChildren(xmlNode* root, const char* name) {
+  std::vector<xmlNode*> result;
+  for (xmlNode* child = root->children; child != NULL; child = child->next) {
+    if (!TagNameEquals(child, name)) {
+      continue;
+    }
+    result.push_back(child);
+  }
+  return result;
+}
+
+// Returns the value of a named attribute, or the empty string.
+static std::string GetAttribute(xmlNode* node, const char* attribute_name) {
+  const xmlChar* name = reinterpret_cast<const xmlChar*>(attribute_name);
+  for (xmlAttr* attr = node->properties; attr != NULL; attr = attr->next) {
+    if (!xmlStrcmp(attr->name, name) && attr->children &&
+        attr->children->content) {
+      return std::string(reinterpret_cast<const char*>(
+          attr->children->content));
+    }
+  }
+  return std::string();
+}
+
+// This is used for the xml parser to report errors. This assumes the context
+// is a pointer to a std::string where the error message should be appended.
+static void XmlErrorFunc(void *context, const char *message, ...) {
+  va_list args;
+  va_start(args, message);
+  std::string* error = static_cast<std::string*>(context);
+  base::StringAppendV(error, message, args);
+  va_end(args);
+}
+
+// Utility class for cleaning up the xml document when leaving a scope.
+class ScopedXmlDocument {
+ public:
+  explicit ScopedXmlDocument(xmlDocPtr document) : document_(document) {}
+  ~ScopedXmlDocument() {
+    if (document_)
+      xmlFreeDoc(document_);
+  }
+
+  xmlDocPtr get() {
+    return document_;
+  }
+
+ private:
+  xmlDocPtr document_;
+};
+
+// Parses the <package> tag.
+bool ParsePackageTag(xmlNode* package,
+                     UpdateManifest::Result* result,
+                     std::string* error) {
+  UpdateManifest::Result::Manifest::Package p;
+  p.name = GetAttribute(package, "name");
+  if (p.name.empty()) {
+    *error = "Missing name for package.";
+    return false;
+  }
+
+  p.namediff = GetAttribute(package, "namediff");
+
+  // package_fingerprint is optional. It identifies the package, preferably
+  // with a modified sha256 hash of the package in hex format.
+  p.fingerprint = GetAttribute(package, "fp");
+
+  p.hash_sha256 = GetAttribute(package, "hash_sha256");
+  int size = 0;
+  if (base::StringToInt(GetAttribute(package, "size"), &size)) {
+    p.size = size;
+  }
+
+  p.hashdiff_sha256 = GetAttribute(package, "hashdiff_sha256");
+  int sizediff = 0;
+  if (base::StringToInt(GetAttribute(package, "sizediff"), &sizediff)) {
+    p.sizediff = sizediff;
+  }
+
+  result->manifest.packages.push_back(p);
+
+  return true;
+}
+
+// Parses the <manifest> tag.
+bool ParseManifestTag(xmlNode* manifest,
+                      UpdateManifest::Result* result,
+                      std::string* error) {
+  // Get the version.
+  result->manifest.version = GetAttribute(manifest, "version");
+  if (result->manifest.version.empty()) {
+    *error = "Missing version for manifest.";
+    return false;
+  }
+  Version version(result->manifest.version);
+  if (!version.IsValid()) {
+    *error = "Invalid version: '";
+    *error += result->manifest.version;
+    *error += "'.";
+    return false;
+  }
+
+  // Get the minimum browser version (not required).
+  result->manifest.browser_min_version =
+      GetAttribute(manifest, "prodversionmin");
+  if (result->manifest.browser_min_version.length()) {
+    Version browser_min_version(result->manifest.browser_min_version);
+    if (!browser_min_version.IsValid()) {
+      *error = "Invalid prodversionmin: '";
+      *error += result->manifest.browser_min_version;
+      *error += "'.";
+      return false;
+    }
+  }
+
+  // Get the <packages> node.
+  std::vector<xmlNode*> packages = GetChildren(manifest, "packages");
+  if (packages.empty()) {
+    *error = "Missing packages tag on manifest.";
+    return false;
+  }
+
+  // Parse each of the <package> tags.
+  std::vector<xmlNode*> package = GetChildren(packages[0], "package");
+  for (size_t i = 0; i != package.size(); ++i) {
+    if (!ParsePackageTag(package[i], result, error))
+      return false;
+  }
+
+  return true;
+}
+
+// Parses the <urls> tag and its children in the <updatecheck>.
+bool ParseUrlsTag(xmlNode* urls,
+                  UpdateManifest::Result* result,
+                  std::string* error) {
+  // Get the url nodes.
+  std::vector<xmlNode*> url = GetChildren(urls, "url");
+  if (url.empty()) {
+    *error = "Missing url tags on urls.";
+    return false;
+  }
+
+  // Get the list of urls for full and optionally, for diff updates.
+  // There can only be either a codebase or a codebasediff attribute in a tag.
+  for (size_t i = 0; i != url.size(); ++i) {
+    // Find the url to the crx file.
+    const GURL crx_url(GetAttribute(url[i], "codebase"));
+    if (crx_url.is_valid()) {
+      result->crx_urls.push_back(crx_url);
+      continue;
+    }
+    const GURL crx_diffurl(GetAttribute(url[i], "codebasediff"));
+    if (crx_diffurl.is_valid()) {
+      result->crx_diffurls.push_back(crx_diffurl);
+      continue;
+    }
+  }
+
+  // Expect at least one url for full update.
+  if (result->crx_urls.empty()) {
+    *error = "Missing valid url for full update.";
+    return false;
+  }
+
+  return true;
+}
+
+// Parses the <updatecheck> tag.
+bool ParseUpdateCheckTag(xmlNode* updatecheck,
+                         UpdateManifest::Result* result,
+                         std::string* error) {
+  if (GetAttribute(updatecheck, "status") == "noupdate") {
+    return true;
+  }
+
+  // Get the <urls> tag.
+  std::vector<xmlNode*> urls = GetChildren(updatecheck, "urls");
+  if (urls.empty()) {
+    *error = "Missing urls on updatecheck.";
+    return false;
+  }
+
+  if (!ParseUrlsTag(urls[0], result, error)) {
+    return false;
+  }
+
+  std::vector<xmlNode*> manifests = GetChildren(updatecheck, "manifest");
+  if (urls.empty()) {
+    *error = "Missing urls on updatecheck.";
+    return false;
+  }
+
+  return ParseManifestTag(manifests[0], result, error);
+}
+
+// Parses a single <app> tag.
+bool ParseAppTag(xmlNode* app,
+                 UpdateManifest::Result* result,
+                 std::string* error) {
+  // Read the crx id.
+  result->extension_id = GetAttribute(app, "appid");
+  if (result->extension_id.empty()) {
+    *error = "Missing appid on app node";
+    return false;
+  }
+
+  // Get the <updatecheck> tag.
+  std::vector<xmlNode*> updates = GetChildren(app, "updatecheck");
+  if (updates.empty()) {
+    *error = "Missing updatecheck on app.";
+    return false;
+  }
+
+  return ParseUpdateCheckTag(updates[0], result, error);
+}
+
+bool UpdateManifest::Parse(const std::string& manifest_xml) {
+  results_.daystart_elapsed_seconds = kNoDaystart;
+  results_.list.clear();
+  errors_.clear();
+
+  if (manifest_xml.length() < 1) {
+    ParseError("Empty xml");
+    return false;
+  }
+
+  std::string xml_errors;
+  ScopedXmlErrorFunc error_func(&xml_errors, &XmlErrorFunc);
+
+  // Start up the xml parser with the manifest_xml contents.
+  ScopedXmlDocument document(xmlParseDoc(
+      reinterpret_cast<const xmlChar*>(manifest_xml.c_str())));
+  if (!document.get()) {
+    ParseError("%s", xml_errors.c_str());
+    return false;
+  }
+
+  xmlNode* root = xmlDocGetRootElement(document.get());
+  if (!root) {
+    ParseError("Missing root node");
+    return false;
+  }
+
+  if (!TagNameEquals(root, "response")) {
+    ParseError("Missing response tag");
+    return false;
+  }
+
+  // Check for the response "protocol" attribute.
+  if (GetAttribute(root, "protocol") != kExpectedResponseProtocol) {
+    ParseError("Missing/incorrect protocol on response tag "
+        "(expected '%s')", kExpectedResponseProtocol);
+    return false;
+  }
+
+  // Parse the first <daystart> if it is present.
+  std::vector<xmlNode*> daystarts = GetChildren(root, "daystart");
+  if (!daystarts.empty()) {
+    xmlNode* first = daystarts[0];
+    std::string elapsed_seconds = GetAttribute(first, "elapsed_seconds");
+    int parsed_elapsed = kNoDaystart;
+    if (base::StringToInt(elapsed_seconds, &parsed_elapsed)) {
+      results_.daystart_elapsed_seconds = parsed_elapsed;
+    }
+  }
+
+  // Parse each of the <app> tags.
+  std::vector<xmlNode*> apps = GetChildren(root, "app");
+  for (size_t i = 0; i != apps.size(); ++i) {
+    Result result;
+    std::string error;
+    if (ParseAppTag(apps[i], &result, &error)) {
+      results_.list.push_back(result);
+    } else {
+      ParseError("%s", error.c_str());
+    }
+  }
+
+  return true;
+}
+
+}  // namespace component_updater
+
diff --git a/chrome/browser/component_updater/update_manifest.h b/chrome/browser/component_updater/update_manifest.h
new file mode 100644
index 0000000..109d60a
--- /dev/null
+++ b/chrome/browser/component_updater/update_manifest.h
@@ -0,0 +1,134 @@
+// Copyright 2013 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.
+
+#ifndef CHROME_BROWSER_COMPONENT_UPDATER_UPDATE_MANIFEST_H_
+#define CHROME_BROWSER_COMPONENT_UPDATER_UPDATE_MANIFEST_H_
+
+#include <string>
+#include <vector>
+#include "base/basictypes.h"
+#include "url/gurl.h"
+
+namespace component_updater {
+
+// Parses responses for the update protocol version 3.
+// (http://code.google.com/p/omaha/wiki/ServerProtocol)
+//
+// An update manifest looks like this:
+//
+// <?xml version="1.0" encoding="UTF-8"?>
+//  <response protocol="3.0" server="prod">
+//    <daystart elapsed_seconds="56508"/>
+//    <app appid="{430FD4D0-B729-4F61-AA34-91526481799D}" status="ok">
+//      <updatecheck status="noupdate"/>
+//      <ping status="ok"/>
+//    </app>
+//    <app appid="{D0AB2EBC-931B-4013-9FEB-C9C4C2225C8C}" status="ok">
+//      <updatecheck status="ok">
+//        <urls>
+//          <url codebase="http://host/edgedl/chrome/install/782.112/"
+//          <url codebasediff="http://fallback/chrome/diff/782.112/"/>
+//        </urls>
+//        <manifest version="13.0.782.112" prodversionmin="2.0.143.0">
+//          <packages>
+//            <package name="component.crx"
+//                     namediff="diff_1.2.3.4.crx"
+//                     fp="1.123"
+//                     hash_sha256="9830b4245c4..." size="23963192"
+//                     hashdiff_sha256="cfb6caf3d0..." sizediff="101"/>
+//          </packages>
+//        </manifest>
+//      </updatecheck>
+//      <ping status="ok"/>
+//    </app>
+//  </response>
+//
+// The <daystart> tag contains a "elapsed_seconds" attribute which refers to
+// the server's notion of how many seconds it has been since midnight.
+//
+// The "appid" attribute of the <app> tag refers to the unique id of the
+// extension. The "codebase" attribute of the <updatecheck> tag is the url to
+// fetch the updated crx file, and the "prodversionmin" attribute refers to
+// the minimum version of the chrome browser that the update applies to.
+//
+// The diff data members correspond to the differential update package, if
+// a differential update is specified in the response.
+class UpdateManifest {
+ public:
+  // The result of parsing one <app> tag in an xml update check manifest.
+  struct Result {
+    struct Manifest {
+      struct Package {
+        Package();
+        ~Package();
+
+        std::string fingerprint;
+
+        // Attributes for the full update.
+        std::string name;
+        std::string hash_sha256;
+        int size;
+
+        // Attributes for the differential update.
+        std::string namediff;
+        std::string hashdiff_sha256;
+        int sizediff;
+      };
+
+      Manifest();
+      ~Manifest();
+
+      std::string version;
+      std::string browser_min_version;
+      std::vector<Package> packages;
+    };
+
+    Result();
+    ~Result();
+
+    std::string extension_id;
+
+    // The list of fallback urls, for full and diff updates respectively.
+    std::vector<GURL> crx_urls;
+    std::vector<GURL> crx_diffurls;
+
+    Manifest manifest;
+  };
+
+  static const int kNoDaystart = -1;
+  struct Results {
+    Results();
+    ~Results();
+
+    // This will be >= 0, or kNoDaystart if the <daystart> tag was not present.
+    int daystart_elapsed_seconds;
+    std::vector<Result> list;
+  };
+
+  UpdateManifest();
+  ~UpdateManifest();
+
+  // Parses an update manifest xml string into Result data. Returns a bool
+  // indicating success or failure. On success, the results are available by
+  // calling results(). The details for any failures are available by calling
+  // errors().
+  bool Parse(const std::string& manifest_xml);
+
+  const Results& results() const { return results_; }
+  const std::string& errors() const { return errors_; }
+
+ private:
+  Results results_;
+  std::string errors_;
+
+  // Adds parse error details to |errors_| string.
+  void ParseError(const char* details, ...);
+
+  DISALLOW_COPY_AND_ASSIGN(UpdateManifest);
+};
+
+}  // namespace component_updater
+
+#endif  // CHROME_BROWSER_COMPONENT_UPDATER_UPDATE_MANIFEST_H_
+