/src/fwupd/libfwupdplugin/fu-string.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright 2017 Richard Hughes <richard@hughsie.com> |
3 | | * |
4 | | * SPDX-License-Identifier: LGPL-2.1-or-later |
5 | | */ |
6 | | |
7 | 137k | #define G_LOG_DOMAIN "FuCommon" |
8 | | |
9 | | #include "config.h" |
10 | | |
11 | | #include "fu-byte-array.h" |
12 | | #include "fu-chunk-array.h" |
13 | | #include "fu-mem.h" |
14 | | #include "fu-partial-input-stream.h" |
15 | | #include "fu-string.h" |
16 | | |
17 | | /** |
18 | | * fu_strtoull: |
19 | | * @str: a string, e.g. `0x1234` |
20 | | * @value: (out) (nullable): parsed value |
21 | | * @min: minimum acceptable value, typically 0 |
22 | | * @max: maximum acceptable value, typically G_MAXUINT64 |
23 | | * @base: default log base, usually %FU_INTEGER_BASE_AUTO |
24 | | * @error: (nullable): optional return location for an error |
25 | | * |
26 | | * Converts a string value to an integer. If the @value is prefixed with `0x` then the base is |
27 | | * set to 16 automatically. |
28 | | * |
29 | | * Returns: %TRUE if the value was parsed correctly, or %FALSE for error |
30 | | * |
31 | | * Since: 2.0.0 |
32 | | **/ |
33 | | gboolean |
34 | | fu_strtoull(const gchar *str, |
35 | | guint64 *value, |
36 | | guint64 min, |
37 | | guint64 max, |
38 | | FuIntegerBase base, |
39 | | GError **error) |
40 | 14.9M | { |
41 | 14.9M | gchar *endptr = NULL; |
42 | 14.9M | guint64 value_tmp; |
43 | | |
44 | | /* sanity check */ |
45 | 14.9M | if (str == NULL) { |
46 | 3 | g_set_error_literal(error, |
47 | 3 | FWUPD_ERROR, |
48 | 3 | FWUPD_ERROR_INVALID_DATA, |
49 | 3 | "cannot parse NULL"); |
50 | 3 | return FALSE; |
51 | 3 | } |
52 | | |
53 | | /* detect hex */ |
54 | 14.9M | if (base == FU_INTEGER_BASE_AUTO) { |
55 | 1.27k | if (g_str_has_prefix(str, "0x")) { |
56 | 307 | str += 2; |
57 | 307 | base = FU_INTEGER_BASE_16; |
58 | 971 | } else { |
59 | 971 | base = FU_INTEGER_BASE_10; |
60 | 971 | } |
61 | 14.9M | } else if (base == FU_INTEGER_BASE_16 && g_str_has_prefix(str, "0x")) { |
62 | 2.69k | str += 2; |
63 | 14.9M | } else if (base == FU_INTEGER_BASE_10 && g_str_has_prefix(str, "0x")) { |
64 | 0 | g_set_error_literal(error, |
65 | 0 | FWUPD_ERROR, |
66 | 0 | FWUPD_ERROR_INVALID_DATA, |
67 | 0 | "cannot parse 0x-prefixed base-10 string"); |
68 | 0 | return FALSE; |
69 | 0 | } |
70 | | |
71 | | /* convert */ |
72 | 14.9M | value_tmp = g_ascii_strtoull(str, &endptr, base); /* nocheck:blocked */ |
73 | 14.9M | if ((gsize)(endptr - str) != strlen(str) && *endptr != '\n') { |
74 | 627 | g_set_error_literal(error, |
75 | 627 | FWUPD_ERROR, |
76 | 627 | FWUPD_ERROR_INVALID_DATA, |
77 | 627 | "cannot parse datastream"); |
78 | 627 | return FALSE; |
79 | 627 | } |
80 | | |
81 | | /* overflow check */ |
82 | 14.9M | if (value_tmp == G_MAXUINT64) { |
83 | 41 | g_set_error_literal(error, |
84 | 41 | FWUPD_ERROR, |
85 | 41 | FWUPD_ERROR_INVALID_DATA, |
86 | 41 | "parsing datastream caused overflow"); |
87 | 41 | return FALSE; |
88 | 41 | } |
89 | | |
90 | | /* range check */ |
91 | 14.9M | if (value_tmp < min) { |
92 | 0 | g_set_error(error, |
93 | 0 | FWUPD_ERROR, |
94 | 0 | FWUPD_ERROR_INVALID_DATA, |
95 | 0 | "value %" G_GUINT64_FORMAT " was below minimum %" G_GUINT64_FORMAT, |
96 | 0 | value_tmp, |
97 | 0 | min); |
98 | 0 | return FALSE; |
99 | 0 | } |
100 | 14.9M | if (value_tmp > max) { |
101 | 394 | g_set_error(error, |
102 | 394 | FWUPD_ERROR, |
103 | 394 | FWUPD_ERROR_INVALID_DATA, |
104 | 394 | "value %" G_GUINT64_FORMAT " was above maximum %" G_GUINT64_FORMAT, |
105 | 394 | value_tmp, |
106 | 394 | max); |
107 | 394 | return FALSE; |
108 | 394 | } |
109 | | |
110 | | /* success */ |
111 | 14.9M | if (value != NULL) |
112 | 14.9M | *value = value_tmp; |
113 | 14.9M | return TRUE; |
114 | 14.9M | } |
115 | | |
116 | | /** |
117 | | * fu_strtoll: |
118 | | * @str: a string, e.g. `0x1234`, `-12345` |
119 | | * @value: (out) (nullable): parsed value |
120 | | * @min: minimum acceptable value, typically 0 |
121 | | * @max: maximum acceptable value, typically G_MAXINT64 |
122 | | * @base: default log base, usually %FU_INTEGER_BASE_AUTO |
123 | | * @error: (nullable): optional return location for an error |
124 | | * |
125 | | * Converts a string value to an integer. Values are assumed base 10, unless |
126 | | * prefixed with "0x" where they are parsed as base 16. |
127 | | * |
128 | | * Returns: %TRUE if the value was parsed correctly, or %FALSE for error |
129 | | * |
130 | | * Since: 2.0.0 |
131 | | **/ |
132 | | gboolean |
133 | | fu_strtoll(const gchar *str, |
134 | | gint64 *value, |
135 | | gint64 min, |
136 | | gint64 max, |
137 | | FuIntegerBase base, |
138 | | GError **error) |
139 | 0 | { |
140 | 0 | gchar *endptr = NULL; |
141 | 0 | gint64 value_tmp; |
142 | | |
143 | | /* sanity check */ |
144 | 0 | if (str == NULL) { |
145 | 0 | g_set_error_literal(error, |
146 | 0 | FWUPD_ERROR, |
147 | 0 | FWUPD_ERROR_INVALID_DATA, |
148 | 0 | "cannot parse NULL"); |
149 | 0 | return FALSE; |
150 | 0 | } |
151 | | |
152 | | /* detect hex */ |
153 | 0 | if (base == FU_INTEGER_BASE_AUTO) { |
154 | 0 | if (g_str_has_prefix(str, "0x")) { |
155 | 0 | str += 2; |
156 | 0 | base = FU_INTEGER_BASE_16; |
157 | 0 | } else { |
158 | 0 | base = FU_INTEGER_BASE_10; |
159 | 0 | } |
160 | 0 | } else if (base == FU_INTEGER_BASE_16 && g_str_has_prefix(str, "0x")) { |
161 | 0 | str += 2; |
162 | 0 | } else if (base == FU_INTEGER_BASE_10 && g_str_has_prefix(str, "0x")) { |
163 | 0 | g_set_error_literal(error, |
164 | 0 | FWUPD_ERROR, |
165 | 0 | FWUPD_ERROR_INVALID_DATA, |
166 | 0 | "cannot parse 0x-prefixed base-10 string"); |
167 | 0 | return FALSE; |
168 | 0 | } |
169 | | |
170 | | /* convert */ |
171 | 0 | value_tmp = g_ascii_strtoll(str, &endptr, base); /* nocheck:blocked */ |
172 | 0 | if ((gsize)(endptr - str) != strlen(str) && *endptr != '\n') { |
173 | 0 | g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_DATA, "cannot parse %s", str); |
174 | 0 | return FALSE; |
175 | 0 | } |
176 | | |
177 | | /* overflow check */ |
178 | 0 | if (value_tmp == G_MAXINT64) { |
179 | 0 | g_set_error(error, |
180 | 0 | FWUPD_ERROR, |
181 | 0 | FWUPD_ERROR_INVALID_DATA, |
182 | 0 | "cannot parse %s as caused overflow", |
183 | 0 | str); |
184 | 0 | return FALSE; |
185 | 0 | } |
186 | | |
187 | | /* range check */ |
188 | 0 | if (value_tmp < min) { |
189 | 0 | g_set_error(error, |
190 | 0 | FWUPD_ERROR, |
191 | 0 | FWUPD_ERROR_INVALID_DATA, |
192 | 0 | "value %" G_GINT64_FORMAT " was below minimum %" G_GINT64_FORMAT, |
193 | 0 | value_tmp, |
194 | 0 | min); |
195 | 0 | return FALSE; |
196 | 0 | } |
197 | 0 | if (value_tmp > max) { |
198 | 0 | g_set_error(error, |
199 | 0 | FWUPD_ERROR, |
200 | 0 | FWUPD_ERROR_INVALID_DATA, |
201 | 0 | "value %" G_GINT64_FORMAT " was above maximum %" G_GINT64_FORMAT, |
202 | 0 | value_tmp, |
203 | 0 | max); |
204 | 0 | return FALSE; |
205 | 0 | } |
206 | | |
207 | | /* success */ |
208 | 0 | if (value != NULL) |
209 | 0 | *value = value_tmp; |
210 | 0 | return TRUE; |
211 | 0 | } |
212 | | |
213 | | /** |
214 | | * fu_strtobool: |
215 | | * @str: a string, e.g. `true` |
216 | | * @value: (out) (nullable): parsed value |
217 | | * @error: (nullable): optional return location for an error |
218 | | * |
219 | | * Converts a string value to a boolean. Only `true` and `false` are accepted values. |
220 | | * |
221 | | * Returns: %TRUE if the value was parsed correctly, or %FALSE for error |
222 | | * |
223 | | * Since: 1.8.2 |
224 | | **/ |
225 | | gboolean |
226 | | fu_strtobool(const gchar *str, gboolean *value, GError **error) |
227 | 0 | { |
228 | | /* sanity check */ |
229 | 0 | if (str == NULL) { |
230 | 0 | g_set_error_literal(error, |
231 | 0 | FWUPD_ERROR, |
232 | 0 | FWUPD_ERROR_INVALID_DATA, |
233 | 0 | "cannot parse NULL"); |
234 | 0 | return FALSE; |
235 | 0 | } |
236 | | |
237 | | /* be super strict */ |
238 | 0 | if (g_strcmp0(str, "true") == 0) { |
239 | 0 | if (value != NULL) |
240 | 0 | *value = TRUE; |
241 | 0 | return TRUE; |
242 | 0 | } |
243 | 0 | if (g_strcmp0(str, "false") == 0) { |
244 | 0 | if (value != NULL) |
245 | 0 | *value = FALSE; |
246 | 0 | return TRUE; |
247 | 0 | } |
248 | | |
249 | | /* invalid */ |
250 | 0 | g_set_error(error, |
251 | 0 | FWUPD_ERROR, |
252 | 0 | FWUPD_ERROR_INVALID_DATA, |
253 | 0 | "cannot parse %s as boolean, expected true|false", |
254 | 0 | str); |
255 | 0 | return FALSE; |
256 | 0 | } |
257 | | |
258 | | /** |
259 | | * fu_strstrip: |
260 | | * @str: a string, e.g. ` test ` |
261 | | * |
262 | | * Removes leading and trailing whitespace from a constant string. |
263 | | * |
264 | | * Returns: newly allocated string |
265 | | * |
266 | | * Since: 1.8.2 |
267 | | **/ |
268 | | gchar * |
269 | | fu_strstrip(const gchar *str) |
270 | 0 | { |
271 | 0 | guint head = G_MAXUINT; |
272 | 0 | guint tail = 0; |
273 | |
|
274 | 0 | g_return_val_if_fail(str != NULL, NULL); |
275 | | |
276 | | /* find first non-space char */ |
277 | 0 | for (guint i = 0; str[i] != '\0'; i++) { |
278 | 0 | if (str[i] != ' ') { |
279 | 0 | head = i; |
280 | 0 | break; |
281 | 0 | } |
282 | 0 | } |
283 | 0 | if (head == G_MAXUINT) |
284 | 0 | return g_strdup(""); |
285 | | |
286 | | /* find last non-space char */ |
287 | 0 | for (guint i = head; str[i] != '\0'; i++) { |
288 | 0 | if (!g_ascii_isspace(str[i])) |
289 | 0 | tail = i; |
290 | 0 | } |
291 | 0 | return g_strndup(str + head, tail - head + 1); |
292 | 0 | } |
293 | | |
294 | | /** |
295 | | * fu_strdup: |
296 | | * @str: a string, e.g. ` test ` |
297 | | * @bufsz: the maximum size of @str |
298 | | * @offset: the offset to start copying from |
299 | | * |
300 | | * Copies a string from a buffer of a specified size up to (but not including) `NUL`. |
301 | | * |
302 | | * Returns: (transfer full): a #GString, possibly of zero size. |
303 | | * |
304 | | * Since: 1.8.11 |
305 | | **/ |
306 | | GString * |
307 | | fu_strdup(const gchar *str, gsize bufsz, gsize offset) |
308 | 0 | { |
309 | 0 | GString *substr; |
310 | |
|
311 | 0 | g_return_val_if_fail(str != NULL, NULL); |
312 | 0 | g_return_val_if_fail(offset < bufsz, NULL); |
313 | | |
314 | 0 | substr = g_string_new(NULL); |
315 | 0 | while (offset < bufsz) { |
316 | 0 | if (str[offset] == '\0') |
317 | 0 | break; |
318 | 0 | g_string_append_c(substr, str[offset++]); |
319 | 0 | } |
320 | 0 | return substr; |
321 | 0 | } |
322 | | |
323 | | /** |
324 | | * fu_strwidth: |
325 | | * @text: the string to operate on |
326 | | * |
327 | | * Returns the width of the string in displayed characters on the console. |
328 | | * |
329 | | * Returns: width of text |
330 | | * |
331 | | * Since: 1.8.2 |
332 | | **/ |
333 | | gsize |
334 | | fu_strwidth(const gchar *text) |
335 | 0 | { |
336 | 0 | const gchar *p = text; |
337 | 0 | gsize width = 0; |
338 | |
|
339 | 0 | g_return_val_if_fail(text != NULL, 0); |
340 | | |
341 | 0 | while (*p) { |
342 | 0 | gunichar c = g_utf8_get_char(p); |
343 | 0 | if (g_unichar_iswide(c)) |
344 | 0 | width += 2; |
345 | 0 | else if (!g_unichar_iszerowidth(c)) |
346 | 0 | width += 1; |
347 | 0 | p = g_utf8_next_char(p); |
348 | 0 | } |
349 | 0 | return width; |
350 | 0 | } |
351 | | |
352 | | /** |
353 | | * fu_strsplit: |
354 | | * @str: (not nullable): a string to split |
355 | | * @sz: size of @str, which must be more than 0 |
356 | | * @delimiter: a string which specifies the places at which to split the string |
357 | | * @max_tokens: the maximum number of pieces to split @str into |
358 | | * |
359 | | * Splits a string into a maximum of @max_tokens pieces, using the given |
360 | | * delimiter. If @max_tokens is reached, the remainder of string is appended |
361 | | * to the last token. |
362 | | * |
363 | | * Returns: (transfer full): a newly-allocated NULL-terminated array of strings |
364 | | * |
365 | | * Since: 1.8.2 |
366 | | **/ |
367 | | gchar ** |
368 | | fu_strsplit(const gchar *str, gsize sz, const gchar *delimiter, gint max_tokens) |
369 | 0 | { |
370 | 0 | g_return_val_if_fail(str != NULL, NULL); |
371 | 0 | g_return_val_if_fail(sz > 0, NULL); |
372 | 0 | if (str[sz - 1] != '\0') { |
373 | 0 | g_autofree gchar *str2 = g_strndup(str, sz); |
374 | 0 | return g_strsplit(str2, delimiter, max_tokens); |
375 | 0 | } |
376 | 0 | return g_strsplit(str, delimiter, max_tokens); |
377 | 0 | } |
378 | | |
379 | | /** |
380 | | * fu_strsplit_bytes: |
381 | | * @blob: (not nullable): a #GBytes |
382 | | * @delimiter: a string which specifies the places at which to split the string |
383 | | * @max_tokens: the maximum number of pieces to split @str into |
384 | | * |
385 | | * Splits a string into a maximum of @max_tokens pieces, using the given |
386 | | * delimiter. If @max_tokens is reached, the remainder of string is appended |
387 | | * to the last token. |
388 | | * |
389 | | * Returns: (transfer full): a newly-allocated NULL-terminated array of strings |
390 | | * |
391 | | * Since: 2.0.7 |
392 | | **/ |
393 | | gchar ** |
394 | | fu_strsplit_bytes(GBytes *blob, const gchar *delimiter, gint max_tokens) |
395 | 0 | { |
396 | 0 | g_return_val_if_fail(blob != NULL, NULL); |
397 | 0 | return fu_strsplit(g_bytes_get_data(blob, NULL), |
398 | 0 | g_bytes_get_size(blob), |
399 | 0 | delimiter, |
400 | 0 | max_tokens); |
401 | 0 | } |
402 | | |
403 | | typedef struct { |
404 | | FuStrsplitFunc callback; |
405 | | gpointer user_data; |
406 | | guint token_idx; |
407 | | const gchar *delimiter; |
408 | | gsize delimiter_sz; |
409 | | gboolean detected_nul; |
410 | | gboolean more_chunks; |
411 | | } FuStrsplitHelper; |
412 | | |
413 | | static gboolean |
414 | | fu_strsplit_buffer_drain(GByteArray *buf, FuStrsplitHelper *helper, GError **error) |
415 | 238k | { |
416 | 238k | gsize buf_offset = 0; |
417 | 19.0M | while (buf_offset <= buf->len) { |
418 | 18.8M | gsize offset; |
419 | 18.8M | g_autoptr(GString) token = g_string_new(NULL); |
420 | | |
421 | | /* find first match in buffer, starting at the buffer offset */ |
422 | 6.07G | for (offset = buf_offset; offset < buf->len; offset++) { |
423 | 6.07G | if (buf->data[offset] == 0x0) { |
424 | 336 | helper->detected_nul = TRUE; |
425 | 336 | break; |
426 | 336 | } |
427 | 6.07G | if (strncmp((const gchar *)buf->data + offset, |
428 | 6.07G | helper->delimiter, |
429 | 6.07G | helper->delimiter_sz) == 0) |
430 | 18.5M | break; |
431 | 6.07G | } |
432 | | |
433 | | /* no token found, keep going */ |
434 | 18.8M | if (helper->more_chunks && offset == buf->len) |
435 | 19.8k | break; |
436 | | |
437 | | /* sanity check is valid UTF-8 */ |
438 | 18.7M | g_string_append_len(token, |
439 | 18.7M | (const gchar *)buf->data + buf_offset, |
440 | 18.7M | offset - buf_offset); |
441 | 18.7M | if (!g_utf8_validate_len(token->str, token->len, NULL)) { |
442 | 137k | g_debug("ignoring invalid UTF-8, got: %s", token->str); |
443 | 18.6M | } else { |
444 | 18.6M | if (!helper->callback(token, helper->token_idx++, helper->user_data, error)) |
445 | 4.89k | return FALSE; |
446 | 18.6M | } |
447 | 18.7M | if (helper->detected_nul) { |
448 | 289 | buf_offset = buf->len; |
449 | 289 | break; |
450 | 289 | } |
451 | 18.7M | buf_offset = offset + helper->delimiter_sz; |
452 | 18.7M | } |
453 | 233k | g_byte_array_remove_range(buf, 0, MIN(buf_offset, buf->len)); |
454 | 233k | return TRUE; |
455 | 238k | } |
456 | | |
457 | | /** |
458 | | * fu_strsplit_stream: |
459 | | * @stream: a #GInputStream to split |
460 | | * @offset: offset into @stream |
461 | | * @delimiter: a string which specifies the places at which to split the string |
462 | | * @callback: (scope call) (closure user_data): a #FuStrsplitFunc. |
463 | | * @user_data: user data |
464 | | * @error: (nullable): optional return location for an error |
465 | | * |
466 | | * Splits the string, calling the given function for each |
467 | | * of the tokens found. If any @callback returns %FALSE scanning is aborted. |
468 | | * |
469 | | * Use this function in preference to fu_strsplit() when the input file is untrusted, |
470 | | * and you don't want to allocate a GStrv with billions of one byte items. |
471 | | * |
472 | | * Returns: %TRUE if no @callback returned FALSE |
473 | | * |
474 | | * Since: 2.0.0 |
475 | | */ |
476 | | gboolean |
477 | | fu_strsplit_stream(GInputStream *stream, |
478 | | gsize offset, |
479 | | const gchar *delimiter, |
480 | | FuStrsplitFunc callback, |
481 | | gpointer user_data, |
482 | | GError **error) |
483 | 218k | { |
484 | 218k | g_autoptr(FuChunkArray) chunks = NULL; |
485 | 218k | g_autoptr(GByteArray) buf = g_byte_array_new(); |
486 | 218k | g_autoptr(GInputStream) stream_partial = NULL; |
487 | 218k | FuStrsplitHelper helper = { |
488 | 218k | .callback = callback, |
489 | 218k | .user_data = user_data, |
490 | 218k | .delimiter = delimiter, |
491 | 218k | .token_idx = 0, |
492 | 218k | }; |
493 | | |
494 | 218k | g_return_val_if_fail(G_IS_INPUT_STREAM(stream), FALSE); |
495 | 218k | g_return_val_if_fail(delimiter != NULL && delimiter[0] != '\0', FALSE); |
496 | 218k | g_return_val_if_fail(callback != NULL, FALSE); |
497 | 218k | g_return_val_if_fail(error == NULL || *error == NULL, FALSE); |
498 | | |
499 | 218k | helper.delimiter_sz = strlen(delimiter); |
500 | 218k | if (offset > 0) { |
501 | 0 | stream_partial = fu_partial_input_stream_new(stream, offset, G_MAXSIZE, error); |
502 | 0 | if (stream_partial == NULL) { |
503 | 0 | g_prefix_error_literal(error, "failed to cut string: "); |
504 | 0 | return FALSE; |
505 | 0 | } |
506 | 218k | } else { |
507 | 218k | stream_partial = g_object_ref(stream); |
508 | 218k | } |
509 | 218k | chunks = fu_chunk_array_new_from_stream(stream_partial, |
510 | 218k | FU_CHUNK_ADDR_OFFSET_NONE, |
511 | 218k | FU_CHUNK_PAGESZ_NONE, |
512 | 218k | 0x8000, |
513 | 218k | error); |
514 | 218k | if (chunks == NULL) |
515 | 0 | return FALSE; |
516 | 452k | for (gsize i = 0; i < fu_chunk_array_length(chunks); i++) { |
517 | 238k | g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i, error); |
518 | 238k | if (chk == NULL) |
519 | 0 | return FALSE; |
520 | 238k | g_byte_array_append(buf, fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk)); |
521 | 238k | helper.more_chunks = i != fu_chunk_array_length(chunks) - 1; |
522 | 238k | if (!fu_strsplit_buffer_drain(buf, &helper, error)) |
523 | 4.89k | return FALSE; |
524 | 233k | if (helper.detected_nul) |
525 | 289 | break; |
526 | 233k | } |
527 | 213k | return TRUE; |
528 | 218k | } |
529 | | |
530 | | /** |
531 | | * fu_strsplit_full: |
532 | | * @str: a string to split |
533 | | * @sz: size of @str, or -1 for unknown |
534 | | * @delimiter: a string which specifies the places at which to split the string |
535 | | * @callback: (scope call) (closure user_data): a #FuStrsplitFunc. |
536 | | * @user_data: user data |
537 | | * @error: (nullable): optional return location for an error |
538 | | * |
539 | | * Splits the string, calling the given function for each |
540 | | * of the tokens found. If any @callback returns %FALSE scanning is aborted. |
541 | | * |
542 | | * Use this function in preference to fu_strsplit() when the input file is untrusted, |
543 | | * and you don't want to allocate a GStrv with billions of one byte items. |
544 | | * |
545 | | * Returns: %TRUE if no @callback returned FALSE |
546 | | * |
547 | | * Since: 1.8.2 |
548 | | */ |
549 | | gboolean |
550 | | fu_strsplit_full(const gchar *str, |
551 | | gssize sz, |
552 | | const gchar *delimiter, |
553 | | FuStrsplitFunc callback, |
554 | | gpointer user_data, |
555 | | GError **error) |
556 | 9.96k | { |
557 | 9.96k | gsize delimiter_sz; |
558 | 9.96k | gsize offset_old = 0; |
559 | 9.96k | gsize str_sz; |
560 | 9.96k | guint token_idx = 0; |
561 | | |
562 | 9.96k | g_return_val_if_fail(str != NULL, FALSE); |
563 | 9.96k | g_return_val_if_fail(delimiter != NULL && delimiter[0] != '\0', FALSE); |
564 | 9.96k | g_return_val_if_fail(callback != NULL, FALSE); |
565 | 9.96k | g_return_val_if_fail(error == NULL || *error == NULL, FALSE); |
566 | | |
567 | | /* make known */ |
568 | 9.96k | str_sz = sz != -1 ? (gsize)sz : strlen(str); |
569 | 9.96k | delimiter_sz = strlen(delimiter); |
570 | | |
571 | | /* cannot split */ |
572 | 9.96k | if (delimiter_sz > str_sz) { |
573 | 455 | g_autoptr(GString) token = g_string_new(str); |
574 | 455 | return callback(token, token_idx, user_data, error); |
575 | 455 | } |
576 | | |
577 | | /* start splittin' */ |
578 | 13.4M | while (offset_old <= str_sz) { |
579 | 13.3M | gsize offset; |
580 | 13.3M | g_autoptr(GString) token = g_string_new(NULL); |
581 | | |
582 | 15.3M | for (offset = offset_old; offset < str_sz; offset++) { |
583 | 15.3M | if (strncmp(str + offset, delimiter, delimiter_sz) == 0) |
584 | 13.3M | break; |
585 | 15.3M | } |
586 | 13.3M | g_string_append_len(token, str + offset_old, offset - offset_old); |
587 | 13.3M | if (!callback(token, token_idx++, user_data, error)) |
588 | 0 | return FALSE; |
589 | 13.3M | offset_old = offset + delimiter_sz; |
590 | 13.3M | } |
591 | | |
592 | | /* success */ |
593 | 9.51k | return TRUE; |
594 | 9.51k | } |
595 | | |
596 | | /** |
597 | | * fu_strsafe: |
598 | | * @str: (nullable): a string to make safe for printing |
599 | | * @maxsz: maximum size of returned string, or %G_MAXSIZE for no limit |
600 | | * |
601 | | * Converts a string into something that can be safely printed. |
602 | | * |
603 | | * Returns: (transfer full): safe string, or %NULL if there was nothing valid |
604 | | * |
605 | | * Since: 1.8.2 |
606 | | **/ |
607 | | gchar * |
608 | | fu_strsafe(const gchar *str, gsize maxsz) |
609 | 27.1M | { |
610 | 27.1M | gboolean valid = FALSE; |
611 | 27.1M | g_autoptr(GString) tmp = g_string_new(NULL); |
612 | | |
613 | | /* sanity check */ |
614 | 27.1M | if (str == NULL || maxsz == 0) |
615 | 1 | return NULL; |
616 | | |
617 | | /* replace non-printable chars with '.' */ |
618 | 84.3M | for (gsize i = 0; i < maxsz && str[i] != '\0'; i++) { |
619 | 57.1M | if (!g_ascii_isgraph(str[i]) && !g_ascii_isspace(str[i])) { |
620 | 20.9M | g_string_append_c(tmp, '.'); |
621 | 20.9M | continue; |
622 | 20.9M | } |
623 | 36.2M | g_string_append_c(tmp, str[i]); |
624 | 36.2M | if (!g_ascii_isspace(str[i])) |
625 | 35.4M | valid = TRUE; |
626 | 36.2M | } |
627 | | |
628 | | /* if just junk, don't return 'all dots' */ |
629 | 27.1M | if (tmp->len == 0 || !valid) |
630 | 17.4M | return NULL; |
631 | 9.75M | return g_string_free(g_steal_pointer(&tmp), FALSE); |
632 | 27.1M | } |
633 | | |
634 | | /** |
635 | | * fu_strsafe_bytes: |
636 | | * @blob: (not nullable): a #GBytes |
637 | | * @maxsz: maximum size of returned string, or %G_MAXSIZE for no limit |
638 | | * |
639 | | * Converts a #GBytes into something that can be safely printed. |
640 | | * |
641 | | * Returns: (transfer full): safe string, or %NULL if there was nothing valid |
642 | | * |
643 | | * Since: 2.0.2 |
644 | | **/ |
645 | | gchar * |
646 | | fu_strsafe_bytes(GBytes *blob, gsize maxsz) |
647 | 0 | { |
648 | 0 | g_return_val_if_fail(blob != NULL, NULL); |
649 | 0 | return fu_strsafe((const gchar *)g_bytes_get_data(blob, NULL), |
650 | 0 | MIN(g_bytes_get_size(blob), maxsz)); |
651 | 0 | } |
652 | | |
653 | | /** |
654 | | * fu_strjoin: |
655 | | * @separator: (nullable): string to insert between each of the strings |
656 | | * @array: (element-type utf8): a #GPtrArray |
657 | | * |
658 | | * Joins an array of strings together to form one long string, with the optional |
659 | | * separator inserted between each of them. |
660 | | * |
661 | | * If @array has no items, the return value will be an empty string. |
662 | | * If @array contains a single item, separator will not appear in the resulting |
663 | | * string. |
664 | | * |
665 | | * Returns: a string |
666 | | * |
667 | | * Since: 1.8.2 |
668 | | **/ |
669 | | gchar * |
670 | | fu_strjoin(const gchar *separator, GPtrArray *array) |
671 | 0 | { |
672 | 0 | g_autofree const gchar **strv = NULL; |
673 | |
|
674 | 0 | g_return_val_if_fail(array != NULL, NULL); |
675 | | |
676 | 0 | strv = g_new0(const gchar *, array->len + 1); |
677 | 0 | for (guint i = 0; i < array->len; i++) |
678 | 0 | strv[i] = g_ptr_array_index(array, i); |
679 | 0 | return g_strjoinv(separator, (gchar **)strv); |
680 | 0 | } |
681 | | |
682 | | /** |
683 | | * fu_strpassmask: |
684 | | * @str: (nullable): a string to make safe for printing |
685 | | * |
686 | | * Hides password strings encoded in HTTP requests. |
687 | | * |
688 | | * Returns: a string |
689 | | * |
690 | | * Since: 1.9.10 |
691 | | **/ |
692 | | gchar * |
693 | | fu_strpassmask(const gchar *str) |
694 | 0 | { |
695 | 0 | g_autoptr(GString) tmp = g_string_new(str); |
696 | 0 | if (tmp->str != NULL && g_strstr_len(tmp->str, -1, "@") != NULL && |
697 | 0 | g_strstr_len(tmp->str, -1, ":") != NULL) { |
698 | 0 | gboolean is_password = FALSE; |
699 | 0 | gboolean is_url = FALSE; |
700 | 0 | for (guint i = 0; i < tmp->len; i++) { |
701 | 0 | const gchar *url_prefixes[] = {"http://", "https://", NULL}; |
702 | 0 | for (guint j = 0; url_prefixes[j] != NULL; j++) { |
703 | 0 | if (g_str_has_prefix(tmp->str + i, url_prefixes[j])) { |
704 | 0 | is_url = TRUE; |
705 | 0 | i += strlen(url_prefixes[j]); |
706 | 0 | break; |
707 | 0 | } |
708 | 0 | } |
709 | 0 | if (tmp->str[i] == ' ' || tmp->str[i] == '@' || tmp->str[i] == '/') { |
710 | 0 | is_url = FALSE; |
711 | 0 | is_password = FALSE; |
712 | 0 | continue; |
713 | 0 | } |
714 | 0 | if (is_url && tmp->str[i] == ':') { |
715 | 0 | is_password = TRUE; |
716 | 0 | continue; |
717 | 0 | } |
718 | 0 | if (is_url && is_password) { |
719 | 0 | if (tmp->str[i] == '@') { |
720 | 0 | is_password = FALSE; |
721 | 0 | continue; |
722 | 0 | } |
723 | 0 | tmp->str[i] = 'X'; |
724 | 0 | } |
725 | 0 | } |
726 | 0 | } |
727 | 0 | return g_string_free(g_steal_pointer(&tmp), FALSE); |
728 | 0 | } |
729 | | |
730 | | /** |
731 | | * fu_utf16_to_utf8_byte_array: |
732 | | * @array: a #GByteArray |
733 | | * @endian: an endian type, e.g. %G_LITTLE_ENDIAN |
734 | | * @error: (nullable): optional return location for an error |
735 | | * |
736 | | * Converts a UTF-16 buffer to a UTF-8 string. |
737 | | * |
738 | | * Returns: (transfer full): a string, or %NULL on error |
739 | | * |
740 | | * Since: 1.9.3 |
741 | | **/ |
742 | | gchar * |
743 | | fu_utf16_to_utf8_byte_array(GByteArray *array, FuEndianType endian, GError **error) |
744 | 6.68k | { |
745 | 6.68k | g_autofree guint16 *buf16 = NULL; |
746 | | |
747 | 6.68k | g_return_val_if_fail(array != NULL, NULL); |
748 | 6.68k | g_return_val_if_fail(error == NULL || *error == NULL, NULL); |
749 | | |
750 | 6.68k | if (array->len % 2 != 0) { |
751 | 363 | g_set_error_literal(error, |
752 | 363 | FWUPD_ERROR, |
753 | 363 | FWUPD_ERROR_INVALID_DATA, |
754 | 363 | "invalid UTF-16 buffer length"); |
755 | 363 | return NULL; |
756 | 363 | } |
757 | 6.32k | buf16 = g_new0(guint16, (array->len / sizeof(guint16)) + 1); |
758 | 58.6M | for (guint i = 0; i < array->len / 2; i++) { |
759 | 58.6M | guint16 data = fu_memread_uint16(array->data + (i * 2), endian); |
760 | 58.6M | fu_memwrite_uint16((guint8 *)(buf16 + i), data, G_BYTE_ORDER); |
761 | 58.6M | } |
762 | 6.32k | return g_utf16_to_utf8(buf16, array->len / sizeof(guint16), NULL, NULL, error); |
763 | 6.68k | } |
764 | | |
765 | | /** |
766 | | * fu_utf8_to_utf16_byte_array: |
767 | | * @str: a UTF-8 string |
768 | | * @endian: an endian type, e.g. %G_LITTLE_ENDIAN |
769 | | * @flags: a FuUtfConvertFlags, e.g. %FU_UTF_CONVERT_FLAG_APPEND_NUL |
770 | | * @error: (nullable): optional return location for an error |
771 | | * |
772 | | * Converts UTF-8 string to a buffer of UTF-16, optionially including the trailing NULw. |
773 | | * |
774 | | * Returns: (transfer full): a #GByteArray, or %NULL on error |
775 | | * |
776 | | * Since: 1.9.3 |
777 | | **/ |
778 | | GByteArray * |
779 | | fu_utf8_to_utf16_byte_array(const gchar *str, |
780 | | FuEndianType endian, |
781 | | FuUtfConvertFlags flags, |
782 | | GError **error) |
783 | 1.88k | { |
784 | 1.88k | glong buf_utf16sz = 0; |
785 | 1.88k | g_autoptr(GByteArray) array = g_byte_array_new(); |
786 | 1.88k | g_autofree gunichar2 *buf_utf16 = NULL; |
787 | | |
788 | 1.88k | g_return_val_if_fail(str != NULL, NULL); |
789 | 1.88k | g_return_val_if_fail(error == NULL || *error == NULL, NULL); |
790 | | |
791 | 1.88k | buf_utf16 = g_utf8_to_utf16(str, (glong)-1, NULL, &buf_utf16sz, error); |
792 | 1.88k | if (buf_utf16 == NULL) |
793 | 0 | return NULL; |
794 | 1.88k | if (flags & FU_UTF_CONVERT_FLAG_APPEND_NUL) |
795 | 1.33k | buf_utf16sz += 1; |
796 | 190k | for (glong i = 0; i < buf_utf16sz; i++) { |
797 | 188k | guint16 data = fu_memread_uint16((guint8 *)(buf_utf16 + i), G_BYTE_ORDER); |
798 | 188k | fu_byte_array_append_uint16(array, data, endian); |
799 | 188k | } |
800 | 1.88k | return g_steal_pointer(&array); |
801 | 1.88k | } |
802 | | |
803 | | /** |
804 | | * fu_utf16_to_utf8_bytes: |
805 | | * @bytes: a #GBytes |
806 | | * @endian: an endian type, e.g. %G_LITTLE_ENDIAN |
807 | | * @error: (nullable): optional return location for an error |
808 | | * |
809 | | * Converts a UTF-16 buffer to a UTF-8 string. |
810 | | * |
811 | | * Returns: (transfer full): a string, or %NULL on error |
812 | | * |
813 | | * Since: 1.9.3 |
814 | | **/ |
815 | | gchar * |
816 | | fu_utf16_to_utf8_bytes(GBytes *bytes, FuEndianType endian, GError **error) |
817 | 2.24k | { |
818 | 2.24k | GByteArray array = {0x0}; |
819 | | |
820 | 2.24k | g_return_val_if_fail(bytes != NULL, NULL); |
821 | 2.24k | g_return_val_if_fail(error == NULL || *error == NULL, NULL); |
822 | | |
823 | 2.24k | array.data = (guint8 *)g_bytes_get_data(bytes, NULL); |
824 | 2.24k | array.len = g_bytes_get_size(bytes); |
825 | 2.24k | return fu_utf16_to_utf8_byte_array(&array, endian, error); |
826 | 2.24k | } |
827 | | |
828 | | /** |
829 | | * fu_utf8_to_utf16_bytes: |
830 | | * @str: a UTF-8 string |
831 | | * @endian: an endian type, e.g. %G_LITTLE_ENDIAN |
832 | | * @error: (nullable): optional return location for an error |
833 | | * |
834 | | * Converts UTF-8 string to a buffer of UTF-16, optionally including the trailing NULw. |
835 | | * |
836 | | * Returns: (transfer full): a #GBytes, or %NULL on error |
837 | | * |
838 | | * Since: 1.9.3 |
839 | | **/ |
840 | | GBytes * |
841 | | fu_utf8_to_utf16_bytes(const gchar *str, |
842 | | FuEndianType endian, |
843 | | FuUtfConvertFlags flags, |
844 | | GError **error) |
845 | 0 | { |
846 | 0 | g_autoptr(GByteArray) buf = NULL; |
847 | |
|
848 | 0 | g_return_val_if_fail(str != NULL, NULL); |
849 | 0 | g_return_val_if_fail(error == NULL || *error == NULL, NULL); |
850 | | |
851 | 0 | buf = fu_utf8_to_utf16_byte_array(str, endian, flags, error); |
852 | 0 | if (buf == NULL) |
853 | 0 | return NULL; |
854 | 0 | return g_bytes_new(buf->data, buf->len); |
855 | 0 | } |