root/chrome/android/java/src/org/chromium/chrome/browser/omaha/ResponseParser.java

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. parseResponse
  2. getDaystartSeconds
  3. getNewVersion
  4. getURL
  5. getAppStatus
  6. getUpdateStatus
  7. resetParsedData
  8. logError
  9. parseRootNode
  10. parseResponseNode
  11. parseDaystartNode
  12. parseAppNode
  13. parseUpdatecheck
  14. parsePing
  15. parseEvent
  16. parseUrls
  17. parseUrl
  18. parseManifest

// Copyright 2014 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.chromium.chrome.browser.omaha.XMLParser.Node;

/**
 * Parses XML responses from the Omaha Update Server.
 *
 * Expects XML formatted like:
 * <?xml version="1.0" encoding="UTF-8"?>
 *   <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 variant of Chrome that is running.
 */
public class ResponseParser {
    private static final String TAG = "ResponseParser";

    // 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 String mAppId;
    private final boolean mExpectInstallEvent;
    private final boolean mExpectPing;
    private final boolean mExpectUpdatecheck;
    private final boolean mStrictParsingMode;

    private Integer mDaystartSeconds;
    private String mAppStatus;

    private String mUpdateStatus;
    private String mNewVersion;
    private String mUrl;

    private boolean mParsedInstallEvent;
    private boolean mParsedPing;
    private boolean mParsedUpdatecheck;

    public ResponseParser(String appId, boolean expectInstallEvent, boolean expectPing,
            boolean expectUpdatecheck) {
        this(false, appId, expectInstallEvent, expectPing, expectUpdatecheck);
    }

    public ResponseParser(boolean strictParsing, String appId, boolean expectInstallEvent,
            boolean expectPing, boolean expectUpdatecheck) {
        mStrictParsingMode = strictParsing;
        mAppId = appId;
        mExpectInstallEvent = expectInstallEvent;
        mExpectPing = expectPing;
        mExpectUpdatecheck = expectUpdatecheck;
    }

    public void parseResponse(String xml) throws RequestFailureException {
        XMLParser parser = new XMLParser(xml);
        Node rootNode = parser.getRootNode();
        parseRootNode(rootNode);
    }

    public int getDaystartSeconds() {
        if (mDaystartSeconds == null) return 0;
        return mDaystartSeconds;
    }

    public String getNewVersion() {
        return mNewVersion;
    }

    public String getURL() {
        return mUrl;
    }

    public String getAppStatus() {
        return mAppStatus;
    }

    public String getUpdateStatus() {
        return mUpdateStatus;
    }

    private void resetParsedData() {
        mDaystartSeconds = null;
        mNewVersion = null;
        mUrl = null;
        mUpdateStatus = null;
        mAppStatus = null;

        mParsedInstallEvent = false;
        mParsedPing = false;
        mParsedUpdatecheck = false;
    }

    private boolean logError(Node node, int errorCode) throws RequestFailureException {
        String errorMessage = "Failed to parse: " + node.tag;
        if (mStrictParsingMode) throw new RequestFailureException(errorMessage, errorCode);

        Log.e(TAG, errorMessage);
        return false;
    }

    private void parseRootNode(Node rootNode) throws RequestFailureException {
        for (int i = 0; i < rootNode.children.size(); ++i) {
            if (TextUtils.equals(TAG_RESPONSE, rootNode.children.get(i).tag)) {
                if (parseResponseNode(rootNode.children.get(i))) return;
                break;
            }
        }

        // The tag was bad; reset all of our state and bail.
        resetParsedData();
        logError(rootNode, RequestFailureException.ERROR_PARSE_ROOT);
    }

    private boolean parseResponseNode(Node node) throws RequestFailureException {
        boolean success = true;
        String serverType = node.attributes.get("server");
        success &= TextUtils.equals("3.0", node.attributes.get("protocol"));

        if (!TextUtils.equals("prod", serverType)) Log.w(TAG, "Server type: " + serverType);

        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);
            }
        }

        if (!success) {
            return logError(node, RequestFailureException.ERROR_PARSE_RESPONSE);
        } else if (mDaystartSeconds == null) {
            return logError(node, RequestFailureException.ERROR_PARSE_DAYSTART);
        } else if (mAppStatus == null) {
            return logError(node, RequestFailureException.ERROR_PARSE_APP);
        } else if (mExpectInstallEvent != mParsedInstallEvent) {
            return logError(node, RequestFailureException.ERROR_PARSE_EVENT);
        } else if (mExpectPing != mParsedPing) {
            return logError(node, RequestFailureException.ERROR_PARSE_PING);
        } else if (mExpectUpdatecheck != mParsedUpdatecheck) {
            return logError(node, RequestFailureException.ERROR_PARSE_UPDATECHECK);
        }

        return true;
    }

    private boolean parseDaystartNode(Node node) throws RequestFailureException {
        try {
            mDaystartSeconds = Integer.parseInt(node.attributes.get("elapsed_seconds"));
        } catch (NumberFormatException e) {
            return logError(node, RequestFailureException.ERROR_PARSE_DAYSTART);
        }
        return true;
    }

    private boolean parseAppNode(Node node) throws RequestFailureException {
        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)) {
                    parseEvent(current);
                } else if (TextUtils.equals(TAG_PING, current.tag)) {
                    parsePing(current);
                }
            }
        } else if (TextUtils.equals("restricted", mAppStatus)) {
            // Omaha isn't allowed to get data in this country.  Pretend the request was fine.
        } else {
            success = false;
        }

        if (success) return true;
        return logError(node, RequestFailureException.ERROR_PARSE_APP);
    }

    private boolean parseUpdatecheck(Node node) throws RequestFailureException {
        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)) {
                    parseUrls(current);
                } else if (TextUtils.equals(TAG_MANIFEST, current.tag)) {
                    parseManifest(current);
                }
            }

            // Confirm all the tags we expected to see were parsed properly.
            if (mUrl == null) {
                return logError(node, RequestFailureException.ERROR_PARSE_URLS);
            } else if (mNewVersion == null) {
                return logError(node, RequestFailureException.ERROR_PARSE_MANIFEST);
            }
        } else if (TextUtils.equals("noupdate", mUpdateStatus)) {
            // No update is available.  Don't bother searching for other attributes.
        } else if (mUpdateStatus != null && mUpdateStatus.startsWith("error")) {
            Log.w(TAG, "Ignoring error status for " + node.tag + ": " + mUpdateStatus);
        } else {
            Log.w(TAG, "Ignoring unknown status for " + node.tag + ": " + mUpdateStatus);
        }

        mParsedUpdatecheck = true;
        return true;
    }

    private void parsePing(Node node) {
        if (TextUtils.equals("ok", node.attributes.get("status"))) mParsedPing = true;
    }

    private void parseEvent(Node node) {
        if (TextUtils.equals("ok", node.attributes.get("status"))) mParsedInstallEvent = true;
    }

    private void 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);
        }
    }

    private void parseUrl(Node node) {
        String url = node.attributes.get("codebase");
        if (url == null) return;

        // The URL gets a "/" tacked onto it by the server.  Remove it.
        if (url.endsWith("/")) url = url.substring(0, url.length() - 1);
        mUrl = url;
    }

    private void parseManifest(Node node) {
        mNewVersion = node.attributes.get("version");
    }
}

/* [<][>][^][v][top][bottom][index][help] */