/* -*- 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 #include #include #include #include "mozilla/CheckedInt.h" #if defined(XP_WIN) && !defined(UPDATER_NO_STRING_GLUE_STL) # include # 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 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(&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(&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 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(&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(&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