Coverage Report

Created: 2025-11-11 06:44

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}