1 /*********************************************************
2 *                                                        *
3 * (c) 2011-2015 Marko Lindqvist                          *
4 *                                                        *
5 * Version contributed to Freeciv has been made available *
6 * under GNU Public License; either version 2, or         *
7 * (at your option) any later version.                    *
8 *                                                        *
9 *********************************************************/
10 
11 #ifdef HAVE_CONFIG_H
12 #include <fc_config.h>
13 #endif
14 
15 #include <assert.h>
16 #include <ctype.h>
17 #include <stdbool.h>
18 #include <stdlib.h>
19 #include <string.h>
20 
21 #include "cvercmp.h"
22 
23 static char **cvercmp_ver_tokenize(const char *ver);
24 static int cvercmp_next_token(const char *str);
25 static char **cvercmp_ver_subtokenize(const char *ver);
26 static int cvercmp_next_subtoken(const char *str);
27 
28 enum cvercmp_prever
29 {
30   CVERCMP_PRE_DEV,
31   CVERCMP_PRE_ALPHA,
32   CVERCMP_PRE_BETA,
33   CVERCMP_PRE_PRE,
34   CVERCMP_PRE_RC,
35   CVERCMP_PRE_NONE
36 };
37 
38 struct preverstr
39 {
40   const char *str;
41   enum cvercmp_prever prever;
42 };
43 
44 struct preverstr preverstrs[] =
45 {
46   { "dev", CVERCMP_PRE_DEV },
47   { "alpha", CVERCMP_PRE_ALPHA },
48   { "beta", CVERCMP_PRE_BETA },
49   { "pre", CVERCMP_PRE_PRE },
50   { "candidate", CVERCMP_PRE_RC },
51   { "rc", CVERCMP_PRE_RC },
52   { NULL, CVERCMP_PRE_NONE }
53 };
54 
55 static enum cvercmp_prever cvercmp_parse_prever(const char *ver);
56 
cvercmp(const char * ver1,const char * ver2,enum cvercmp_type type)57 bool cvercmp(const char *ver1, const char *ver2, enum cvercmp_type type)
58 {
59   typedef bool (*cmpfunc)(const char *ver1, const char *ver2);
60 
61   cmpfunc cmpfuncs[] =
62   {
63     cvercmp_equal,
64     cvercmp_nonequal,
65     cvercmp_greater,
66     cvercmp_lesser,
67     cvercmp_min,
68     cvercmp_max
69   };
70 
71   return cmpfuncs[type](ver1, ver2);
72 }
73 
cvercmp_equal(const char * ver1,const char * ver2)74 bool cvercmp_equal(const char *ver1, const char *ver2)
75 {
76   return cvercmp_cmp(ver1, ver2) == CVERCMP_EQUAL;
77 }
78 
cvercmp_nonequal(const char * ver1,const char * ver2)79 bool cvercmp_nonequal(const char *ver1, const char *ver2)
80 {
81   return cvercmp_cmp(ver1, ver2) != CVERCMP_EQUAL;
82 }
83 
cvercmp_greater(const char * ver1,const char * ver2)84 bool cvercmp_greater(const char *ver1, const char *ver2)
85 {
86   return cvercmp_cmp(ver1, ver2) == CVERCMP_GREATER;
87 }
88 
cvercmp_lesser(const char * ver1,const char * ver2)89 bool cvercmp_lesser(const char *ver1, const char *ver2)
90 {
91   return cvercmp_cmp(ver1, ver2) == CVERCMP_LESSER;
92 }
93 
cvercmp_min(const char * ver1,const char * ver2)94 bool cvercmp_min(const char *ver1, const char *ver2)
95 {
96   return cvercmp_cmp(ver1, ver2) != CVERCMP_LESSER;
97 }
98 
cvercmp_max(const char * ver1,const char * ver2)99 bool cvercmp_max(const char *ver1, const char *ver2)
100 {
101   return cvercmp_cmp(ver1, ver2) != CVERCMP_GREATER;
102 }
103 
cvercmp_tokens(const char * token1,const char * token2)104 static enum cvercmp_type cvercmp_tokens(const char *token1, const char *token2)
105 {
106   char **t1 = cvercmp_ver_subtokenize(token1);
107   char **t2 = cvercmp_ver_subtokenize(token2);
108   int i;
109   enum cvercmp_type result = CVERCMP_EQUAL;
110   bool solution = false;
111 
112   for (i = 0; (t1[i] != NULL && t2[i] != NULL) && !solution; i++) {
113     int t1c0 = t1[i][0];
114     int t2c0 = t2[i][0];
115     bool d1 = isdigit(t1c0);
116     bool d2 = isdigit(t2c0);
117 
118     if (d1 && !d2) {
119       /* Numbers are always greater than alpha, so don't need to check prever */
120       result = CVERCMP_GREATER;
121       solution = true;
122     } else if (!d1 && d2) {
123       result = CVERCMP_LESSER;
124       solution = true;
125     } else if (d1) {
126       int val1 = atoi(t1[i]);
127       int val2 = atoi(t2[i]);
128 
129       if (val1 > val2) {
130         result = CVERCMP_GREATER;
131         solution = true;
132       } else if (val1 < val2) {
133         result = CVERCMP_LESSER;
134         solution = true;
135       }
136     } else {
137       int alphacmp = strcasecmp(t1[i], t2[i]);
138 
139       if (alphacmp) {
140         enum cvercmp_prever pre1 = cvercmp_parse_prever(t1[i]);
141         enum cvercmp_prever pre2 = cvercmp_parse_prever(t2[i]);
142 
143         if (pre1 > pre2) {
144           result = CVERCMP_GREATER;
145         } else if (pre1 < pre2) {
146           result = CVERCMP_LESSER;
147         } else if (alphacmp < 0) {
148           result = CVERCMP_LESSER;
149         } else if (alphacmp > 0) {
150           result = CVERCMP_GREATER;
151         } else {
152           assert(false);
153         }
154 
155         solution = true;
156       }
157     }
158   }
159 
160   if (!solution) {
161     /* Got to the end of one token.
162      * Longer token is greater, unless the next subtoken is
163      * a prerelease string. */
164     if (t1[i] != NULL && t2[i] == NULL) {
165       if (cvercmp_parse_prever(t1[i]) != CVERCMP_PRE_NONE) {
166         result = CVERCMP_LESSER;
167       } else {
168         result = CVERCMP_GREATER;
169       }
170     } else if (t1[i] == NULL && t2[i] != NULL) {
171       if (cvercmp_parse_prever(t2[i]) != CVERCMP_PRE_NONE) {
172         result = CVERCMP_GREATER;
173       } else {
174         result = CVERCMP_LESSER;
175       }
176     } else {
177       /* Both ran out at the same time. */
178       result = CVERCMP_EQUAL;
179     }
180   }
181 
182   for (i = 0; t1[i] != NULL; i++) {
183     free(t1[i]);
184   }
185   for (i = 0; t2[i] != NULL; i++) {
186     free(t2[i]);
187   }
188   free(t1);
189   free(t2);
190 
191   return result;
192 }
193 
cvercmp_cmp(const char * ver1,const char * ver2)194 enum cvercmp_type cvercmp_cmp(const char *ver1, const char *ver2)
195 {
196   enum cvercmp_type result = CVERCMP_EQUAL;
197   bool solution = false;
198   int i;
199   char **tokens1 = cvercmp_ver_tokenize(ver1);
200   char **tokens2 = cvercmp_ver_tokenize(ver2);
201 
202   for (i = 0; (tokens1[i] != NULL && tokens2[i] != NULL) && !solution; i++) {
203     if (strcasecmp(tokens1[i], tokens2[i])) {
204       /* Parts are not equal */
205       result = cvercmp_tokens(tokens1[i], tokens2[i]);
206       solution = true;
207     }
208   }
209 
210   if (!solution) {
211     /* Ran out of tokens in one string. */
212     result = cvercmp_tokens(tokens1[i], tokens2[i]);
213   }
214 
215   for (i = 0; tokens1[i] != NULL; i++) {
216     free(tokens1[i]);
217   }
218   for (i = 0; tokens2[i] != NULL; i++) {
219     free(tokens2[i]);
220   }
221   free(tokens1);
222   free(tokens2);
223 
224   return result;
225 }
226 
cvercmp_ver_tokenize(const char * ver)227 static char **cvercmp_ver_tokenize(const char *ver)
228 {
229   int num = 0;
230   int idx;
231   int verlen = strlen(ver);
232   char **tokens;
233   int i;
234   int tokenlen;
235 
236   for (idx = 0; idx < verlen; idx += cvercmp_next_token(ver + idx) + 1) {
237     num++;
238   }
239 
240   tokens = calloc(sizeof(char *), num + 1);
241 
242   for (i = 0, idx = 0; i < num; i++) {
243     tokenlen = cvercmp_next_token(ver + idx);
244     tokens[i] = malloc(sizeof(char) * (tokenlen + 1));
245     strncpy(tokens[i], ver + idx, tokenlen);
246     tokens[i][tokenlen] = '\0';
247     idx += tokenlen + 1; /* Skip character separating tokens. */
248   }
249 
250   return tokens;
251 }
252 
cvercmp_next_token(const char * str)253 static int cvercmp_next_token(const char *str)
254 {
255   int i;
256 
257   for (i = 0;
258        str[i] != '\0' && str[i] != '.' && str[i] != '-' && str[i] != '_';
259        i++) {
260   }
261 
262   return i;
263 }
264 
cvercmp_ver_subtokenize(const char * ver)265 static char **cvercmp_ver_subtokenize(const char *ver)
266 {
267   int num = 0;
268   int idx;
269   int verlen;
270   char **tokens;
271   int i;
272   int tokenlen;
273 
274   /* Treat NULL string as empty string */
275   if (!ver) {
276       ver = "";
277   }
278   verlen = strlen(ver);
279 
280   for (idx = 0; idx < verlen; idx += cvercmp_next_subtoken(ver + idx)) {
281     num++;
282   }
283 
284   tokens = calloc(sizeof(char *), num + 1);
285 
286   for (i = 0, idx = 0; i < num; i++) {
287     tokenlen = cvercmp_next_subtoken(ver + idx);
288     tokens[i] = malloc(sizeof(char) * (tokenlen + 1));
289     if (tokenlen > 0) {
290       strncpy(tokens[i], ver + idx, tokenlen);
291     }
292     tokens[i][tokenlen] = '\0';
293     idx += tokenlen;
294   }
295 
296   return tokens;
297 }
298 
cvercmp_next_subtoken(const char * str)299 static int cvercmp_next_subtoken(const char *str)
300 {
301   int i;
302   bool alpha;
303   int sc0 = str[0];
304   int sci;
305 
306   if (isdigit(sc0)) {
307     alpha = false;
308   } else {
309     alpha = true;
310   }
311 
312   for (i = 0; sci = str[i],
313          sci != '\0'
314          && ((alpha && !isdigit(sci)) || (!alpha && isdigit(sci)));
315        i++) {
316   }
317 
318   return i;
319 }
320 
cvercmp_parse_prever(const char * ver)321 static enum cvercmp_prever cvercmp_parse_prever(const char *ver)
322 {
323   int i;
324 
325   for (i = 0; preverstrs[i].str != NULL; i++) {
326     if (!strcasecmp(ver, preverstrs[i].str)) {
327       return preverstrs[i].prever;
328     }
329   }
330 
331   return CVERCMP_PRE_NONE;
332 }
333