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