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 #if defined(XP_WIN) && !defined(UPDATER_NO_STRING_GLUE_STL)
13 #include <wchar.h>
14 #include "nsString.h"
15 #endif
16 
17 struct VersionPart {
18   int32_t numA;
19 
20   const char* strB;  // NOT null-terminated, can be a null pointer
21   uint32_t strBlen;
22 
23   int32_t numC;
24 
25   char* extraD;  // null-terminated
26 };
27 
28 #ifdef XP_WIN
29 struct VersionPartW {
30   int32_t numA;
31 
32   wchar_t* strB;  // NOT null-terminated, can be a null pointer
33   uint32_t strBlen;
34 
35   int32_t numC;
36 
37   wchar_t* extraD;  // null-terminated
38 };
39 #endif
40 
41 /**
42  * Parse a version part into a number and "extra text".
43  *
44  * @returns A pointer to the next versionpart, or null if none.
45  */
ParseVP(char * aPart,VersionPart & aResult)46 static char* ParseVP(char* aPart, VersionPart& aResult) {
47   char* dot;
48 
49   aResult.numA = 0;
50   aResult.strB = nullptr;
51   aResult.strBlen = 0;
52   aResult.numC = 0;
53   aResult.extraD = nullptr;
54 
55   if (!aPart) {
56     return aPart;
57   }
58 
59   dot = strchr(aPart, '.');
60   if (dot) {
61     *dot = '\0';
62   }
63 
64   if (aPart[0] == '*' && aPart[1] == '\0') {
65     aResult.numA = INT32_MAX;
66     aResult.strB = "";
67   } else {
68     aResult.numA = strtol(aPart, const_cast<char**>(&aResult.strB), 10);
69   }
70 
71   if (!*aResult.strB) {
72     aResult.strB = nullptr;
73     aResult.strBlen = 0;
74   } else {
75     if (aResult.strB[0] == '+') {
76       static const char kPre[] = "pre";
77 
78       ++aResult.numA;
79       aResult.strB = kPre;
80       aResult.strBlen = sizeof(kPre) - 1;
81     } else {
82       const char* numstart = strpbrk(aResult.strB, "0123456789+-");
83       if (!numstart) {
84         aResult.strBlen = strlen(aResult.strB);
85       } else {
86         aResult.strBlen = numstart - aResult.strB;
87 
88         aResult.numC = strtol(numstart, &aResult.extraD, 10);
89         if (!*aResult.extraD) {
90           aResult.extraD = nullptr;
91         }
92       }
93     }
94   }
95 
96   if (dot) {
97     ++dot;
98 
99     if (!*dot) {
100       dot = nullptr;
101     }
102   }
103 
104   return dot;
105 }
106 
107 /**
108  * Parse a version part into a number and "extra text".
109  *
110  * @returns A pointer to the next versionpart, or null if none.
111  */
112 #ifdef XP_WIN
ParseVP(wchar_t * aPart,VersionPartW & aResult)113 static wchar_t* ParseVP(wchar_t* aPart, VersionPartW& aResult) {
114   wchar_t* dot;
115 
116   aResult.numA = 0;
117   aResult.strB = nullptr;
118   aResult.strBlen = 0;
119   aResult.numC = 0;
120   aResult.extraD = nullptr;
121 
122   if (!aPart) {
123     return aPart;
124   }
125 
126   dot = wcschr(aPart, '.');
127   if (dot) {
128     *dot = '\0';
129   }
130 
131   if (aPart[0] == '*' && aPart[1] == '\0') {
132     static wchar_t kEmpty[] = L"";
133 
134     aResult.numA = INT32_MAX;
135     aResult.strB = kEmpty;
136   } else {
137     aResult.numA = wcstol(aPart, const_cast<wchar_t**>(&aResult.strB), 10);
138   }
139 
140   if (!*aResult.strB) {
141     aResult.strB = nullptr;
142     aResult.strBlen = 0;
143   } else {
144     if (aResult.strB[0] == '+') {
145       static wchar_t kPre[] = L"pre";
146 
147       ++aResult.numA;
148       aResult.strB = kPre;
149       aResult.strBlen = sizeof(kPre) - 1;
150     } else {
151       const wchar_t* numstart = wcspbrk(aResult.strB, L"0123456789+-");
152       if (!numstart) {
153         aResult.strBlen = wcslen(aResult.strB);
154       } else {
155         aResult.strBlen = numstart - aResult.strB;
156 
157         aResult.numC = wcstol(numstart, &aResult.extraD, 10);
158         if (!*aResult.extraD) {
159           aResult.extraD = nullptr;
160         }
161       }
162     }
163   }
164 
165   if (dot) {
166     ++dot;
167 
168     if (!*dot) {
169       dot = nullptr;
170     }
171   }
172 
173   return dot;
174 }
175 #endif
176 
177 // compare two null-terminated strings, which may be null pointers
ns_strcmp(const char * aStr1,const char * aStr2)178 static int32_t ns_strcmp(const char* aStr1, const char* aStr2) {
179   // any string is *before* no string
180   if (!aStr1) {
181     return aStr2 != 0;
182   }
183 
184   if (!aStr2) {
185     return -1;
186   }
187 
188   return strcmp(aStr1, aStr2);
189 }
190 
191 // compare two length-specified string, which may be null pointers
ns_strnncmp(const char * aStr1,uint32_t aLen1,const char * aStr2,uint32_t aLen2)192 static int32_t ns_strnncmp(const char* aStr1, uint32_t aLen1, const char* aStr2,
193                            uint32_t aLen2) {
194   // any string is *before* no string
195   if (!aStr1) {
196     return aStr2 != 0;
197   }
198 
199   if (!aStr2) {
200     return -1;
201   }
202 
203   for (; aLen1 && aLen2; --aLen1, --aLen2, ++aStr1, ++aStr2) {
204     if (*aStr1 < *aStr2) {
205       return -1;
206     }
207 
208     if (*aStr1 > *aStr2) {
209       return 1;
210     }
211   }
212 
213   if (aLen1 == 0) {
214     return aLen2 == 0 ? 0 : -1;
215   }
216 
217   return 1;
218 }
219 
220 // compare two int32_t
ns_cmp(int32_t aNum1,int32_t aNum2)221 static int32_t ns_cmp(int32_t aNum1, int32_t aNum2) {
222   if (aNum1 < aNum2) {
223     return -1;
224   }
225 
226   return aNum1 != aNum2;
227 }
228 
229 /**
230  * Compares two VersionParts
231  */
CompareVP(VersionPart & aVer1,VersionPart & aVer2)232 static int32_t CompareVP(VersionPart& aVer1, VersionPart& aVer2) {
233   int32_t r = ns_cmp(aVer1.numA, aVer2.numA);
234   if (r) {
235     return r;
236   }
237 
238   r = ns_strnncmp(aVer1.strB, aVer1.strBlen, aVer2.strB, aVer2.strBlen);
239   if (r) {
240     return r;
241   }
242 
243   r = ns_cmp(aVer1.numC, aVer2.numC);
244   if (r) {
245     return r;
246   }
247 
248   return ns_strcmp(aVer1.extraD, aVer2.extraD);
249 }
250 
251 /**
252  * Compares two VersionParts
253  */
254 #ifdef XP_WIN
CompareVP(VersionPartW & aVer1,VersionPartW & aVer2)255 static int32_t CompareVP(VersionPartW& aVer1, VersionPartW& aVer2) {
256   int32_t r = ns_cmp(aVer1.numA, aVer2.numA);
257   if (r) {
258     return r;
259   }
260 
261   r = wcsncmp(aVer1.strB, aVer2.strB, XPCOM_MIN(aVer1.strBlen, aVer2.strBlen));
262   if (r) {
263     return r;
264   }
265 
266   r = ns_cmp(aVer1.numC, aVer2.numC);
267   if (r) {
268     return r;
269   }
270 
271   if (!aVer1.extraD) {
272     return aVer2.extraD != 0;
273   }
274 
275   if (!aVer2.extraD) {
276     return -1;
277   }
278 
279   return wcscmp(aVer1.extraD, aVer2.extraD);
280 }
281 #endif
282 
283 namespace mozilla {
284 
285 #ifdef XP_WIN
CompareVersions(const char16_t * aStrA,const char16_t * aStrB)286 int32_t CompareVersions(const char16_t* aStrA, const char16_t* aStrB) {
287   wchar_t* A2 = wcsdup(char16ptr_t(aStrA));
288   if (!A2) {
289     return 1;
290   }
291 
292   wchar_t* B2 = wcsdup(char16ptr_t(aStrB));
293   if (!B2) {
294     free(A2);
295     return 1;
296   }
297 
298   int32_t result;
299   wchar_t* a = A2;
300   wchar_t* b = B2;
301 
302   do {
303     VersionPartW va, vb;
304 
305     a = ParseVP(a, va);
306     b = ParseVP(b, vb);
307 
308     result = CompareVP(va, vb);
309     if (result) {
310       break;
311     }
312 
313   } while (a || b);
314 
315   free(A2);
316   free(B2);
317 
318   return result;
319 }
320 #endif
321 
CompareVersions(const char * aStrA,const char * aStrB)322 int32_t CompareVersions(const char* aStrA, const char* aStrB) {
323   char* A2 = strdup(aStrA);
324   if (!A2) {
325     return 1;
326   }
327 
328   char* B2 = strdup(aStrB);
329   if (!B2) {
330     free(A2);
331     return 1;
332   }
333 
334   int32_t result;
335   char* a = A2;
336   char* b = B2;
337 
338   do {
339     VersionPart va, vb;
340 
341     a = ParseVP(a, va);
342     b = ParseVP(b, vb);
343 
344     result = CompareVP(va, vb);
345     if (result) {
346       break;
347     }
348 
349   } while (a || b);
350 
351   free(A2);
352   free(B2);
353 
354   return result;
355 }
356 
357 }  // namespace mozilla
358