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

Unified Diff: chrome/android/java/src/org/chromium/chrome/browser/omaha/ResponseParser.java

Issue 67313003: Upstream the updated Java Omaha XML parser (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 7 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/android/java/src/org/chromium/chrome/browser/omaha/ResponseParser.java
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omaha/ResponseParser.java b/chrome/android/java/src/org/chromium/chrome/browser/omaha/ResponseParser.java
new file mode 100644
index 0000000000000000000000000000000000000000..04d6fd647989594f33d7bbb87c99c0fe8fe816c5
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omaha/ResponseParser.java
@@ -0,0 +1,342 @@
+// 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.
+
+package org.chromium.chrome.browser.omaha;
+
+import android.text.TextUtils;
+import android.util.Log;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Stack;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+/**
+ * Parses XML responses from the Omaha Update Server.
+ *
+ * Expects XML formatted like:
+ * <?xml version="1.0" encoding="UTF-8"?>
+ * <response protocol="3.0" server="prod">
+ * <daystart elapsed_seconds="65524"/>
+ * <app appid="{appid}" status="ok">
+ * <updatecheck status="ok">
+ * <urls>
+ * <url codebase="https://market.android.com/details?id=com.google.android.apps.chrome/"/>
+ * </urls>
+ * <manifest version="0.16.4130.199">
+ * <packages>
+ * <package hash="0" name="dummy.apk" required="true" size="0"/>
+ * </packages>
+ * <actions>
+ * <action event="install" run="dummy.apk"/>
+ * <action event="postinstall"/>
+ * </actions>
+ * </manifest>
+ * </updatecheck>
+ * <ping status="ok"/>
+ * </app>
+ * </response>
+ *
+ * The appid is dependent on the version of Clank that we're using.
+ */
+public class ResponseParser extends DefaultHandler {
+ private static final String TAG = "ResponseParser";
+
+ private static final class Node {
+ public final String tag;
+ public final Map<String, String> attributes;
+ public final List<Node> children;
+
+ public Node(String tagName) {
+ tag = tagName;
+ attributes = new HashMap<String, String>();
+ children = new ArrayList<Node>();
+ }
+ }
+
+ // Tags that we care to parse from the response.
+ private static final String TAG_APP = "app";
+ private static final String TAG_DAYSTART = "daystart";
+ private static final String TAG_EVENT = "event";
+ private static final String TAG_MANIFEST = "manifest";
+ private static final String TAG_PING = "ping";
+ private static final String TAG_RESPONSE = "response";
+ private static final String TAG_UPDATECHECK = "updatecheck";
+ private static final String TAG_URL = "url";
+ private static final String TAG_URLS = "urls";
+
+ private final Node mRootNode;
+ private final Stack<Node> mTagStack;
+
+ private final String mAppId;
+ private final boolean mSentInstallEvent;
+
+ private Integer mDaystartSeconds;
+ private String mMarketVersion;
nyquist 2013/11/11 17:43:41 mPlayVersion ?
gone 2013/11/11 18:22:34 Done.
+ private String mUrl;
+ private String mAppStatus;
+ private String mUpdateStatus;
+
+ private boolean mParsedInstallEvent;
+ private boolean mParsedPing;
+ private boolean mParsedUpdateCheck;
+
+ public ResponseParser(String serverResponse, String appId, boolean sentInstallEvent)
+ throws RequestFailureException {
+ mRootNode = new Node(null);
+ mTagStack = new Stack<Node>();
+ mTagStack.push(mRootNode);
+
+ mAppId = appId;
+ mSentInstallEvent = sentInstallEvent;
+
+ try {
+ SAXParserFactory factory = SAXParserFactory.newInstance();
+ SAXParser saxParser = factory.newSAXParser();
+ saxParser.parse(new InputSource(new StringReader(serverResponse)), this);
+ } catch (IOException e) {
+ throw new RequestFailureException("Hit IOException", e);
+ } catch (ParserConfigurationException e) {
+ throw new RequestFailureException("Hit ParserConfigurationException", e);
+ } catch (SAXParseException e) {
+ throw new RequestFailureException("Hit SAXParseException", e);
+ } catch (SAXException e) {
+ throw new RequestFailureException("Hit SAXException", e);
+ }
+
+ if (mTagStack.peek() != mRootNode) {
+ throw new RequestFailureException("XML was malformed.");
+ }
+
+ parseRootNode();
+ }
+
+ public int getDaystartSeconds() {
waffles 2013/11/11 17:34:27 I'm curious about how Chrome on these platforms us
gone 2013/11/11 18:22:34 We actually used to use it to send a single ping p
+ return mDaystartSeconds;
+ }
+
+ public String getMarketVersion() {
+ return mMarketVersion;
+ }
+
+ public String getURL() {
+ return mUrl;
+ }
+
+ public String getUpdateStatus() {
+ return mUpdateStatus;
+ }
+
+ @Override
+ public void startElement(String uri, String localName, String qName, Attributes attributes)
+ throws SAXException {
+ if (mTagStack.empty()) {
+ throw new SAXException("Tag stack is empty when it shouldn't be.");
+ }
+
+ Node currentNode = new Node(qName);
+ mTagStack.peek().children.add(currentNode);
+ mTagStack.push(currentNode);
+
+ for (int i = 0; i < attributes.getLength(); ++i) {
+ String attributeName = attributes.getLocalName(i);
+ String attributeValue = attributes.getValue(attributeName);
+ currentNode.attributes.put(attributeName, attributeValue);
+ }
+ }
+
+ @Override
+ public void endElement(String uri, String localName, String qName) throws SAXException {
+ if (mTagStack.empty()) {
+ throw new SAXException("Tried closing empty stack with " + qName);
+ } else if (!TextUtils.equals(qName, mTagStack.peek().tag)) {
+ throw new SAXException("Tried closing " + mTagStack.peek().tag + " with " + qName);
+ }
+ mTagStack.pop();
+ }
+
+ private void resetParsedData() {
+ mDaystartSeconds = null;
+ mMarketVersion = null;
+ mUrl = null;
+ mUpdateStatus = null;
+ mAppStatus = null;
+
+ mParsedInstallEvent = false;
+ mParsedPing = false;
+ mParsedUpdateCheck = false;
+ }
+
+ private void parseRootNode() throws RequestFailureException {
+ for (int i = 0; i < mRootNode.children.size(); ++i) {
+ if (TextUtils.equals(TAG_RESPONSE, mRootNode.children.get(i).tag)) {
+ boolean success = parseResponseNode(mRootNode.children.get(i));
+ if (mSentInstallEvent) {
+ success &= mParsedInstallEvent && !mParsedPing && !mParsedUpdateCheck;
+ } else {
+ success &= !mParsedInstallEvent && mParsedPing && mParsedUpdateCheck;
+ }
waffles 2013/11/11 17:34:27 Why require that a response that contains an insta
gone 2013/11/11 18:22:34 It's just the way our client works... We don't tr
waffles 2013/11/11 18:48:40 From the server side, it is equivalent to bundle t
+
+ if (success) return;
+ resetParsedData();
+ }
+ }
+
+ throw new RequestFailureException("Failed to parse XML root node.");
+ }
+
+ private boolean parseResponseNode(Node node) {
+ boolean success = true;
+ success &= TextUtils.equals("prod", node.attributes.get("server"));
+ success &= TextUtils.equals("3.0", node.attributes.get("protocol"));
gone 2013/11/08 22:05:32 How long will server protocol 3.0 be handled for?
nyquist 2013/11/11 17:43:41 And also, what is the expected client behavior whe
gone 2013/11/11 18:22:34 Could we just assume that the server got the reque
waffles 2013/11/11 18:48:40 The server is obligated to respond in protocol 3.0
+
+ for (int i = 0; i < node.children.size(); ++i) {
+ Node current = node.children.get(i);
+ if (TextUtils.equals(TAG_DAYSTART, current.tag)) {
+ success &= parseDaystartNode(current);
+ } else if (TextUtils.equals(TAG_APP, current.tag)) {
+ success &= parseAppNode(current);
+ } else {
+ Log.w(TAG, "Ignoring unknown child of <" + node.tag + "> : " + current.tag);
+ }
+ }
+
+ success &= mDaystartSeconds != null;
+
+ if (!success) Log.e(TAG, "Failed to parse: " + node.tag);
+ return success;
+ }
+
+ private boolean parseDaystartNode(Node node) {
+ try {
+ mDaystartSeconds = Integer.parseInt(node.attributes.get("elapsed_seconds"));
+ return true;
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "Failed to parse: " + node.tag);
+ return false;
+ }
+ }
+
+ private boolean parseAppNode(Node node) {
+ boolean success = true;
+ success &= TextUtils.equals(mAppId, node.attributes.get("appid"));
+
+ mAppStatus = node.attributes.get("status");
+ if (TextUtils.equals("ok", mAppStatus)) {
+ for (int i = 0; i < node.children.size(); ++i) {
+ Node current = node.children.get(i);
+ if (TextUtils.equals(TAG_UPDATECHECK, current.tag)) {
+ success &= parseUpdatecheck(current);
+ } else if (TextUtils.equals(TAG_EVENT, current.tag)) {
+ success &= parseEvent(current);
+ } else if (TextUtils.equals(TAG_PING, current.tag)) {
+ success &= parsePing(current);
+ } else {
+ Log.w(TAG, "Ignoring unknown child of <" + node.tag + "> : " + current.tag);
+ }
+ }
+ } else if (TextUtils.equals("restricted", mAppStatus)) {
+ // Chrome isn't allowed to run in this country. Pretend the request was fine.
+ } else {
+ success = false;
+ }
+
+ return success;
+ }
+
+ private boolean parseUpdatecheck(Node node) {
+ boolean success = true;
+
+ mUpdateStatus = node.attributes.get("status");
+ if (TextUtils.equals("ok", mUpdateStatus)) {
+ for (int i = 0; i < node.children.size(); ++i) {
+ Node current = node.children.get(i);
+ if (TextUtils.equals(TAG_URLS, current.tag)) {
+ success &= parseUrls(current);
+ } else if (TextUtils.equals(TAG_MANIFEST, current.tag)) {
+ success &= parseManifest(current);
+ } else {
+ Log.w(TAG, "Ignoring unknown child of <" + node.tag + "> : " + current.tag);
+ }
+ }
+ } else if (TextUtils.equals("noupdate", mUpdateStatus)) {
+ // No update is available. Don't bother searching for other attributes.
+ } else {
+ Log.w(TAG, "Ignoring unknown status for " + node.tag + ": " + mUpdateStatus);
+ }
+
+ if (success) {
+ mParsedUpdateCheck = true;
+ } else {
+ Log.e(TAG, "Failed to parse: " + node.tag);
+ }
+ return success;
+ }
+
+ private boolean parsePing(Node node) {
+ if (!TextUtils.equals("ok", node.attributes.get("status"))) {
+ Log.e(TAG, "Failed to parse: " + node.tag);
+ return false;
+ }
+
+ mParsedPing = true;
+ return true;
+ }
+
+ private boolean parseEvent(Node node) {
+ if (!TextUtils.equals("ok", node.attributes.get("status"))) {
+ Log.e(TAG, "Failed to parse: " + node.tag);
+ return false;
+ }
+
+ mParsedInstallEvent = true;
+ return true;
+ }
+
+ private boolean parseUrls(Node node) {
+ for (int i = 0; i < node.children.size(); ++i) {
+ Node current = node.children.get(i);
+ if (TextUtils.equals(TAG_URL, current.tag)) {
+ parseUrl(current);
+ } else {
+ Log.w(TAG, "Ignoring unknown child of <" + node.tag + "> : " + current.tag);
+ }
+ }
+
+ if (mUrl == null) {
+ Log.e(TAG, "Failed to parse: " + node.tag);
+ return false;
+ }
+ return true;
+ }
+
+ private void parseUrl(Node node) {
+ String url = node.attributes.get("codebase");
+
+ // The URL gets a "/" tacked onto it by the Omaha server. Remove it.
+ if (url.endsWith("/")) {
+ url = url.substring(0, url.length() - 1);
+ }
+
+ if (url != null) mUrl = url;
+ }
+
+ private boolean parseManifest(Node node) {
+ mMarketVersion = node.attributes.get("version");
+ return mMarketVersion != null;
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698