1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2 
3 #ifndef SD_BOOT
4 #include <ctype.h>
5 
6 #include "macro.h"
7 #endif
8 #include "string-util-fundamental.h"
9 
startswith(const sd_char * s,const sd_char * prefix)10 sd_char *startswith(const sd_char *s, const sd_char *prefix) {
11         size_t l;
12 
13         assert(s);
14         assert(prefix);
15 
16         l = strlen(prefix);
17         if (!strneq(s, prefix, l))
18                 return NULL;
19 
20         return (sd_char*) s + l;
21 }
22 
23 #ifndef SD_BOOT
startswith_no_case(const sd_char * s,const sd_char * prefix)24 sd_char *startswith_no_case(const sd_char *s, const sd_char *prefix) {
25         size_t l;
26 
27         assert(s);
28         assert(prefix);
29 
30         l = strlen(prefix);
31         if (!strncaseeq(s, prefix, l))
32                 return NULL;
33 
34         return (sd_char*) s + l;
35 }
36 #endif
37 
endswith(const sd_char * s,const sd_char * postfix)38 sd_char* endswith(const sd_char *s, const sd_char *postfix) {
39         size_t sl, pl;
40 
41         assert(s);
42         assert(postfix);
43 
44         sl = strlen(s);
45         pl = strlen(postfix);
46 
47         if (pl == 0)
48                 return (sd_char*) s + sl;
49 
50         if (sl < pl)
51                 return NULL;
52 
53         if (strcmp(s + sl - pl, postfix) != 0)
54                 return NULL;
55 
56         return (sd_char*) s + sl - pl;
57 }
58 
endswith_no_case(const sd_char * s,const sd_char * postfix)59 sd_char* endswith_no_case(const sd_char *s, const sd_char *postfix) {
60         size_t sl, pl;
61 
62         assert(s);
63         assert(postfix);
64 
65         sl = strlen(s);
66         pl = strlen(postfix);
67 
68         if (pl == 0)
69                 return (sd_char*) s + sl;
70 
71         if (sl < pl)
72                 return NULL;
73 
74         if (strcasecmp(s + sl - pl, postfix) != 0)
75                 return NULL;
76 
77         return (sd_char*) s + sl - pl;
78 }
79 
80 #ifdef SD_BOOT
isdigit(sd_char a)81 static sd_bool isdigit(sd_char a) {
82         return a >= '0' && a <= '9';
83 }
84 #endif
85 
is_alpha(sd_char a)86 static sd_bool is_alpha(sd_char a) {
87         /* Locale independent version of isalpha(). */
88         return (a >= 'a' && a <= 'z') || (a >= 'A' && a <= 'Z');
89 }
90 
is_valid_version_char(sd_char a)91 static sd_bool is_valid_version_char(sd_char a) {
92         return isdigit(a) || is_alpha(a) || IN_SET(a, '~', '-', '^', '.');
93 }
94 
strverscmp_improved(const sd_char * a,const sd_char * b)95 sd_int strverscmp_improved(const sd_char *a, const sd_char *b) {
96 
97         /* This is based on RPM's rpmvercmp(). But this explicitly handles '-' and '.', as we usually
98          * want to directly compare strings which contain both version and release; e.g.
99          * '247.2-3.1.fc33.x86_64' or '5.11.0-0.rc5.20210128git76c057c84d28.137.fc34'.
100          * Unlike rpmvercmp(), this distiguishes e.g. 123a and 123.a, and 123a is newer.
101          *
102          * This splits the input strings into segments. Each segment is numeric or alpha, and may be
103          * prefixed with the following:
104          *  '~' : used for pre-releases, a segment prefixed with this is the oldest,
105          *  '-' : used for the separator between version and release,
106          *  '^' : used for patched releases, a segment with this is newer than one with '-'.
107          *  '.' : used for point releases.
108          * Note, no prefix segment is the newest. All non-supported characters are dropped, and
109          * handled as a separator of segments, e.g., 123_a is equivalent to 123a.
110          *
111          * By using this, version strings can be sorted like following:
112          *  (older) 122.1
113          *     ^    123~rc1-1
114          *     |    123
115          *     |    123-a
116          *     |    123-a.1
117          *     |    123-1
118          *     |    123-1.1
119          *     |    123^post1
120          *     |    123.a-1
121          *     |    123.1-1
122          *     v    123a-1
123          *  (newer) 124-1
124          */
125 
126         if (isempty(a) || isempty(b))
127                 return strcmp_ptr(a, b);
128 
129         for (;;) {
130                 const sd_char *aa, *bb;
131                 sd_int r;
132 
133                 /* Drop leading invalid characters. */
134                 while (*a != '\0' && !is_valid_version_char(*a))
135                         a++;
136                 while (*b != '\0' && !is_valid_version_char(*b))
137                         b++;
138 
139                 /* Handle '~'. Used for pre-releases, e.g. 123~rc1, or 4.5~alpha1 */
140                 if (*a == '~' || *b == '~') {
141                         /* The string prefixed with '~' is older. */
142                         r = CMP(*a != '~', *b != '~');
143                         if (r != 0)
144                                 return r;
145 
146                         /* Now both strings are prefixed with '~'. Compare remaining strings. */
147                         a++;
148                         b++;
149                 }
150 
151                 /* If at least one string reaches the end, then longer is newer.
152                  * Note that except for '~' prefixed segments, a string has more segments is newer.
153                  * So, this check must be after the '~' check. */
154                 if (*a == '\0' || *b == '\0')
155                         return CMP(*a, *b);
156 
157                 /* Handle '-', which separates version and release, e.g 123.4-3.1.fc33.x86_64 */
158                 if (*a == '-' || *b == '-') {
159                         /* The string prefixed with '-' is older (e.g., 123-9 vs 123.1-1) */
160                         r = CMP(*a != '-', *b != '-');
161                         if (r != 0)
162                                 return r;
163 
164                         a++;
165                         b++;
166                 }
167 
168                 /* Handle '^'. Used for patched release. */
169                 if (*a == '^' || *b == '^') {
170                         r = CMP(*a != '^', *b != '^');
171                         if (r != 0)
172                                 return r;
173 
174                         a++;
175                         b++;
176                 }
177 
178                 /* Handle '.'. Used for point releases. */
179                 if (*a == '.' || *b == '.') {
180                         r = CMP(*a != '.', *b != '.');
181                         if (r != 0)
182                                 return r;
183 
184                         a++;
185                         b++;
186                 }
187 
188                 if (isdigit(*a) || isdigit(*b)) {
189                         /* Skip leading '0', to make 00123 equivalent to 123. */
190                         while (*a == '0')
191                                 a++;
192                         while (*b == '0')
193                                 b++;
194 
195                         /* Find the leading numeric segments. One may be an empty string. So,
196                          * numeric segments are always newer than alpha segments. */
197                         for (aa = a; isdigit(*aa); aa++)
198                                 ;
199                         for (bb = b; isdigit(*bb); bb++)
200                                 ;
201 
202                         /* To compare numeric segments without parsing their values, first compare the
203                          * lengths of the segments. Eg. 12345 vs 123, longer is newer. */
204                         r = CMP(aa - a, bb - b);
205                         if (r != 0)
206                                 return r;
207 
208                         /* Then, compare them as strings. */
209                         r = strncmp(a, b, aa - a);
210                         if (r != 0)
211                                 return r;
212                 } else {
213                         /* Find the leading non-numeric segments. */
214                         for (aa = a; is_alpha(*aa); aa++)
215                                 ;
216                         for (bb = b; is_alpha(*bb); bb++)
217                                 ;
218 
219                         /* Note that the segments are usually not NUL-terminated. */
220                         r = strncmp(a, b, MIN(aa - a, bb - b));
221                         if (r != 0)
222                                 return r;
223 
224                         /* Longer is newer, e.g. abc vs abcde. */
225                         r = CMP(aa - a, bb - b);
226                         if (r != 0)
227                                 return r;
228                 }
229 
230                 /* The current segments are equivalent. Let's compare the next one. */
231                 a = aa;
232                 b = bb;
233         }
234 }
235