1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "nsVersionComparator.h"
8 
9 #include <stdlib.h>
10 #include <string.h>
11 #include <stdint.h>
12 #include <errno.h>
13 #include "mozilla/CheckedInt.h"
14 #if defined(XP_WIN) && !defined(UPDATER_NO_STRING_GLUE_STL)
15 #  include <wchar.h>
16 #  include "nsString.h"
17 #endif
18 
19 struct VersionPart {
20   int32_t numA;
21 
22   const char* strB;  // NOT null-terminated, can be a null pointer
23   uint32_t strBlen;
24 
25   int32_t numC;
26 
27   char* extraD;  // null-terminated
28 };
29 
30 #ifdef XP_WIN
31 struct VersionPartW {
32   int32_t numA;
33 
34   wchar_t* strB;  // NOT null-terminated, can be a null pointer
35   uint32_t strBlen;
36 
37   int32_t numC;
38 
39   wchar_t* extraD;  // null-terminated
40 };
41 #endif
42 
ns_strtol(const char * aPart,char ** aNext)43 static int32_t ns_strtol(const char* aPart, char** aNext) {
44   errno = 0;
45   long result_long = strtol(aPart, aNext, 10);
46 
47   // Different platforms seem to disagree on what to return when the value
48   // is out of range so we ensure that it is always what we want it to be.
49   // We choose 0 firstly because that is the default when the number doesn't
50   // exist at all and also because it would be easier to recover from should
51   // you somehow end up in a situation where an old version is invalid. It is
52   // much easier to create a version either larger or smaller than 0, much
53   // harder to do the same with INT_MAX.
54   if (errno != 0) {
55     return 0;
56   }
57 
58   mozilla::CheckedInt<int32_t> result = result_long;
59   if (!result.isValid()) {
60     return 0;
61   }
62 
63   return result.value();
64 }
65 
66 /**
67  * Parse a version part into a number and "extra text".
68  *
69  * @returns A pointer to the next versionpart, or null if none.
70  */
ParseVP(char * aPart,VersionPart & aResult)71 static char* ParseVP(char* aPart, VersionPart& aResult) {
72   char* dot;
73 
74   aResult.numA = 0;
75   aResult.strB = nullptr;
76   aResult.strBlen = 0;
77   aResult.numC = 0;
78   aResult.extraD = nullptr;
79 
80   if (!aPart) {
81     return aPart;
82   }
83 
84   dot = strchr(aPart, '.');
85   if (dot) {
86     *dot = '\0';
87   }
88 
89   if (aPart[0] == '*' && aPart[1] == '\0') {
90     aResult.numA = INT32_MAX;
91     aResult.strB = "";
92   } else {
93     aResult.numA = ns_strtol(aPart, const_cast<char**>(&aResult.strB));
94   }
95 
96   if (!*aResult.strB) {
97     aResult.strB = nullptr;
98     aResult.strBlen = 0;
99   } else {
100     if (aResult.strB[0] == '+') {
101       static const char kPre[] = "pre";
102 
103       ++aResult.numA;
104       aResult.strB = kPre;
105       aResult.strBlen = sizeof(kPre) - 1;
106     } else {
107       const char* numstart = strpbrk(aResult.strB, "0123456789+-");
108       if (!numstart) {
109         aResult.strBlen = strlen(aResult.strB);
110       } else {
111         aResult.strBlen = numstart - aResult.strB;
112         aResult.numC = ns_strtol(numstart, const_cast<char**>(&aResult.extraD));
113 
114         if (!*aResult.extraD) {
115           aResult.extraD = nullptr;
116         }
117       }
118     }
119   }
120 
121   if (dot) {
122     ++dot;
123 
124     if (!*dot) {
125       dot = nullptr;
126     }
127   }
128 
129   return dot;
130 }
131 
132 /**
133  * Parse a version part into a number and "extra text".
134  *
135  * @returns A pointer to the next versionpart, or null if none.
136  */
137 #ifdef XP_WIN
138 
ns_wcstol(const wchar_t * aPart,wchar_t ** aNext)139 static int32_t ns_wcstol(const wchar_t* aPart, wchar_t** aNext) {
140   errno = 0;
141   long result_long = wcstol(aPart, aNext, 10);
142 
143   // See above for the rationale for using 0 here.
144   if (errno != 0) {
145     return 0;
146   }
147 
148   mozilla::CheckedInt<int32_t> result = result_long;
149   if (!result.isValid()) {
150     return 0;
151   }
152 
153   return result.value();
154 }
155 
ParseVP(wchar_t * aPart,VersionPartW & aResult)156 static wchar_t* ParseVP(wchar_t* aPart, VersionPartW& aResult) {
157   wchar_t* dot;
158 
159   aResult.numA = 0;
160   aResult.strB = nullptr;
161   aResult.strBlen = 0;
162   aResult.numC = 0;
163   aResult.extraD = nullptr;
164 
165   if (!aPart) {
166     return aPart;
167   }
168 
169   dot = wcschr(aPart, '.');
170   if (dot) {
171     *dot = '\0';
172   }
173 
174   if (aPart[0] == '*' && aPart[1] == '\0') {
175     static wchar_t kEmpty[] = L"";
176 
177     aResult.numA = INT32_MAX;
178     aResult.strB = kEmpty;
179   } else {
180     aResult.numA = ns_wcstol(aPart, const_cast<wchar_t**>(&aResult.strB));
181   }
182 
183   if (!*aResult.strB) {
184     aResult.strB = nullptr;
185     aResult.strBlen = 0;
186   } else {
187     if (aResult.strB[0] == '+') {
188       static wchar_t kPre[] = L"pre";
189 
190       ++aResult.numA;
191       aResult.strB = kPre;
192       aResult.strBlen = sizeof(kPre) - 1;
193     } else {
194       const wchar_t* numstart = wcspbrk(aResult.strB, L"0123456789+-");
195       if (!numstart) {
196         aResult.strBlen = wcslen(aResult.strB);
197       } else {
198         aResult.strBlen = numstart - aResult.strB;
199         aResult.numC =
200             ns_wcstol(numstart, const_cast<wchar_t**>(&aResult.extraD));
201 
202         if (!*aResult.extraD) {
203           aResult.extraD = nullptr;
204         }
205       }
206     }
207   }
208 
209   if (dot) {
210     ++dot;
211 
212     if (!*dot) {
213       dot = nullptr;
214     }
215   }
216 
217   return dot;
218 }
219 #endif
220 
221 // compare two null-terminated strings, which may be null pointers
ns_strcmp(const char * aStr1,const char * aStr2)222 static int32_t ns_strcmp(const char* aStr1, const char* aStr2) {
223   // any string is *before* no string
224   if (!aStr1) {
225     return aStr2 != 0;
226   }
227 
228   if (!aStr2) {
229     return -1;
230   }
231 
232   return strcmp(aStr1, aStr2);
233 }
234 
235 // compare two length-specified string, which may be null pointers
ns_strnncmp(const char * aStr1,uint32_t aLen1,const char * aStr2,uint32_t aLen2)236 static int32_t ns_strnncmp(const char* aStr1, uint32_t aLen1, const char* aStr2,
237                            uint32_t aLen2) {
238   // any string is *before* no string
239   if (!aStr1) {
240     return aStr2 != 0;
241   }
242 
243   if (!aStr2) {
244     return -1;
245   }
246 
247   for (; aLen1 && aLen2; --aLen1, --aLen2, ++aStr1, ++aStr2) {
248     if (*aStr1 < *aStr2) {
249       return -1;
250     }
251 
252     if (*aStr1 > *aStr2) {
253       return 1;
254     }
255   }
256 
257   if (aLen1 == 0) {
258     return aLen2 == 0 ? 0 : -1;
259   }
260 
261   return 1;
262 }
263 
264 // compare two int32_t
ns_cmp(int32_t aNum1,int32_t aNum2)265 static int32_t ns_cmp(int32_t aNum1, int32_t aNum2) {
266   if (aNum1 < aNum2) {
267     return -1;
268   }
269 
270   return aNum1 != aNum2;
271 }
272 
273 /**
274  * Compares two VersionParts
275  */
CompareVP(VersionPart & aVer1,VersionPart & aVer2)276 static int32_t CompareVP(VersionPart& aVer1, VersionPart& aVer2) {
277   int32_t r = ns_cmp(aVer1.numA, aVer2.numA);
278   if (r) {
279     return r;
280   }
281 
282   r = ns_strnncmp(aVer1.strB, aVer1.strBlen, aVer2.strB, aVer2.strBlen);
283   if (r) {
284     return r;
285   }
286 
287   r = ns_cmp(aVer1.numC, aVer2.numC);
288   if (r) {
289     return r;
290   }
291 
292   return ns_strcmp(aVer1.extraD, aVer2.extraD);
293 }
294 
295 /**
296  * Compares two VersionParts
297  */
298 #ifdef XP_WIN
CompareVP(VersionPartW & aVer1,VersionPartW & aVer2)299 static int32_t CompareVP(VersionPartW& aVer1, VersionPartW& aVer2) {
300   int32_t r = ns_cmp(aVer1.numA, aVer2.numA);
301   if (r) {
302     return r;
303   }
304 
305   r = wcsncmp(aVer1.strB, aVer2.strB, XPCOM_MIN(aVer1.strBlen, aVer2.strBlen));
306   if (r) {
307     return r;
308   }
309 
310   r = ns_cmp(aVer1.numC, aVer2.numC);
311   if (r) {
312     return r;
313   }
314 
315   if (!aVer1.extraD) {
316     return aVer2.extraD != 0;
317   }
318 
319   if (!aVer2.extraD) {
320     return -1;
321   }
322 
323   return wcscmp(aVer1.extraD, aVer2.extraD);
324 }
325 #endif
326 
327 namespace mozilla {
328 
329 #ifdef XP_WIN
CompareVersions(const char16_t * aStrA,const char16_t * aStrB)330 int32_t CompareVersions(const char16_t* aStrA, const char16_t* aStrB) {
331   wchar_t* A2 = wcsdup(char16ptr_t(aStrA));
332   if (!A2) {
333     return 1;
334   }
335 
336   wchar_t* B2 = wcsdup(char16ptr_t(aStrB));
337   if (!B2) {
338     free(A2);
339     return 1;
340   }
341 
342   int32_t result;
343   wchar_t* a = A2;
344   wchar_t* b = B2;
345 
346   do {
347     VersionPartW va, vb;
348 
349     a = ParseVP(a, va);
350     b = ParseVP(b, vb);
351 
352     result = CompareVP(va, vb);
353     if (result) {
354       break;
355     }
356 
357   } while (a || b);
358 
359   free(A2);
360   free(B2);
361 
362   return result;
363 }
364 #endif
365 
CompareVersions(const char * aStrA,const char * aStrB)366 int32_t CompareVersions(const char* aStrA, const char* aStrB) {
367   char* A2 = strdup(aStrA);
368   if (!A2) {
369     return 1;
370   }
371 
372   char* B2 = strdup(aStrB);
373   if (!B2) {
374     free(A2);
375     return 1;
376   }
377 
378   int32_t result;
379   char* a = A2;
380   char* b = B2;
381 
382   do {
383     VersionPart va, vb;
384 
385     a = ParseVP(a, va);
386     b = ParseVP(b, vb);
387 
388     result = CompareVP(va, vb);
389     if (result) {
390       break;
391     }
392 
393   } while (a || b);
394 
395   free(A2);
396   free(B2);
397 
398   return result;
399 }
400 
401 }  // namespace mozilla
402