Coverage Report

Created: 2025-08-26 06:55

/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
}