/
githubmirror
/
fwupd
Обзор
Документация
Войти
/
githubmirror
/
fwupd
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
2.0.21
libfwupdplugin/fu-string.c
885 строк
22 KB
Richard Hughes
trivial: Strip whitespace from the string when using fu_device_add_instance_strsafe()
03 окт 2025, 22:56
03 окт 2025, 22:56
c47db9e
Код
Авторство
О чём код?
/* * Copyright 2017 Richard Hughes <richard@hughsie.com> * * SPDX-License-Identifier: LGPL-2.1-or-later */ #define G_LOG_DOMAIN "FuCommon" #include "config.h" #include "fu-byte-array.h" #include "fu-chunk-array.h" #include "fu-mem.h" #include "fu-partial-input-stream.h" #include "fu-string.h" /** * fu_strtoull: * @str: a string, e.g. `0x1234` * @value: (out) (nullable): parsed value * @min: minimum acceptable value, typically 0 * @max: maximum acceptable value, typically G_MAXUINT64 * @base: default log base, usually %FU_INTEGER_BASE_AUTO * @error: (nullable): optional return location for an error * * Converts a string value to an integer. If the @value is prefixed with `0x` then the base is * set to 16 automatically. * * Returns: %TRUE if the value was parsed correctly, or %FALSE for error * * Since: 2.0.0 **/ gboolean fu_strtoull(const gchar *str, guint64 *value, guint64 min, guint64 max, FuIntegerBase base, GError **error) { gchar *endptr = NULL; guint64 value_tmp; /* sanity check */ if (str == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_DATA, "cannot parse NULL"); return FALSE; } /* detect hex */ if (base == FU_INTEGER_BASE_AUTO) { if (g_str_has_prefix(str, "0x")) { str += 2; base = FU_INTEGER_BASE_16; } else { base = FU_INTEGER_BASE_10; } } else if (base == FU_INTEGER_BASE_16 && g_str_has_prefix(str, "0x")) { str += 2; } else if (base == FU_INTEGER_BASE_10 && g_str_has_prefix(str, "0x")) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_DATA, "cannot parse 0x-prefixed base-10 string"); return FALSE; } /* convert */ value_tmp = g_ascii_strtoull(str, &endptr, base); /* nocheck:blocked */ if ((gsize)(endptr - str) != strlen(str) && *endptr != '\n') { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_DATA, "cannot parse datastream"); return FALSE; } /* overflow check */ if (value_tmp == G_MAXUINT64) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_DATA, "parsing datastream caused overflow"); return FALSE; } /* range check */ if (value_tmp < min) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_DATA, "value %" G_GUINT64_FORMAT " was below minimum %" G_GUINT64_FORMAT, value_tmp, min); return FALSE; } if (value_tmp > max) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_DATA, "value %" G_GUINT64_FORMAT " was above maximum %" G_GUINT64_FORMAT, value_tmp, max); return FALSE; } /* success */ if (value != NULL) *value = value_tmp; return TRUE; } /** * fu_strtoll: * @str: a string, e.g. `0x1234`, `-12345` * @value: (out) (nullable): parsed value * @min: minimum acceptable value, typically 0 * @max: maximum acceptable value, typically G_MAXINT64 * @base: default log base, usually %FU_INTEGER_BASE_AUTO * @error: (nullable): optional return location for an error * * Converts a string value to an integer. Values are assumed base 10, unless * prefixed with "0x" where they are parsed as base 16. * * Returns: %TRUE if the value was parsed correctly, or %FALSE for error * * Since: 2.0.0 **/ gboolean fu_strtoll(const gchar *str, gint64 *value, gint64 min, gint64 max, FuIntegerBase base, GError **error) { gchar *endptr = NULL; gint64 value_tmp; /* sanity check */ if (str == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_DATA, "cannot parse NULL"); return FALSE; } /* detect hex */ if (base == FU_INTEGER_BASE_AUTO) { if (g_str_has_prefix(str, "0x")) { str += 2; base = FU_INTEGER_BASE_16; } else { base = FU_INTEGER_BASE_10; } } else if (base == FU_INTEGER_BASE_16 && g_str_has_prefix(str, "0x")) { str += 2; } else if (base == FU_INTEGER_BASE_10 && g_str_has_prefix(str, "0x")) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_DATA, "cannot parse 0x-prefixed base-10 string"); return FALSE; } /* convert */ value_tmp = g_ascii_strtoll(str, &endptr, base); /* nocheck:blocked */ if ((gsize)(endptr - str) != strlen(str) && *endptr != '\n') { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_DATA, "cannot parse %s", str); return FALSE; } /* overflow check */ if (value_tmp == G_MAXINT64) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_DATA, "cannot parse %s as caused overflow", str); return FALSE; } /* range check */ if (value_tmp < min) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_DATA, "value %" G_GINT64_FORMAT " was below minimum %" G_GINT64_FORMAT, value_tmp, min); return FALSE; } if (value_tmp > max) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_DATA, "value %" G_GINT64_FORMAT " was above maximum %" G_GINT64_FORMAT, value_tmp, max); return FALSE; } /* success */ if (value != NULL) *value = value_tmp; return TRUE; } /** * fu_strtobool: * @str: a string, e.g. `true` * @value: (out) (nullable): parsed value * @error: (nullable): optional return location for an error * * Converts a string value to a boolean. Only `true` and `false` are accepted values. * * Returns: %TRUE if the value was parsed correctly, or %FALSE for error * * Since: 1.8.2 **/ gboolean fu_strtobool(const gchar *str, gboolean *value, GError **error) { /* sanity check */ if (str == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_DATA, "cannot parse NULL"); return FALSE; } /* be super strict */ if (g_strcmp0(str, "true") == 0) { if (value != NULL) *value = TRUE; return TRUE; } if (g_strcmp0(str, "false") == 0) { if (value != NULL) *value = FALSE; return TRUE; } /* invalid */ g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_DATA, "cannot parse %s as boolean, expected true|false", str); return FALSE; } /** * fu_strstrip: * @str: a string, e.g. ` test ` * * Removes leading and trailing whitespace from a constant string. * * Returns: newly allocated string * * Since: 1.8.2 **/ gchar * fu_strstrip(const gchar *str) { guint head = G_MAXUINT; guint tail = 0; g_return_val_if_fail(str != NULL, NULL); /* find first non-space char */ for (guint i = 0; str[i] != '\0'; i++) { if (str[i] != ' ') { head = i; break; } } if (head == G_MAXUINT) return g_strdup(""); /* find last non-space char */ for (guint i = head; str[i] != '\0'; i++) { if (!g_ascii_isspace(str[i])) tail = i; } return g_strndup(str + head, tail - head + 1); } /** * fu_string_strip: * @str: a #GString, e.g. ` test ` * * Removes leading and trailing whitespace from a mutable string. * * Since: 2.0.17 **/ void fu_string_strip(GString *str) { guint i; /* leading whitespace */ for (i = 0; i < str->len; i++) { if (!g_ascii_isspace(str->str[i])) break; } if (i > 0) g_string_erase(str, 0, i); /* trailing whitespace */ for (i = 0; i < str->len; i++) { if (!g_ascii_isspace(str->str[str->len - (i + 1)])) break; } if (i < str->len) g_string_truncate(str, str->len - i); } /** * fu_strdup: * @str: a string, e.g. ` test ` * @bufsz: the maximum size of @str * @offset: the offset to start copying from * * Copies a string from a buffer of a specified size up to (but not including) `NUL`. * * Returns: (transfer full): a #GString, possibly of zero size. * * Since: 1.8.11 **/ GString * fu_strdup(const gchar *str, gsize bufsz, gsize offset) { GString *substr; g_return_val_if_fail(str != NULL, NULL); g_return_val_if_fail(offset < bufsz, NULL); substr = g_string_new(NULL); while (offset < bufsz) { if (str[offset] == '\0') break; g_string_append_c(substr, str[offset++]); } return substr; } /** * fu_strwidth: * @text: the string to operate on * * Returns the width of the string in displayed characters on the console. * * Returns: width of text * * Since: 1.8.2 **/ gsize fu_strwidth(const gchar *text) { const gchar *p = text; gsize width = 0; g_return_val_if_fail(text != NULL, 0); while (*p) { gunichar c = g_utf8_get_char(p); if (g_unichar_iswide(c)) width += 2; else if (!g_unichar_iszerowidth(c)) width += 1; p = g_utf8_next_char(p); } return width; } /** * fu_strsplit: * @str: (not nullable): a string to split * @sz: size of @str, which must be more than 0 * @delimiter: a string which specifies the places at which to split the string * @max_tokens: the maximum number of pieces to split @str into * * Splits a string into a maximum of @max_tokens pieces, using the given * delimiter. If @max_tokens is reached, the remainder of string is appended * to the last token. * * Returns: (transfer full): a newly-allocated NULL-terminated array of strings * * Since: 1.8.2 **/ gchar ** fu_strsplit(const gchar *str, gsize sz, const gchar *delimiter, gint max_tokens) { g_return_val_if_fail(str != NULL, NULL); g_return_val_if_fail(sz > 0, NULL); if (str[sz - 1] != '\0') { g_autofree gchar *str2 = g_strndup(str, sz); return g_strsplit(str2, delimiter, max_tokens); } return g_strsplit(str, delimiter, max_tokens); } /** * fu_strsplit_bytes: * @blob: (not nullable): a #GBytes * @delimiter: a string which specifies the places at which to split the string * @max_tokens: the maximum number of pieces to split @str into * * Splits a string into a maximum of @max_tokens pieces, using the given * delimiter. If @max_tokens is reached, the remainder of string is appended * to the last token. * * Returns: (transfer full): a newly-allocated NULL-terminated array of strings * * Since: 2.0.7 **/ gchar ** fu_strsplit_bytes(GBytes *blob, const gchar *delimiter, gint max_tokens) { g_return_val_if_fail(blob != NULL, NULL); return fu_strsplit(g_bytes_get_data(blob, NULL), g_bytes_get_size(blob), delimiter, max_tokens); } typedef struct { FuStrsplitFunc callback; gpointer user_data; guint token_idx; const gchar *delimiter; gsize delimiter_sz; gboolean detected_nul; gboolean more_chunks; } FuStrsplitHelper; static gboolean fu_strsplit_buffer_drain(GByteArray *buf, FuStrsplitHelper *helper, GError **error) { gsize buf_offset = 0; while (buf_offset <= buf->len) { gsize offset; g_autoptr(GString) token = g_string_new(NULL); /* find first match in buffer, starting at the buffer offset */ for (offset = buf_offset; offset < buf->len; offset++) { if (buf->data[offset] == 0x0) { helper->detected_nul = TRUE; break; } if (strncmp((const gchar *)buf->data + offset, helper->delimiter, helper->delimiter_sz) == 0) break; } /* no token found, keep going */ if (helper->more_chunks && offset == buf->len) break; /* sanity check is valid UTF-8 */ g_string_append_len(token, (const gchar *)buf->data + buf_offset, offset - buf_offset); if (!g_utf8_validate_len(token->str, token->len, NULL)) { g_debug("ignoring invalid UTF-8, got: %s", token->str); } else { if (!helper->callback(token, helper->token_idx++, helper->user_data, error)) return FALSE; } if (helper->detected_nul) { buf_offset = buf->len; break; } buf_offset = offset + helper->delimiter_sz; } g_byte_array_remove_range(buf, 0, MIN(buf_offset, buf->len)); return TRUE; } /** * fu_strsplit_stream: * @stream: a #GInputStream to split * @offset: offset into @stream * @delimiter: a string which specifies the places at which to split the string * @callback: (scope call) (closure user_data): a #FuStrsplitFunc. * @user_data: user data * @error: (nullable): optional return location for an error * * Splits the string, calling the given function for each * of the tokens found. If any @callback returns %FALSE scanning is aborted. * * Use this function in preference to fu_strsplit() when the input file is untrusted, * and you don't want to allocate a GStrv with billions of one byte items. * * Returns: %TRUE if no @callback returned FALSE * * Since: 2.0.0 */ gboolean fu_strsplit_stream(GInputStream *stream, gsize offset, const gchar *delimiter, FuStrsplitFunc callback, gpointer user_data, GError **error) { g_autoptr(FuChunkArray) chunks = NULL; g_autoptr(GByteArray) buf = g_byte_array_new(); g_autoptr(GInputStream) stream_partial = NULL; FuStrsplitHelper helper = { .callback = callback, .user_data = user_data, .delimiter = delimiter, .token_idx = 0, }; g_return_val_if_fail(G_IS_INPUT_STREAM(stream), FALSE); g_return_val_if_fail(delimiter != NULL && delimiter[0] != '\0', FALSE); g_return_val_if_fail(callback != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); helper.delimiter_sz = strlen(delimiter); if (offset > 0) { stream_partial = fu_partial_input_stream_new(stream, offset, G_MAXSIZE, error); if (stream_partial == NULL) { g_prefix_error_literal(error, "failed to cut string: "); return FALSE; } } else { stream_partial = g_object_ref(stream); } chunks = fu_chunk_array_new_from_stream(stream_partial, FU_CHUNK_ADDR_OFFSET_NONE, FU_CHUNK_PAGESZ_NONE, 0x8000, error); if (chunks == NULL) return FALSE; for (gsize i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i, error); if (chk == NULL) return FALSE; g_byte_array_append(buf, fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk)); helper.more_chunks = i != fu_chunk_array_length(chunks) - 1; if (!fu_strsplit_buffer_drain(buf, &helper, error)) return FALSE; if (helper.detected_nul) break; } return TRUE; } /** * fu_strsplit_full: * @str: a string to split * @sz: size of @str, or -1 for unknown * @delimiter: a string which specifies the places at which to split the string * @callback: (scope call) (closure user_data): a #FuStrsplitFunc. * @user_data: user data * @error: (nullable): optional return location for an error * * Splits the string, calling the given function for each * of the tokens found. If any @callback returns %FALSE scanning is aborted. * * Use this function in preference to fu_strsplit() when the input file is untrusted, * and you don't want to allocate a GStrv with billions of one byte items. * * Returns: %TRUE if no @callback returned FALSE * * Since: 1.8.2 */ gboolean fu_strsplit_full(const gchar *str, gssize sz, const gchar *delimiter, FuStrsplitFunc callback, gpointer user_data, GError **error) { gsize delimiter_sz; gsize offset_old = 0; gsize str_sz; guint token_idx = 0; g_return_val_if_fail(str != NULL, FALSE); g_return_val_if_fail(delimiter != NULL && delimiter[0] != '\0', FALSE); g_return_val_if_fail(callback != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* make known */ str_sz = sz != -1 ? (gsize)sz : strlen(str); delimiter_sz = strlen(delimiter); /* cannot split */ if (delimiter_sz > str_sz) { g_autoptr(GString) token = g_string_new(str); return callback(token, token_idx, user_data, error); } /* start splittin' */ while (offset_old <= str_sz) { gsize offset; g_autoptr(GString) token = g_string_new(NULL); for (offset = offset_old; offset < str_sz; offset++) { if (strncmp(str + offset, delimiter, delimiter_sz) == 0) break; } g_string_append_len(token, str + offset_old, offset - offset_old); if (!callback(token, token_idx++, user_data, error)) return FALSE; offset_old = offset + delimiter_sz; } /* success */ return TRUE; } /** * fu_strsafe: * @str: (nullable): a string to make safe for printing * @maxsz: maximum size of returned string, or %G_MAXSIZE for no limit * * Converts a string into something that can be safely printed. * * Returns: (transfer full): safe string, or %NULL if there was nothing valid * * Since: 1.8.2 **/ gchar * fu_strsafe(const gchar *str, gsize maxsz) { gboolean valid = FALSE; g_autoptr(GString) tmp = g_string_new(NULL); /* sanity check */ if (str == NULL || maxsz == 0) return NULL; /* replace non-printable chars with '.' */ for (gsize i = 0; i < maxsz && str[i] != '\0'; i++) { if (!g_ascii_isgraph(str[i]) && !g_ascii_isspace(str[i])) { g_string_append_c(tmp, '.'); continue; } g_string_append_c(tmp, str[i]); if (!g_ascii_isspace(str[i])) valid = TRUE; } /* if just junk, don't return 'all dots' */ if (tmp->len == 0 || !valid) return NULL; return g_string_free(g_steal_pointer(&tmp), FALSE); } /** * fu_strsafe_bytes: * @blob: (not nullable): a #GBytes * @maxsz: maximum size of returned string, or %G_MAXSIZE for no limit * * Converts a #GBytes into something that can be safely printed. * * Returns: (transfer full): safe string, or %NULL if there was nothing valid * * Since: 2.0.2 **/ gchar * fu_strsafe_bytes(GBytes *blob, gsize maxsz) { g_return_val_if_fail(blob != NULL, NULL); return fu_strsafe((const gchar *)g_bytes_get_data(blob, NULL), MIN(g_bytes_get_size(blob), maxsz)); } /** * fu_strjoin: * @separator: (nullable): string to insert between each of the strings * @array: (element-type utf8): a #GPtrArray * * Joins an array of strings together to form one long string, with the optional * separator inserted between each of them. * * If @array has no items, the return value will be an empty string. * If @array contains a single item, separator will not appear in the resulting * string. * * Returns: a string * * Since: 1.8.2 **/ gchar * fu_strjoin(const gchar *separator, GPtrArray *array) { g_autofree const gchar **strv = NULL; g_return_val_if_fail(array != NULL, NULL); strv = g_new0(const gchar *, array->len + 1); for (guint i = 0; i < array->len; i++) strv[i] = g_ptr_array_index(array, i); return g_strjoinv(separator, (gchar **)strv); } /** * fu_strpassmask: * @str: (nullable): a string to make safe for printing * * Hides password strings encoded in HTTP requests. * * Returns: a string * * Since: 1.9.10 **/ gchar * fu_strpassmask(const gchar *str) { g_autoptr(GString) tmp = g_string_new(str); if (tmp->str != NULL && g_strstr_len(tmp->str, -1, "@") != NULL && g_strstr_len(tmp->str, -1, ":") != NULL) { gboolean is_password = FALSE; gboolean is_url = FALSE; for (guint i = 0; i < tmp->len; i++) { const gchar *url_prefixes[] = {"http://", "https://", NULL}; for (guint j = 0; url_prefixes[j] != NULL; j++) { if (g_str_has_prefix(tmp->str + i, url_prefixes[j])) { is_url = TRUE; i += strlen(url_prefixes[j]); break; } } if (tmp->str[i] == ' ' || tmp->str[i] == '@' || tmp->str[i] == '/') { is_url = FALSE; is_password = FALSE; continue; } if (is_url && tmp->str[i] == ':') { is_password = TRUE; continue; } if (is_url && is_password) { if (tmp->str[i] == '@') { is_password = FALSE; continue; } tmp->str[i] = 'X'; } } } return g_string_free(g_steal_pointer(&tmp), FALSE); } /** * fu_utf16_to_utf8_byte_array: * @array: a #GByteArray * @endian: an endian type, e.g. %G_LITTLE_ENDIAN * @error: (nullable): optional return location for an error * * Converts a UTF-16 buffer to a UTF-8 string. * * Returns: (transfer full): a string, or %NULL on error * * Since: 1.9.3 **/ gchar * fu_utf16_to_utf8_byte_array(GByteArray *array, FuEndianType endian, GError **error) { g_autofree guint16 *buf16 = NULL; g_return_val_if_fail(array != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); if (array->len % 2 != 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_DATA, "invalid UTF-16 buffer length"); return NULL; } buf16 = g_new0(guint16, (array->len / sizeof(guint16)) + 1); for (guint i = 0; i < array->len / 2; i++) { guint16 data = fu_memread_uint16(array->data + (i * 2), endian); fu_memwrite_uint16((guint8 *)(buf16 + i), data, G_BYTE_ORDER); } return g_utf16_to_utf8(buf16, array->len / sizeof(guint16), NULL, NULL, error); } /** * fu_utf8_to_utf16_byte_array: * @str: a UTF-8 string * @endian: an endian type, e.g. %G_LITTLE_ENDIAN * @flags: a FuUtfConvertFlags, e.g. %FU_UTF_CONVERT_FLAG_APPEND_NUL * @error: (nullable): optional return location for an error * * Converts UTF-8 string to a buffer of UTF-16, optionially including the trailing NULw. * * Returns: (transfer full): a #GByteArray, or %NULL on error * * Since: 1.9.3 **/ GByteArray * fu_utf8_to_utf16_byte_array(const gchar *str, FuEndianType endian, FuUtfConvertFlags flags, GError **error) { glong buf_utf16sz = 0; g_autoptr(GByteArray) array = g_byte_array_new(); g_autofree gunichar2 *buf_utf16 = NULL; g_return_val_if_fail(str != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); buf_utf16 = g_utf8_to_utf16(str, (glong)-1, NULL, &buf_utf16sz, error); if (buf_utf16 == NULL) return NULL; if (flags & FU_UTF_CONVERT_FLAG_APPEND_NUL) buf_utf16sz += 1; for (glong i = 0; i < buf_utf16sz; i++) { guint16 data = fu_memread_uint16((guint8 *)(buf_utf16 + i), G_BYTE_ORDER); fu_byte_array_append_uint16(array, data, endian); } return g_steal_pointer(&array); } /** * fu_utf16_to_utf8_bytes: * @bytes: a #GBytes * @endian: an endian type, e.g. %G_LITTLE_ENDIAN * @error: (nullable): optional return location for an error * * Converts a UTF-16 buffer to a UTF-8 string. * * Returns: (transfer full): a string, or %NULL on error * * Since: 1.9.3 **/ gchar * fu_utf16_to_utf8_bytes(GBytes *bytes, FuEndianType endian, GError **error) { GByteArray array = {0x0}; g_return_val_if_fail(bytes != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); array.data = (guint8 *)g_bytes_get_data(bytes, NULL); array.len = g_bytes_get_size(bytes); return fu_utf16_to_utf8_byte_array(&array, endian, error); } /** * fu_utf8_to_utf16_bytes: * @str: a UTF-8 string * @endian: an endian type, e.g. %G_LITTLE_ENDIAN * @error: (nullable): optional return location for an error * * Converts UTF-8 string to a buffer of UTF-16, optionally including the trailing NULw. * * Returns: (transfer full): a #GBytes, or %NULL on error * * Since: 1.9.3 **/ GBytes * fu_utf8_to_utf16_bytes(const gchar *str, FuEndianType endian, FuUtfConvertFlags flags, GError **error) { g_autoptr(GByteArray) buf = NULL; g_return_val_if_fail(str != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); buf = fu_utf8_to_utf16_byte_array(str, endian, flags, error); if (buf == NULL) return NULL; return g_bytes_new(buf->data, buf->len); }