diff options
Diffstat (limited to 'xpcom/base/nsVersionComparator.cpp')
-rw-r--r-- | xpcom/base/nsVersionComparator.cpp | 401 |
1 files changed, 401 insertions, 0 deletions
diff --git a/xpcom/base/nsVersionComparator.cpp b/xpcom/base/nsVersionComparator.cpp new file mode 100644 index 0000000000..f12e54272a --- /dev/null +++ b/xpcom/base/nsVersionComparator.cpp @@ -0,0 +1,401 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsVersionComparator.h" + +#include <stdlib.h> +#include <string.h> +#include <stdint.h> +#include <errno.h> +#include "mozilla/CheckedInt.h" +#if defined(XP_WIN) && !defined(UPDATER_NO_STRING_GLUE_STL) +# include <wchar.h> +# include "nsString.h" +#endif + +struct VersionPart { + int32_t numA; + + const char* strB; // NOT null-terminated, can be a null pointer + uint32_t strBlen; + + int32_t numC; + + char* extraD; // null-terminated +}; + +#ifdef XP_WIN +struct VersionPartW { + int32_t numA; + + wchar_t* strB; // NOT null-terminated, can be a null pointer + uint32_t strBlen; + + int32_t numC; + + wchar_t* extraD; // null-terminated +}; +#endif + +static int32_t ns_strtol(const char* aPart, char** aNext) { + errno = 0; + long result_long = strtol(aPart, aNext, 10); + + // Different platforms seem to disagree on what to return when the value + // is out of range so we ensure that it is always what we want it to be. + // We choose 0 firstly because that is the default when the number doesn't + // exist at all and also because it would be easier to recover from should + // you somehow end up in a situation where an old version is invalid. It is + // much easier to create a version either larger or smaller than 0, much + // harder to do the same with INT_MAX. + if (errno != 0) { + return 0; + } + + mozilla::CheckedInt<int32_t> result = result_long; + if (!result.isValid()) { + return 0; + } + + return result.value(); +} + +/** + * Parse a version part into a number and "extra text". + * + * @returns A pointer to the next versionpart, or null if none. + */ +static char* ParseVP(char* aPart, VersionPart& aResult) { + char* dot; + + aResult.numA = 0; + aResult.strB = nullptr; + aResult.strBlen = 0; + aResult.numC = 0; + aResult.extraD = nullptr; + + if (!aPart) { + return aPart; + } + + dot = strchr(aPart, '.'); + if (dot) { + *dot = '\0'; + } + + if (aPart[0] == '*' && aPart[1] == '\0') { + aResult.numA = INT32_MAX; + aResult.strB = ""; + } else { + aResult.numA = ns_strtol(aPart, const_cast<char**>(&aResult.strB)); + } + + if (!*aResult.strB) { + aResult.strB = nullptr; + aResult.strBlen = 0; + } else { + if (aResult.strB[0] == '+') { + static const char kPre[] = "pre"; + + ++aResult.numA; + aResult.strB = kPre; + aResult.strBlen = sizeof(kPre) - 1; + } else { + const char* numstart = strpbrk(aResult.strB, "0123456789+-"); + if (!numstart) { + aResult.strBlen = strlen(aResult.strB); + } else { + aResult.strBlen = numstart - aResult.strB; + aResult.numC = ns_strtol(numstart, const_cast<char**>(&aResult.extraD)); + + if (!*aResult.extraD) { + aResult.extraD = nullptr; + } + } + } + } + + if (dot) { + ++dot; + + if (!*dot) { + dot = nullptr; + } + } + + return dot; +} + +/** + * Parse a version part into a number and "extra text". + * + * @returns A pointer to the next versionpart, or null if none. + */ +#ifdef XP_WIN + +static int32_t ns_wcstol(const wchar_t* aPart, wchar_t** aNext) { + errno = 0; + long result_long = wcstol(aPart, aNext, 10); + + // See above for the rationale for using 0 here. + if (errno != 0) { + return 0; + } + + mozilla::CheckedInt<int32_t> result = result_long; + if (!result.isValid()) { + return 0; + } + + return result.value(); +} + +static wchar_t* ParseVP(wchar_t* aPart, VersionPartW& aResult) { + wchar_t* dot; + + aResult.numA = 0; + aResult.strB = nullptr; + aResult.strBlen = 0; + aResult.numC = 0; + aResult.extraD = nullptr; + + if (!aPart) { + return aPart; + } + + dot = wcschr(aPart, '.'); + if (dot) { + *dot = '\0'; + } + + if (aPart[0] == '*' && aPart[1] == '\0') { + static wchar_t kEmpty[] = L""; + + aResult.numA = INT32_MAX; + aResult.strB = kEmpty; + } else { + aResult.numA = ns_wcstol(aPart, const_cast<wchar_t**>(&aResult.strB)); + } + + if (!*aResult.strB) { + aResult.strB = nullptr; + aResult.strBlen = 0; + } else { + if (aResult.strB[0] == '+') { + static wchar_t kPre[] = L"pre"; + + ++aResult.numA; + aResult.strB = kPre; + aResult.strBlen = sizeof(kPre) - 1; + } else { + const wchar_t* numstart = wcspbrk(aResult.strB, L"0123456789+-"); + if (!numstart) { + aResult.strBlen = wcslen(aResult.strB); + } else { + aResult.strBlen = numstart - aResult.strB; + aResult.numC = + ns_wcstol(numstart, const_cast<wchar_t**>(&aResult.extraD)); + + if (!*aResult.extraD) { + aResult.extraD = nullptr; + } + } + } + } + + if (dot) { + ++dot; + + if (!*dot) { + dot = nullptr; + } + } + + return dot; +} +#endif + +// compare two null-terminated strings, which may be null pointers +static int32_t ns_strcmp(const char* aStr1, const char* aStr2) { + // any string is *before* no string + if (!aStr1) { + return aStr2 != 0; + } + + if (!aStr2) { + return -1; + } + + return strcmp(aStr1, aStr2); +} + +// compare two length-specified string, which may be null pointers +static int32_t ns_strnncmp(const char* aStr1, uint32_t aLen1, const char* aStr2, + uint32_t aLen2) { + // any string is *before* no string + if (!aStr1) { + return aStr2 != 0; + } + + if (!aStr2) { + return -1; + } + + for (; aLen1 && aLen2; --aLen1, --aLen2, ++aStr1, ++aStr2) { + if (*aStr1 < *aStr2) { + return -1; + } + + if (*aStr1 > *aStr2) { + return 1; + } + } + + if (aLen1 == 0) { + return aLen2 == 0 ? 0 : -1; + } + + return 1; +} + +// compare two int32_t +static int32_t ns_cmp(int32_t aNum1, int32_t aNum2) { + if (aNum1 < aNum2) { + return -1; + } + + return aNum1 != aNum2; +} + +/** + * Compares two VersionParts + */ +static int32_t CompareVP(VersionPart& aVer1, VersionPart& aVer2) { + int32_t r = ns_cmp(aVer1.numA, aVer2.numA); + if (r) { + return r; + } + + r = ns_strnncmp(aVer1.strB, aVer1.strBlen, aVer2.strB, aVer2.strBlen); + if (r) { + return r; + } + + r = ns_cmp(aVer1.numC, aVer2.numC); + if (r) { + return r; + } + + return ns_strcmp(aVer1.extraD, aVer2.extraD); +} + +/** + * Compares two VersionParts + */ +#ifdef XP_WIN +static int32_t CompareVP(VersionPartW& aVer1, VersionPartW& aVer2) { + int32_t r = ns_cmp(aVer1.numA, aVer2.numA); + if (r) { + return r; + } + + r = wcsncmp(aVer1.strB, aVer2.strB, XPCOM_MIN(aVer1.strBlen, aVer2.strBlen)); + if (r) { + return r; + } + + r = ns_cmp(aVer1.numC, aVer2.numC); + if (r) { + return r; + } + + if (!aVer1.extraD) { + return aVer2.extraD != 0; + } + + if (!aVer2.extraD) { + return -1; + } + + return wcscmp(aVer1.extraD, aVer2.extraD); +} +#endif + +namespace mozilla { + +#ifdef XP_WIN +int32_t CompareVersions(const char16_t* aStrA, const char16_t* aStrB) { + wchar_t* A2 = wcsdup(char16ptr_t(aStrA)); + if (!A2) { + return 1; + } + + wchar_t* B2 = wcsdup(char16ptr_t(aStrB)); + if (!B2) { + free(A2); + return 1; + } + + int32_t result; + wchar_t* a = A2; + wchar_t* b = B2; + + do { + VersionPartW va, vb; + + a = ParseVP(a, va); + b = ParseVP(b, vb); + + result = CompareVP(va, vb); + if (result) { + break; + } + + } while (a || b); + + free(A2); + free(B2); + + return result; +} +#endif + +int32_t CompareVersions(const char* aStrA, const char* aStrB) { + char* A2 = strdup(aStrA); + if (!A2) { + return 1; + } + + char* B2 = strdup(aStrB); + if (!B2) { + free(A2); + return 1; + } + + int32_t result; + char* a = A2; + char* b = B2; + + do { + VersionPart va, vb; + + a = ParseVP(a, va); + b = ParseVP(b, vb); + + result = CompareVP(va, vb); + if (result) { + break; + } + + } while (a || b); + + free(A2); + free(B2); + + return result; +} + +} // namespace mozilla |