/src/fwupd/libfwupdplugin/fu-string.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright 2017 Richard Hughes <richard@hughsie.com> |
3 | | * |
4 | | * SPDX-License-Identifier: LGPL-2.1-or-later |
5 | | */ |
6 | | |
7 | 126k | #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 | 6.07M | { |
41 | 6.07M | gchar *endptr = NULL; |
42 | 6.07M | guint64 value_tmp; |
43 | | |
44 | | /* sanity check */ |
45 | 6.07M | if (str == NULL) { |
46 | 13 | g_set_error_literal(error, |
47 | 13 | FWUPD_ERROR, |
48 | 13 | FWUPD_ERROR_INVALID_DATA, |
49 | 13 | "cannot parse NULL"); |
50 | 13 | return FALSE; |
51 | 13 | } |
52 | | |
53 | | /* detect hex */ |
54 | 6.07M | if (base == FU_INTEGER_BASE_AUTO) { |
55 | 1.26k | if (g_str_has_prefix(str, "0x")) { |
56 | 281 | str += 2; |
57 | 281 | base = FU_INTEGER_BASE_16; |
58 | 982 | } else { |
59 | 982 | base = FU_INTEGER_BASE_10; |
60 | 982 | } |
61 | 6.07M | } else if (base == FU_INTEGER_BASE_16 && g_str_has_prefix(str, "0x")) { |
62 | 2.99k | str += 2; |
63 | 6.06M | } 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 | 6.07M | value_tmp = g_ascii_strtoull(str, &endptr, base); /* nocheck:blocked */ |
73 | 6.07M | if ((gsize)(endptr - str) != strlen(str) && *endptr != '\n') { |
74 | 659 | g_set_error_literal(error, |
75 | 659 | FWUPD_ERROR, |
76 | 659 | FWUPD_ERROR_INVALID_DATA, |
77 | 659 | "cannot parse datastream"); |
78 | 659 | return FALSE; |
79 | 659 | } |
80 | | |
81 | | /* overflow check */ |
82 | 6.07M | if (value_tmp == G_MAXUINT64) { |
83 | 47 | g_set_error_literal(error, |
84 | 47 | FWUPD_ERROR, |
85 | 47 | FWUPD_ERROR_INVALID_DATA, |
86 | 47 | "parsing datastream caused overflow"); |
87 | 47 | return FALSE; |
88 | 47 | } |
89 | | |
90 | | /* range check */ |
91 | 6.07M | 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 | 6.07M | if (value_tmp > max) { |
101 | 361 | g_set_error(error, |
102 | 361 | FWUPD_ERROR, |
103 | 361 | FWUPD_ERROR_INVALID_DATA, |
104 | 361 | "value %" G_GUINT64_FORMAT " was above maximum %" G_GUINT64_FORMAT, |
105 | 361 | value_tmp, |
106 | 361 | max); |
107 | 361 | return FALSE; |
108 | 361 | } |
109 | | |
110 | | /* success */ |
111 | 6.07M | if (value != NULL) |
112 | 6.07M | *value = value_tmp; |
113 | 6.07M | return TRUE; |
114 | 6.07M | } |
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_string_strip: |
296 | | * @str: a #GString, e.g. ` test ` |
297 | | * |
298 | | * Removes leading and trailing whitespace from a mutable string. |
299 | | * |
300 | | * Since: 2.0.17 |
301 | | **/ |
302 | | void |
303 | | fu_string_strip(GString *str) |
304 | 0 | { |
305 | 0 | guint i; |
306 | | |
307 | | /* leading whitespace */ |
308 | 0 | for (i = 0; i < str->len; i++) { |
309 | 0 | if (!g_ascii_isspace(str->str[i])) |
310 | 0 | break; |
311 | 0 | } |
312 | 0 | if (i > 0) |
313 | 0 | g_string_erase(str, 0, i); |
314 | | |
315 | | /* trailing whitespace */ |
316 | 0 | for (i = 0; i < str->len; i++) { |
317 | 0 | if (!g_ascii_isspace(str->str[str->len - (i + 1)])) |
318 | 0 | break; |
319 | 0 | } |
320 | 0 | if (i < str->len) |
321 | 0 | g_string_truncate(str, str->len - i); |
322 | 0 | } |
323 | | |
324 | | /** |
325 | | * fu_strdup: |
326 | | * @str: a string, e.g. ` test ` |
327 | | * @bufsz: the maximum size of @str |
328 | | * @offset: the offset to start copying from |
329 | | * |
330 | | * Copies a string from a buffer of a specified size up to (but not including) `NUL`. |
331 | | * |
332 | | * Returns: (transfer full): a #GString, possibly of zero size. |
333 | | * |
334 | | * Since: 1.8.11 |
335 | | **/ |
336 | | GString * |
337 | | fu_strdup(const gchar *str, gsize bufsz, gsize offset) |
338 | 0 | { |
339 | 0 | GString *substr; |
340 | |
|
341 | 0 | g_return_val_if_fail(str != NULL, NULL); |
342 | 0 | g_return_val_if_fail(offset < bufsz, NULL); |
343 | | |
344 | 0 | substr = g_string_new(NULL); |
345 | 0 | while (offset < bufsz) { |
346 | 0 | if (str[offset] == '\0') |
347 | 0 | break; |
348 | 0 | g_string_append_c(substr, str[offset++]); |
349 | 0 | } |
350 | 0 | return substr; |
351 | 0 | } |
352 | | |
353 | | /** |
354 | | * fu_strwidth: |
355 | | * @text: the string to operate on |
356 | | * |
357 | | * Returns the width of the string in displayed characters on the console. |
358 | | * |
359 | | * Returns: width of text |
360 | | * |
361 | | * Since: 1.8.2 |
362 | | **/ |
363 | | gsize |
364 | | fu_strwidth(const gchar *text) |
365 | 0 | { |
366 | 0 | const gchar *p = text; |
367 | 0 | gsize width = 0; |
368 | |
|
369 | 0 | g_return_val_if_fail(text != NULL, 0); |
370 | | |
371 | 0 | while (*p) { |
372 | 0 | gunichar c = g_utf8_get_char(p); |
373 | 0 | if (g_unichar_iswide(c)) |
374 | 0 | width += 2; |
375 | 0 | else if (!g_unichar_iszerowidth(c)) |
376 | 0 | width += 1; |
377 | 0 | p = g_utf8_next_char(p); |
378 | 0 | } |
379 | 0 | return width; |
380 | 0 | } |
381 | | |
382 | | /** |
383 | | * fu_strsplit: |
384 | | * @str: (not nullable): a string to split |
385 | | * @sz: size of @str, which must be more than 0 |
386 | | * @delimiter: a string which specifies the places at which to split the string |
387 | | * @max_tokens: the maximum number of pieces to split @str into |
388 | | * |
389 | | * Splits a string into a maximum of @max_tokens pieces, using the given |
390 | | * delimiter. If @max_tokens is reached, the remainder of string is appended |
391 | | * to the last token. |
392 | | * |
393 | | * Returns: (transfer full): a newly-allocated NULL-terminated array of strings |
394 | | * |
395 | | * Since: 1.8.2 |
396 | | **/ |
397 | | gchar ** |
398 | | fu_strsplit(const gchar *str, gsize sz, const gchar *delimiter, gint max_tokens) |
399 | 0 | { |
400 | 0 | g_return_val_if_fail(str != NULL, NULL); |
401 | 0 | g_return_val_if_fail(sz > 0, NULL); |
402 | 0 | if (str[sz - 1] != '\0') { |
403 | 0 | g_autofree gchar *str2 = g_strndup(str, sz); |
404 | 0 | return g_strsplit(str2, delimiter, max_tokens); |
405 | 0 | } |
406 | 0 | return g_strsplit(str, delimiter, max_tokens); |
407 | 0 | } |
408 | | |
409 | | /** |
410 | | * fu_strsplit_bytes: |
411 | | * @blob: (not nullable): a #GBytes |
412 | | * @delimiter: a string which specifies the places at which to split the string |
413 | | * @max_tokens: the maximum number of pieces to split @str into |
414 | | * |
415 | | * Splits a string into a maximum of @max_tokens pieces, using the given |
416 | | * delimiter. If @max_tokens is reached, the remainder of string is appended |
417 | | * to the last token. |
418 | | * |
419 | | * Returns: (transfer full): a newly-allocated NULL-terminated array of strings |
420 | | * |
421 | | * Since: 2.0.7 |
422 | | **/ |
423 | | gchar ** |
424 | | fu_strsplit_bytes(GBytes *blob, const gchar *delimiter, gint max_tokens) |
425 | 0 | { |
426 | 0 | g_return_val_if_fail(blob != NULL, NULL); |
427 | 0 | return fu_strsplit(g_bytes_get_data(blob, NULL), |
428 | 0 | g_bytes_get_size(blob), |
429 | 0 | delimiter, |
430 | 0 | max_tokens); |
431 | 0 | } |
432 | | |
433 | | typedef struct { |
434 | | FuStrsplitFunc callback; |
435 | | gpointer user_data; |
436 | | guint token_idx; |
437 | | const gchar *delimiter; |
438 | | gsize delimiter_sz; |
439 | | gboolean detected_nul; |
440 | | gboolean more_chunks; |
441 | | } FuStrsplitHelper; |
442 | | |
443 | | static gboolean |
444 | | fu_strsplit_buffer_drain(GByteArray *buf, FuStrsplitHelper *helper, GError **error) |
445 | 220k | { |
446 | 220k | gsize buf_offset = 0; |
447 | 15.1M | while (buf_offset <= buf->len) { |
448 | 14.9M | gsize offset; |
449 | 14.9M | g_autoptr(GString) token = g_string_new(NULL); |
450 | | |
451 | | /* find first match in buffer, starting at the buffer offset */ |
452 | 5.52G | for (offset = buf_offset; offset < buf->len; offset++) { |
453 | 5.52G | if (buf->data[offset] == 0x0) { |
454 | 320 | helper->detected_nul = TRUE; |
455 | 320 | break; |
456 | 320 | } |
457 | 5.52G | if (strncmp((const gchar *)buf->data + offset, |
458 | 5.52G | helper->delimiter, |
459 | 5.52G | helper->delimiter_sz) == 0) |
460 | 14.6M | break; |
461 | 5.52G | } |
462 | | |
463 | | /* no token found, keep going */ |
464 | 14.9M | if (helper->more_chunks && offset == buf->len) |
465 | 17.2k | break; |
466 | | |
467 | | /* sanity check is valid UTF-8 */ |
468 | 14.8M | g_string_append_len(token, |
469 | 14.8M | (const gchar *)buf->data + buf_offset, |
470 | 14.8M | offset - buf_offset); |
471 | 14.8M | if (!g_utf8_validate_len(token->str, token->len, NULL)) { |
472 | 126k | g_debug("ignoring invalid UTF-8, got: %s", token->str); |
473 | 14.7M | } else { |
474 | 14.7M | if (!helper->callback(token, helper->token_idx++, helper->user_data, error)) |
475 | 4.86k | return FALSE; |
476 | 14.7M | } |
477 | 14.8M | if (helper->detected_nul) { |
478 | 261 | buf_offset = buf->len; |
479 | 261 | break; |
480 | 261 | } |
481 | 14.8M | buf_offset = offset + helper->delimiter_sz; |
482 | 14.8M | } |
483 | 215k | g_byte_array_remove_range(buf, 0, MIN(buf_offset, buf->len)); |
484 | 215k | return TRUE; |
485 | 220k | } |
486 | | |
487 | | /** |
488 | | * fu_strsplit_stream: |
489 | | * @stream: a #GInputStream to split |
490 | | * @offset: offset into @stream |
491 | | * @delimiter: a string which specifies the places at which to split the string |
492 | | * @callback: (scope call) (closure user_data): a #FuStrsplitFunc. |
493 | | * @user_data: user data |
494 | | * @error: (nullable): optional return location for an error |
495 | | * |
496 | | * Splits the string, calling the given function for each |
497 | | * of the tokens found. If any @callback returns %FALSE scanning is aborted. |
498 | | * |
499 | | * Use this function in preference to fu_strsplit() when the input file is untrusted, |
500 | | * and you don't want to allocate a GStrv with billions of one byte items. |
501 | | * |
502 | | * Returns: %TRUE if no @callback returned FALSE |
503 | | * |
504 | | * Since: 2.0.0 |
505 | | */ |
506 | | gboolean |
507 | | fu_strsplit_stream(GInputStream *stream, |
508 | | gsize offset, |
509 | | const gchar *delimiter, |
510 | | FuStrsplitFunc callback, |
511 | | gpointer user_data, |
512 | | GError **error) |
513 | 203k | { |
514 | 203k | g_autoptr(FuChunkArray) chunks = NULL; |
515 | 203k | g_autoptr(GByteArray) buf = g_byte_array_new(); |
516 | 203k | g_autoptr(GInputStream) stream_partial = NULL; |
517 | 203k | FuStrsplitHelper helper = { |
518 | 203k | .callback = callback, |
519 | 203k | .user_data = user_data, |
520 | 203k | .delimiter = delimiter, |
521 | 203k | .token_idx = 0, |
522 | 203k | }; |
523 | | |
524 | 203k | g_return_val_if_fail(G_IS_INPUT_STREAM(stream), FALSE); |
525 | 203k | g_return_val_if_fail(delimiter != NULL && delimiter[0] != '\0', FALSE); |
526 | 203k | g_return_val_if_fail(callback != NULL, FALSE); |
527 | 203k | g_return_val_if_fail(error == NULL || *error == NULL, FALSE); |
528 | | |
529 | 203k | helper.delimiter_sz = strlen(delimiter); |
530 | 203k | if (offset > 0) { |
531 | 0 | stream_partial = fu_partial_input_stream_new(stream, offset, G_MAXSIZE, error); |
532 | 0 | if (stream_partial == NULL) { |
533 | 0 | g_prefix_error_literal(error, "failed to cut string: "); |
534 | 0 | return FALSE; |
535 | 0 | } |
536 | 203k | } else { |
537 | 203k | stream_partial = g_object_ref(stream); |
538 | 203k | } |
539 | 203k | chunks = fu_chunk_array_new_from_stream(stream_partial, |
540 | 203k | FU_CHUNK_ADDR_OFFSET_NONE, |
541 | 203k | FU_CHUNK_PAGESZ_NONE, |
542 | 203k | 0x8000, |
543 | 203k | error); |
544 | 203k | if (chunks == NULL) |
545 | 0 | return FALSE; |
546 | 418k | for (gsize i = 0; i < fu_chunk_array_length(chunks); i++) { |
547 | 220k | g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i, error); |
548 | 220k | if (chk == NULL) |
549 | 0 | return FALSE; |
550 | 220k | g_byte_array_append(buf, fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk)); |
551 | 220k | helper.more_chunks = i != fu_chunk_array_length(chunks) - 1; |
552 | 220k | if (!fu_strsplit_buffer_drain(buf, &helper, error)) |
553 | 4.86k | return FALSE; |
554 | 215k | if (helper.detected_nul) |
555 | 261 | break; |
556 | 215k | } |
557 | 198k | return TRUE; |
558 | 203k | } |
559 | | |
560 | | /** |
561 | | * fu_strsplit_full: |
562 | | * @str: a string to split |
563 | | * @sz: size of @str, or -1 for unknown |
564 | | * @delimiter: a string which specifies the places at which to split the string |
565 | | * @callback: (scope call) (closure user_data): a #FuStrsplitFunc. |
566 | | * @user_data: user data |
567 | | * @error: (nullable): optional return location for an error |
568 | | * |
569 | | * Splits the string, calling the given function for each |
570 | | * of the tokens found. If any @callback returns %FALSE scanning is aborted. |
571 | | * |
572 | | * Use this function in preference to fu_strsplit() when the input file is untrusted, |
573 | | * and you don't want to allocate a GStrv with billions of one byte items. |
574 | | * |
575 | | * Returns: %TRUE if no @callback returned FALSE |
576 | | * |
577 | | * Since: 1.8.2 |
578 | | */ |
579 | | gboolean |
580 | | fu_strsplit_full(const gchar *str, |
581 | | gssize sz, |
582 | | const gchar *delimiter, |
583 | | FuStrsplitFunc callback, |
584 | | gpointer user_data, |
585 | | GError **error) |
586 | 11.3k | { |
587 | 11.3k | gsize delimiter_sz; |
588 | 11.3k | gsize offset_old = 0; |
589 | 11.3k | gsize str_sz; |
590 | 11.3k | guint token_idx = 0; |
591 | | |
592 | 11.3k | g_return_val_if_fail(str != NULL, FALSE); |
593 | 11.3k | g_return_val_if_fail(delimiter != NULL && delimiter[0] != '\0', FALSE); |
594 | 11.3k | g_return_val_if_fail(callback != NULL, FALSE); |
595 | 11.3k | g_return_val_if_fail(error == NULL || *error == NULL, FALSE); |
596 | | |
597 | | /* make known */ |
598 | 11.3k | str_sz = sz != -1 ? (gsize)sz : strlen(str); |
599 | 11.3k | delimiter_sz = strlen(delimiter); |
600 | | |
601 | | /* cannot split */ |
602 | 11.3k | if (delimiter_sz > str_sz) { |
603 | 303 | g_autoptr(GString) token = g_string_new(str); |
604 | 303 | return callback(token, token_idx, user_data, error); |
605 | 303 | } |
606 | | |
607 | | /* start splittin' */ |
608 | 10.9M | while (offset_old <= str_sz) { |
609 | 10.9M | gsize offset; |
610 | 10.9M | g_autoptr(GString) token = g_string_new(NULL); |
611 | | |
612 | 12.6M | for (offset = offset_old; offset < str_sz; offset++) { |
613 | 12.6M | if (strncmp(str + offset, delimiter, delimiter_sz) == 0) |
614 | 10.8M | break; |
615 | 12.6M | } |
616 | 10.9M | g_string_append_len(token, str + offset_old, offset - offset_old); |
617 | 10.9M | if (!callback(token, token_idx++, user_data, error)) |
618 | 0 | return FALSE; |
619 | 10.9M | offset_old = offset + delimiter_sz; |
620 | 10.9M | } |
621 | | |
622 | | /* success */ |
623 | 11.0k | return TRUE; |
624 | 11.0k | } |
625 | | |
626 | | /** |
627 | | * fu_strsafe: |
628 | | * @str: (nullable): a string to make safe for printing |
629 | | * @maxsz: maximum size of returned string, or %G_MAXSIZE for no limit |
630 | | * |
631 | | * Converts a string into something that can be safely printed. |
632 | | * |
633 | | * Returns: (transfer full): safe string, or %NULL if there was nothing valid |
634 | | * |
635 | | * Since: 1.8.2 |
636 | | **/ |
637 | | gchar * |
638 | | fu_strsafe(const gchar *str, gsize maxsz) |
639 | 2.62M | { |
640 | 2.62M | gboolean valid = FALSE; |
641 | 2.62M | g_autoptr(GString) tmp = g_string_new(NULL); |
642 | | |
643 | | /* sanity check */ |
644 | 2.62M | if (str == NULL || maxsz == 0) |
645 | 1 | return NULL; |
646 | | |
647 | | /* replace non-printable chars with '.' */ |
648 | 12.1M | for (gsize i = 0; i < maxsz && str[i] != '\0'; i++) { |
649 | 9.49M | if (!g_ascii_isgraph(str[i]) && !g_ascii_isspace(str[i])) { |
650 | 2.69M | g_string_append_c(tmp, '.'); |
651 | 2.69M | continue; |
652 | 2.69M | } |
653 | 6.80M | g_string_append_c(tmp, str[i]); |
654 | 6.80M | if (!g_ascii_isspace(str[i])) |
655 | 6.11M | valid = TRUE; |
656 | 6.80M | } |
657 | | |
658 | | /* if just junk, don't return 'all dots' */ |
659 | 2.62M | if (tmp->len == 0 || !valid) |
660 | 1.04M | return NULL; |
661 | 1.58M | return g_string_free(g_steal_pointer(&tmp), FALSE); |
662 | 2.62M | } |
663 | | |
664 | | /** |
665 | | * fu_strsafe_bytes: |
666 | | * @blob: (not nullable): a #GBytes |
667 | | * @maxsz: maximum size of returned string, or %G_MAXSIZE for no limit |
668 | | * |
669 | | * Converts a #GBytes into something that can be safely printed. |
670 | | * |
671 | | * Returns: (transfer full): safe string, or %NULL if there was nothing valid |
672 | | * |
673 | | * Since: 2.0.2 |
674 | | **/ |
675 | | gchar * |
676 | | fu_strsafe_bytes(GBytes *blob, gsize maxsz) |
677 | 0 | { |
678 | 0 | g_return_val_if_fail(blob != NULL, NULL); |
679 | 0 | return fu_strsafe((const gchar *)g_bytes_get_data(blob, NULL), |
680 | 0 | MIN(g_bytes_get_size(blob), maxsz)); |
681 | 0 | } |
682 | | |
683 | | /** |
684 | | * fu_strjoin: |
685 | | * @separator: (nullable): string to insert between each of the strings |
686 | | * @array: (element-type utf8): a #GPtrArray |
687 | | * |
688 | | * Joins an array of strings together to form one long string, with the optional |
689 | | * separator inserted between each of them. |
690 | | * |
691 | | * If @array has no items, the return value will be an empty string. |
692 | | * If @array contains a single item, separator will not appear in the resulting |
693 | | * string. |
694 | | * |
695 | | * Returns: a string |
696 | | * |
697 | | * Since: 1.8.2 |
698 | | **/ |
699 | | gchar * |
700 | | fu_strjoin(const gchar *separator, GPtrArray *array) |
701 | 376 | { |
702 | 376 | g_autofree const gchar **strv = NULL; |
703 | | |
704 | 376 | g_return_val_if_fail(array != NULL, NULL); |
705 | | |
706 | 376 | strv = g_new0(const gchar *, array->len + 1); |
707 | 10.1k | for (guint i = 0; i < array->len; i++) |
708 | 9.73k | strv[i] = g_ptr_array_index(array, i); |
709 | 376 | return g_strjoinv(separator, (gchar **)strv); |
710 | 376 | } |
711 | | |
712 | | /** |
713 | | * fu_strpassmask: |
714 | | * @str: (nullable): a string to make safe for printing |
715 | | * |
716 | | * Hides password strings encoded in HTTP requests. |
717 | | * |
718 | | * Returns: a string |
719 | | * |
720 | | * Since: 1.9.10 |
721 | | **/ |
722 | | gchar * |
723 | | fu_strpassmask(const gchar *str) |
724 | 0 | { |
725 | 0 | g_autoptr(GString) tmp = g_string_new(str); |
726 | 0 | if (tmp->str != NULL && g_strstr_len(tmp->str, -1, "@") != NULL && |
727 | 0 | g_strstr_len(tmp->str, -1, ":") != NULL) { |
728 | 0 | gboolean is_password = FALSE; |
729 | 0 | gboolean is_url = FALSE; |
730 | 0 | for (guint i = 0; i < tmp->len; i++) { |
731 | 0 | const gchar *url_prefixes[] = {"http://", "https://", NULL}; |
732 | 0 | for (guint j = 0; url_prefixes[j] != NULL; j++) { |
733 | 0 | if (g_str_has_prefix(tmp->str + i, url_prefixes[j])) { |
734 | 0 | is_url = TRUE; |
735 | 0 | i += strlen(url_prefixes[j]); |
736 | 0 | break; |
737 | 0 | } |
738 | 0 | } |
739 | 0 | if (tmp->str[i] == ' ' || tmp->str[i] == '@' || tmp->str[i] == '/') { |
740 | 0 | is_url = FALSE; |
741 | 0 | is_password = FALSE; |
742 | 0 | continue; |
743 | 0 | } |
744 | 0 | if (is_url && tmp->str[i] == ':') { |
745 | 0 | is_password = TRUE; |
746 | 0 | continue; |
747 | 0 | } |
748 | 0 | if (is_url && is_password) { |
749 | 0 | if (tmp->str[i] == '@') { |
750 | 0 | is_password = FALSE; |
751 | 0 | continue; |
752 | 0 | } |
753 | 0 | tmp->str[i] = 'X'; |
754 | 0 | } |
755 | 0 | } |
756 | 0 | } |
757 | 0 | return g_string_free(g_steal_pointer(&tmp), FALSE); |
758 | 0 | } |
759 | | |
760 | | /** |
761 | | * fu_utf16_to_utf8_byte_array: |
762 | | * @array: a #GByteArray |
763 | | * @endian: an endian type, e.g. %G_LITTLE_ENDIAN |
764 | | * @error: (nullable): optional return location for an error |
765 | | * |
766 | | * Converts a UTF-16 buffer to a UTF-8 string. |
767 | | * |
768 | | * Returns: (transfer full): a string, or %NULL on error |
769 | | * |
770 | | * Since: 1.9.3 |
771 | | **/ |
772 | | gchar * |
773 | | fu_utf16_to_utf8_byte_array(GByteArray *array, FuEndianType endian, GError **error) |
774 | 9.82k | { |
775 | 9.82k | g_autofree guint16 *buf16 = NULL; |
776 | | |
777 | 9.82k | g_return_val_if_fail(array != NULL, NULL); |
778 | 9.82k | g_return_val_if_fail(error == NULL || *error == NULL, NULL); |
779 | | |
780 | 9.82k | if (array->len % 2 != 0) { |
781 | 358 | g_set_error_literal(error, |
782 | 358 | FWUPD_ERROR, |
783 | 358 | FWUPD_ERROR_INVALID_DATA, |
784 | 358 | "invalid UTF-16 buffer length"); |
785 | 358 | return NULL; |
786 | 358 | } |
787 | 9.47k | buf16 = g_new0(guint16, (array->len / sizeof(guint16)) + 1); |
788 | 64.0M | for (guint i = 0; i < array->len / 2; i++) { |
789 | 64.0M | guint16 data = fu_memread_uint16(array->data + (i * 2), endian); |
790 | 64.0M | fu_memwrite_uint16((guint8 *)(buf16 + i), data, G_BYTE_ORDER); |
791 | 64.0M | } |
792 | 9.47k | return g_utf16_to_utf8(buf16, array->len / sizeof(guint16), NULL, NULL, error); |
793 | 9.82k | } |
794 | | |
795 | | /** |
796 | | * fu_utf8_to_utf16_byte_array: |
797 | | * @str: a UTF-8 string |
798 | | * @endian: an endian type, e.g. %G_LITTLE_ENDIAN |
799 | | * @flags: a FuUtfConvertFlags, e.g. %FU_UTF_CONVERT_FLAG_APPEND_NUL |
800 | | * @error: (nullable): optional return location for an error |
801 | | * |
802 | | * Converts UTF-8 string to a buffer of UTF-16, optionially including the trailing NULw. |
803 | | * |
804 | | * Returns: (transfer full): a #GByteArray, or %NULL on error |
805 | | * |
806 | | * Since: 1.9.3 |
807 | | **/ |
808 | | GByteArray * |
809 | | fu_utf8_to_utf16_byte_array(const gchar *str, |
810 | | FuEndianType endian, |
811 | | FuUtfConvertFlags flags, |
812 | | GError **error) |
813 | 2.30k | { |
814 | 2.30k | glong buf_utf16sz = 0; |
815 | 2.30k | g_autoptr(GByteArray) array = g_byte_array_new(); |
816 | 2.30k | g_autofree gunichar2 *buf_utf16 = NULL; |
817 | | |
818 | 2.30k | g_return_val_if_fail(str != NULL, NULL); |
819 | 2.30k | g_return_val_if_fail(error == NULL || *error == NULL, NULL); |
820 | | |
821 | 2.30k | buf_utf16 = g_utf8_to_utf16(str, (glong)-1, NULL, &buf_utf16sz, error); |
822 | 2.30k | if (buf_utf16 == NULL) |
823 | 0 | return NULL; |
824 | 2.30k | if (flags & FU_UTF_CONVERT_FLAG_APPEND_NUL) |
825 | 1.71k | buf_utf16sz += 1; |
826 | 210k | for (glong i = 0; i < buf_utf16sz; i++) { |
827 | 208k | guint16 data = fu_memread_uint16((guint8 *)(buf_utf16 + i), G_BYTE_ORDER); |
828 | 208k | fu_byte_array_append_uint16(array, data, endian); |
829 | 208k | } |
830 | 2.30k | return g_steal_pointer(&array); |
831 | 2.30k | } |
832 | | |
833 | | /** |
834 | | * fu_utf16_to_utf8_bytes: |
835 | | * @bytes: a #GBytes |
836 | | * @endian: an endian type, e.g. %G_LITTLE_ENDIAN |
837 | | * @error: (nullable): optional return location for an error |
838 | | * |
839 | | * Converts a UTF-16 buffer to a UTF-8 string. |
840 | | * |
841 | | * Returns: (transfer full): a string, or %NULL on error |
842 | | * |
843 | | * Since: 1.9.3 |
844 | | **/ |
845 | | gchar * |
846 | | fu_utf16_to_utf8_bytes(GBytes *bytes, FuEndianType endian, GError **error) |
847 | 2.23k | { |
848 | 2.23k | GByteArray array = {0x0}; |
849 | | |
850 | 2.23k | g_return_val_if_fail(bytes != NULL, NULL); |
851 | 2.23k | g_return_val_if_fail(error == NULL || *error == NULL, NULL); |
852 | | |
853 | 2.23k | array.data = (guint8 *)g_bytes_get_data(bytes, NULL); |
854 | 2.23k | array.len = g_bytes_get_size(bytes); |
855 | 2.23k | return fu_utf16_to_utf8_byte_array(&array, endian, error); |
856 | 2.23k | } |
857 | | |
858 | | /** |
859 | | * fu_utf8_to_utf16_bytes: |
860 | | * @str: a UTF-8 string |
861 | | * @endian: an endian type, e.g. %G_LITTLE_ENDIAN |
862 | | * @error: (nullable): optional return location for an error |
863 | | * |
864 | | * Converts UTF-8 string to a buffer of UTF-16, optionally including the trailing NULw. |
865 | | * |
866 | | * Returns: (transfer full): a #GBytes, or %NULL on error |
867 | | * |
868 | | * Since: 1.9.3 |
869 | | **/ |
870 | | GBytes * |
871 | | fu_utf8_to_utf16_bytes(const gchar *str, |
872 | | FuEndianType endian, |
873 | | FuUtfConvertFlags flags, |
874 | | GError **error) |
875 | 483 | { |
876 | 483 | g_autoptr(GByteArray) buf = NULL; |
877 | | |
878 | 483 | g_return_val_if_fail(str != NULL, NULL); |
879 | 483 | g_return_val_if_fail(error == NULL || *error == NULL, NULL); |
880 | | |
881 | 483 | buf = fu_utf8_to_utf16_byte_array(str, endian, flags, error); |
882 | 483 | if (buf == NULL) |
883 | 0 | return NULL; |
884 | 483 | return g_bytes_new(buf->data, buf->len); |
885 | 483 | } |