blob: 27767ee770c13307d46d14b8d91aeaef0dd29da1 [file] [log] [blame]
// Copyright 2015 The Android Open Source Project
//
// This software is licensed under the terms of the GNU General Public
// License version 2, as published by the Free Software Foundation, and
// may be copied, distributed, and modified under those terms.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
#include "VersionExtractor.h"
#include "android/base/ArraySize.h"
#include "android/base/Compiler.h"
#include "android/base/containers/Lookup.h"
#include "android/base/memory/ScopedPtr.h"
#include "android/base/Optional.h"
#include "android/utils/debug.h"
#include "android/version.h"
#include "config-host.h"
#include <libxml/tree.h>
#include <algorithm>
#include <iterator>
#include <limits>
#include <memory>
#include <unordered_map>
#include <errno.h>
#include <stdlib.h>
namespace android {
namespace update_check {
using android::base::findOrDefault;
using android::base::Optional;
using android::base::Version;
using android::studio::UpdateChannel;
const char* const VersionExtractor::kXmlNamespace =
"http://schemas.android.com/sdk/android/repo/repository2/01";
namespace {
struct DeleteXmlObject {
template <class T>
void operator()(T t) const {
xmlFree(t);
}
void operator()(xmlDocPtr doc) const { xmlFreeDoc(doc); }
};
}
template <class T>
using xmlAutoPtr = std::unique_ptr<T, DeleteXmlObject>;
static Optional<Version::ComponentType> parseInt(const xmlChar* str,
const char* stopChars = "") {
if (!str) {
return {};
}
char* end;
errno = 0;
const auto value = strtoul(reinterpret_cast<const char*>(str), &end, 10);
if (errno || strchr(stopChars, *end) == nullptr ||
value > std::numeric_limits<Version::ComponentType>::max() ||
value == Version::kNone) {
dwarning("UpdateCheck: invalid value of a version attribute");
return {};
}
return static_cast<Version::ComponentType>(value);
}
static const xmlChar* getContent(xmlNodePtr node) {
// libxml2 has the following structure of the node content:
// <a>aaa</a> ->
// xmlNode(name = "a", type=element_node)
// -> first child xmlNode(name = "text", type=element_text)
// ->content
if (node->type != XML_ELEMENT_NODE || !node->children ||
node->children->type != XML_TEXT_NODE || !node->children->content) {
return {};
}
return node->children->content;
}
static std::pair<Version, std::string> parseVersion(xmlNodePtr node) {
Optional<Version::ComponentType> major, minor, micro, build;
xmlNodePtr revNode = nullptr;
std::string channelName;
for (auto child = node->children;
child && (!revNode || !build || channelName.empty());
child = child->next) {
if (child->type == XML_COMMENT_NODE && child->content) {
// the comment with build number has a string "bid:<build>"
const auto buildStr = xmlStrstr(child->content, BAD_CAST "bid:");
if (buildStr) {
build = parseInt(buildStr + STRING_LITERAL_LENGTH("bid:"),
", \r\n\t");
}
} else if (child->type == XML_ELEMENT_NODE &&
xmlStrcmp(child->name, BAD_CAST "revision") == 0) {
revNode = child;
} else if (child->type == XML_ELEMENT_NODE &&
xmlStrcmp(child->name, BAD_CAST "channelRef") == 0) {
const xmlAutoPtr<xmlChar> channelRef(
xmlGetProp(child, BAD_CAST "ref"));
if (channelRef) {
channelName = std::string(
reinterpret_cast<const char*>(channelRef.get()));
}
}
}
if (!revNode) {
return {};
}
for (xmlNodePtr verPart = revNode->children; verPart;
verPart = verPart->next) {
const auto content = getContent(verPart);
if (const auto val = parseInt(content)) {
if (xmlStrcmp(verPart->name, BAD_CAST "major") == 0) {
major = val;
} else if (xmlStrcmp(verPart->name, BAD_CAST "minor") == 0) {
minor = val;
} else if (xmlStrcmp(verPart->name, BAD_CAST "micro") == 0) {
micro = val;
}
}
}
if (!major || !minor || !micro) {
return {};
}
return {Version(*major, *minor, *micro, build.valueOr(0)),
std::move(channelName)};
}
static UpdateChannel parseUpdateChannelName(const xmlChar* name) {
static const struct NamedChannel {
const xmlChar* name;
UpdateChannel channel;
} channels[] = {{BAD_CAST "stable", UpdateChannel::Stable},
{BAD_CAST "beta", UpdateChannel::Beta},
{BAD_CAST "dev", UpdateChannel::Dev},
{BAD_CAST "canary", UpdateChannel::Canary}};
const auto it = std::find_if(std::begin(channels), std::end(channels),
[name](const NamedChannel& nc) {
return xmlStrcmp(nc.name, name) == 0;
});
if (it == std::end(channels)) {
return UpdateChannel::Unknown;
}
return it->channel;
}
IVersionExtractor::Versions VersionExtractor::extractVersions(
StringView data) const {
// make sure libxml2 is initialized and is of proper version
LIBXML_TEST_VERSION
const xmlAutoPtr<xmlDoc> doc(
xmlReadMemory(data.c_str(), data.size(), "none.xml", nullptr, 0));
if (!doc) {
return {};
}
const xmlNodePtr root = xmlDocGetRootElement(doc.get());
if (!root->ns || xmlStrcmp(root->ns->href, BAD_CAST kXmlNamespace) != 0) {
return {};
}
std::unordered_map<std::string, UpdateChannel> channelMap;
Versions res;
// iterate over all nodes in the document and find all <tool> ones
for (xmlNodePtr node = root->children; node; node = node->next) {
if (node->type != XML_ELEMENT_NODE) {
continue;
}
if (xmlStrcmp(node->name, BAD_CAST "channel") == 0) {
// <channel id="id">name</channel>
const auto channelName = getContent(node);
if (!channelName) {
continue;
}
const xmlAutoPtr<xmlChar> id(xmlGetProp(node, BAD_CAST "id"));
if (!id) {
continue;
}
const UpdateChannel channel = parseUpdateChannelName(channelName);
if (channel == UpdateChannel::Unknown) {
continue;
}
channelMap[std::string(reinterpret_cast<const char*>(id.get()))] =
channel;
} else if (xmlStrcmp(node->name, BAD_CAST "remotePackage") == 0) {
// <remotePackage path="tools">
// !version
// !channel info
// !build info
// </revotePachage>
xmlAutoPtr<xmlChar> pathVal(xmlGetProp(node, BAD_CAST "path"));
if (!pathVal || xmlStrcmp(pathVal.get(), BAD_CAST "tools") != 0) {
continue;
}
Version nodeVer;
std::string channelName;
std::tie(nodeVer, channelName) = parseVersion(node);
if (!nodeVer.isValid()) {
continue;
}
const auto channel = findOrDefault(channelMap, channelName,
UpdateChannel::Canary);
Version& existingVer = res[channel];
if (!existingVer.isValid() || existingVer < nodeVer) {
existingVer = nodeVer;
}
}
}
return res;
}
Version VersionExtractor::getCurrentVersion() const {
static const Version currentVersion =
Version(EMULATOR_FULL_VERSION_STRING);
return currentVersion;
}
} // namespace update_check
} // namespace android