]>
Commit | Line | Data |
---|---|---|
d973a13d MT |
1 | /*############################################################################# |
2 | # # | |
3 | # Pakfire - The IPFire package management system # | |
4 | # Copyright (C) 2022 Pakfire development team # | |
5 | # # | |
6 | # This program is free software: you can redistribute it and/or modify # | |
7 | # it under the terms of the GNU General Public License as published by # | |
8 | # the Free Software Foundation, either version 3 of the License, or # | |
9 | # (at your option) any later version. # | |
10 | # # | |
11 | # This program is distributed in the hope that it will be useful, # | |
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of # | |
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # | |
14 | # GNU General Public License for more details. # | |
15 | # # | |
16 | # You should have received a copy of the GNU General Public License # | |
17 | # along with this program. If not, see <http://www.gnu.org/licenses/>. # | |
18 | # # | |
19 | #############################################################################*/ | |
20 | ||
21 | #include <errno.h> | |
73dab745 | 22 | #include <stdarg.h> |
d973a13d MT |
23 | #include <stdio.h> |
24 | #include <stdlib.h> | |
25 | #include <string.h> | |
26 | #include <time.h> | |
27 | ||
28 | #include <pakfire/string.h> | |
29 | ||
73dab745 MT |
30 | int __pakfire_string_format(char* s, const size_t length, const char* format, ...) { |
31 | va_list args; | |
cbedac26 | 32 | int r; |
73dab745 | 33 | |
cbedac26 | 34 | // Call __pakfire_string_vformat |
73dab745 | 35 | va_start(args, format); |
cbedac26 | 36 | r = __pakfire_string_vformat(s, length, format, args); |
73dab745 MT |
37 | va_end(args); |
38 | ||
cbedac26 MT |
39 | return r; |
40 | } | |
41 | ||
42 | int __pakfire_string_vformat(char* s, const size_t length, const char* format, va_list args) { | |
43 | // Write string to buffer | |
44 | const ssize_t required = vsnprintf(s, length, format, args); | |
45 | ||
73dab745 MT |
46 | // Catch any errors |
47 | if (required < 0) | |
48 | return 1; | |
49 | ||
50 | // Check if the entire string could be written | |
51 | if ((size_t)required >= length) { | |
52 | errno = ENOMEM; | |
53 | return 1; | |
54 | } | |
55 | ||
56 | // Success | |
57 | return 0; | |
58 | } | |
59 | ||
37c9c092 MT |
60 | int __pakfire_string_set(char* s, const size_t length, const char* value) { |
61 | // If value is NULL or an empty, we will overwrite the buffer with just zeros | |
62 | if (!value || !*value) { | |
63 | for (unsigned int i = 0; i < length; i++) | |
64 | s[i] = '\0'; | |
65 | ||
66 | return 0; | |
67 | } | |
68 | ||
69 | // Otherwise just copy | |
70 | return __pakfire_string_format(s, length, "%s", value); | |
71 | } | |
72 | ||
d973a13d MT |
73 | int pakfire_string_startswith(const char* s, const char* prefix) { |
74 | // Validate input | |
75 | if (!s || !prefix) { | |
76 | errno = EINVAL; | |
77 | return 1; | |
78 | } | |
79 | ||
80 | return !strncmp(s, prefix, strlen(prefix)); | |
81 | } | |
82 | ||
83 | int pakfire_string_endswith(const char* s, const char* suffix) { | |
84 | // Validate input | |
85 | if (!s || !suffix) { | |
86 | errno = EINVAL; | |
87 | return 1; | |
88 | } | |
89 | ||
90 | return !strcmp(s + strlen(s) - strlen(suffix), suffix); | |
91 | } | |
92 | ||
93 | int pakfire_string_matches(const char* s, const char* pattern) { | |
94 | // Validate input | |
95 | if (!s || !pattern) { | |
96 | errno = EINVAL; | |
97 | return 1; | |
98 | } | |
99 | ||
100 | return !!strstr(s, pattern); | |
101 | } | |
102 | ||
103 | int pakfire_string_partition(const char* s, const char* delim, char** s1, char** s2) { | |
104 | char* p = strstr(s, delim); | |
105 | ||
106 | // Delim was not found | |
107 | if (!p) { | |
108 | *s1 = NULL; | |
109 | *s2 = NULL; | |
110 | return 1; | |
111 | } | |
112 | ||
113 | // Length of string before delim | |
114 | size_t l = p - s; | |
115 | ||
116 | char* buffer = malloc(l + 1); | |
117 | if (!buffer) | |
118 | return 1; | |
119 | ||
120 | // Copy first part | |
121 | *s1 = memcpy(buffer, s, l); | |
122 | buffer[l] = '\0'; | |
123 | ||
124 | // Copy second part | |
125 | *s2 = strdup(p + strlen(delim)); | |
126 | ||
127 | return 0; | |
128 | } | |
129 | ||
130 | char* pakfire_string_replace(const char* s, const char* pattern, const char* repl) { | |
131 | // Return NULL on no input or no pattern | |
132 | if (!s || !pattern) { | |
133 | errno = EINVAL; | |
134 | return NULL; | |
135 | } | |
136 | ||
137 | // Replace with nothing when repl is NULL | |
138 | if (!repl) | |
139 | repl = ""; | |
140 | ||
141 | char* result = NULL; | |
142 | const char** cache = NULL; | |
143 | unsigned int count = 0; | |
144 | ||
145 | const size_t pattern_length = strlen(pattern); | |
146 | ||
147 | // Working pointer | |
148 | const char* p = s; | |
149 | ||
150 | // Find all occurrences of pattern and store their location | |
151 | while (1) { | |
152 | const char* needle = strstr(p, pattern); | |
153 | if (!needle) | |
154 | break; | |
155 | ||
156 | // Make space in the cache | |
157 | cache = reallocarray(cache, sizeof(*cache), count + 1); | |
158 | cache[count++] = needle; | |
159 | ||
160 | // Move p forward | |
161 | p = needle + pattern_length; | |
162 | } | |
163 | ||
164 | // Copy the string if no occurence was found | |
165 | if (count == 0) { | |
166 | result = strdup(s); | |
167 | goto ERROR; | |
168 | } | |
169 | ||
170 | // Store the end pointer | |
171 | cache = reallocarray(cache, sizeof(*cache), count + 1); | |
172 | cache[count] = s + strlen(s); | |
173 | ||
174 | const size_t repl_length = strlen(repl); | |
175 | ||
176 | // Determine the length of the final string | |
177 | const size_t length = strlen(s) + ((repl_length - pattern_length) * count); | |
178 | ||
179 | // Allocate enough memory for the result | |
180 | result = malloc(length + 1); | |
181 | if (!result) | |
182 | goto ERROR; | |
183 | ||
184 | // Reset p | |
185 | p = s; | |
186 | ||
187 | // Working pointer for the result | |
188 | char* r = result; | |
189 | ||
190 | // Copy everything up to the first match | |
191 | ssize_t l = cache[0] - s; | |
192 | memcpy(r, p, l); | |
193 | r += l; | |
194 | p += l; | |
195 | ||
196 | for (unsigned int i = 0; i < count; i++) { | |
197 | // Put replacement here | |
198 | memcpy(r, repl, repl_length); | |
199 | r += repl_length; | |
200 | p += pattern_length; | |
201 | ||
202 | // Determine the length between two matches | |
203 | l = cache[i+1] - (cache[i] + pattern_length); | |
204 | ||
205 | memcpy(r, p, l); | |
206 | r += l; | |
207 | p += l; | |
208 | } | |
209 | ||
210 | // Terminate the string | |
211 | result[length] = '\0'; | |
212 | ||
213 | ERROR: | |
214 | if (cache) | |
215 | free(cache); | |
216 | ||
217 | return result; | |
218 | } | |
219 | ||
220 | static unsigned int pakfire_chrcnt(const char* s, char delim) { | |
221 | size_t length = strlen(s); | |
222 | ||
223 | unsigned int count = 0; | |
224 | ||
225 | for (unsigned int i = 0; i < length; i++) | |
226 | if (s[i] == delim) | |
227 | count++; | |
228 | ||
229 | return count; | |
230 | } | |
231 | ||
232 | char** pakfire_string_split(const char* s, char delim) { | |
233 | char** array = NULL; | |
234 | ||
235 | if (!s) { | |
236 | errno = EINVAL; | |
237 | return NULL; | |
238 | } | |
239 | ||
240 | // Count how often we need to split | |
241 | unsigned int count = pakfire_chrcnt(s, delim) + 1; | |
242 | ||
243 | // Allocate array | |
244 | array = calloc(count + 1, sizeof(*array)); | |
245 | if (!array) | |
246 | return NULL; | |
247 | ||
248 | // Copy string to stack | |
249 | char* p = strdupa(s); | |
250 | if (!p) | |
251 | return NULL; | |
252 | ||
253 | unsigned int i = 0; | |
254 | while (*p) { | |
255 | char* e = strchr(p, delim); | |
256 | ||
257 | // Terminate the string | |
258 | if (e) | |
259 | *e = '\0'; | |
260 | ||
261 | // Add string to the array | |
262 | array[i++] = strdup(p); | |
263 | ||
264 | // End loop when we reached the end | |
265 | if (!e) | |
266 | break; | |
267 | ||
268 | // Or continue at the next line | |
269 | p = e + 1; | |
270 | } | |
271 | ||
272 | return array; | |
273 | } | |
274 | ||
275 | char* pakfire_string_join(char** list, const char* delim) { | |
276 | // Validate input | |
277 | if (!list || !delim) { | |
278 | errno = EINVAL; | |
279 | return NULL; | |
280 | } | |
281 | ||
282 | size_t length = 0; | |
283 | unsigned int elements = 0; | |
284 | ||
285 | // Count the number of elements and the total length | |
286 | for (char** item = list; *item; item++) { | |
287 | length += strlen(*item); | |
288 | elements++; | |
289 | } | |
290 | ||
291 | // Empty list? | |
292 | if (!elements) | |
293 | return NULL; | |
294 | ||
295 | // Add the delimiters | |
296 | length += strlen(delim) * (elements - 1); | |
297 | ||
298 | // Allocate the result string | |
299 | char* string = malloc(length + 1); | |
300 | if (!string) | |
301 | return NULL; | |
302 | ||
303 | // Pointer to where we are writing | |
304 | char* p = string; | |
305 | ||
306 | size_t bytes_left = length + 1; | |
307 | size_t bytes_written; | |
308 | ||
309 | for (char** item = list; *item; item++) { | |
310 | bytes_written = snprintf(p, bytes_left, "%s", *item); | |
311 | ||
312 | bytes_left -= bytes_written; | |
313 | p += bytes_written; | |
314 | ||
315 | // Write the delimiter | |
316 | if (bytes_left) { | |
317 | bytes_written = snprintf(p, bytes_left, "%s", delim); | |
318 | ||
319 | bytes_left -= bytes_written; | |
320 | p += bytes_written; | |
321 | } | |
322 | } | |
323 | ||
324 | return string; | |
325 | } | |
326 | ||
327 | int __pakfire_format_size(char* dst, size_t length, double value) { | |
328 | const char* units[] = { | |
329 | "%.0f ", | |
330 | "%.0fk", | |
331 | "%.1fM", | |
332 | "%.1fG", | |
333 | "%.1fT", | |
334 | NULL | |
335 | }; | |
336 | const char** unit = units; | |
337 | ||
338 | while (*(unit + 1) && value >= 1024.0) { | |
339 | value /= 1024.0; | |
340 | unit++; | |
341 | } | |
342 | ||
1cd3e864 | 343 | return __pakfire_string_format(dst, length, *unit, value); |
d973a13d MT |
344 | } |
345 | ||
346 | int pakfire_format_speed(char* dst, size_t length, double value) { | |
347 | const char* units[] = { | |
348 | "%4.0fB/s", | |
349 | "%4.0fkB/s", | |
350 | "%4.1fMB/s", | |
351 | "%4.1fGB/s", | |
352 | "%4.1fTB/s", | |
353 | NULL | |
354 | }; | |
355 | const char** unit = units; | |
356 | ||
357 | while (*(unit + 1) && value >= 1024.0) { | |
358 | value /= 1024.0; | |
359 | unit++; | |
360 | } | |
361 | ||
1cd3e864 | 362 | return __pakfire_string_format(dst, length, *unit, value); |
d973a13d MT |
363 | } |
364 | ||
5cf4a7a1 MT |
365 | int __pakfire_strftime(char* buffer, const size_t length, |
366 | const char* format, const time_t t) { | |
d973a13d | 367 | struct tm* tm = gmtime(&t); |
5cf4a7a1 | 368 | int r; |
d973a13d | 369 | |
5cf4a7a1 MT |
370 | // Format time |
371 | r = strftime(buffer, length, format, tm); | |
372 | if (r == 0) | |
373 | return 1; | |
d973a13d | 374 | |
5cf4a7a1 | 375 | return 0; |
d973a13d | 376 | } |
d973a13d | 377 | |
5cf4a7a1 | 378 | int __pakfire_strftime_now(char* buffer, size_t length, const char* format) { |
d973a13d MT |
379 | // Fetch the current time |
380 | const time_t t = time(NULL); | |
381 | if (t < 0) | |
382 | return 1; | |
383 | ||
5cf4a7a1 | 384 | return __pakfire_strftime(buffer, length, format, t); |
d973a13d | 385 | } |
528b1f1c | 386 | |
b91139b4 MT |
387 | int __pakfire_format_time(char* buffer, const size_t length, const time_t t) { |
388 | const char* format = NULL; | |
389 | ||
390 | // Values smaller than zero for t are invalid | |
391 | if (t < 0) | |
392 | return 1; | |
393 | ||
394 | if (t >= 86400) | |
395 | format = "%dd%Hh%Mm"; | |
396 | else if (t >= 3600) | |
7c8cff9a | 397 | format = "%Hh%Mm%Ss"; |
b91139b4 MT |
398 | else if (t >= 60) |
399 | format = "%Mm%Ss"; | |
400 | else | |
401 | format = "%Ss"; | |
402 | ||
403 | return __pakfire_strftime(buffer, length, format, t); | |
404 | } | |
405 | ||
528b1f1c MT |
406 | size_t pakfire_string_parse_bytes(const char* s) { |
407 | const char* units = "kMGT"; | |
408 | char* unit = NULL; | |
409 | ||
410 | // Read the numeric value | |
411 | size_t bytes = strtoul(s, &unit, 10); | |
412 | ||
413 | // Return bytes if there is no unit | |
414 | if (!unit || !*unit) | |
415 | return bytes; | |
416 | ||
417 | // Is the unit of the correct length? | |
418 | const size_t length = strlen(unit); | |
419 | if (length > 1) | |
420 | goto ERROR; | |
421 | ||
422 | // Walk through all units and find a match | |
423 | for (const char* u = units; *u; u++) { | |
424 | bytes *= 1024; | |
425 | ||
426 | // End if the unit matches | |
427 | if (*unit == *u) | |
428 | return bytes; | |
429 | } | |
430 | ||
431 | ERROR: | |
432 | errno = EINVAL; | |
433 | return 0; | |
434 | } | |
8674de39 MT |
435 | |
436 | int pakfire_string_is_url(const char* s) { | |
437 | static const char* known_schemas[] = { | |
438 | "https://", | |
439 | "http://", | |
440 | "file://", | |
441 | NULL, | |
442 | }; | |
443 | ||
444 | for (const char** schema = known_schemas; *schema; schema++) { | |
445 | if (pakfire_string_startswith(s, *schema)) | |
446 | return 1; | |
447 | } | |
448 | ||
449 | return 0; | |
450 | } |