| // 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 |