Coverage Report

Created: 2025-08-29 06:48

/src/libxmlb/src/xb-string.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright 2018 Richard Hughes <richard@hughsie.com>
3
 *
4
 * SPDX-License-Identifier: LGPL-2.1-or-later
5
 */
6
7
0
#define G_LOG_DOMAIN "XbSilo"
8
9
#include "config.h"
10
11
#include <gio/gio.h>
12
#include <string.h>
13
14
#include "xb-string-private.h"
15
16
/**
17
 * xb_string_replace: (skip)
18
 * @str: The #GString to operate on
19
 * @search: The text to search for
20
 * @replace: The text to use for substitutions
21
 *
22
 * Performs multiple search and replace operations on the given string.
23
 *
24
 * Returns: the number of replacements done, or 0 if @search is not found.
25
 **/
26
guint
27
xb_string_replace(GString *str, const gchar *search, const gchar *replace)
28
6.55M
{
29
6.55M
  gchar *tmp;
30
6.55M
  guint count = 0;
31
6.55M
  gsize search_idx = 0;
32
6.55M
  gsize replace_len;
33
6.55M
  gsize search_len;
34
35
6.55M
  g_return_val_if_fail(str != NULL, 0);
36
6.55M
  g_return_val_if_fail(search != NULL, 0);
37
6.55M
  g_return_val_if_fail(replace != NULL, 0);
38
39
  /* nothing to do */
40
6.55M
  if (str->len == 0)
41
0
    return 0;
42
43
6.55M
  search_len = strlen(search);
44
6.55M
  replace_len = strlen(replace);
45
46
6.55M
  do {
47
6.55M
    tmp = g_strstr_len(str->str + search_idx, -1, search);
48
6.55M
    if (tmp == NULL)
49
6.55M
      break;
50
51
    /* advance the counter in case @replace contains @search */
52
1.27k
    search_idx = (gsize)(tmp - str->str);
53
54
    /* reallocate the string if required */
55
1.27k
    if (search_len > replace_len) {
56
0
      g_string_erase(str, (gssize)search_idx, (gssize)(search_len - replace_len));
57
0
      memcpy(tmp, replace, replace_len);
58
1.27k
    } else if (search_len < replace_len) {
59
1.27k
      g_string_insert_len(str,
60
1.27k
              (gssize)search_idx,
61
1.27k
              replace,
62
1.27k
              (gssize)(replace_len - search_len));
63
      /* we have to treat this specially as it could have
64
       * been reallocated when the insertion happened */
65
1.27k
      memcpy(str->str + search_idx, replace, replace_len);
66
1.27k
    } else {
67
      /* just memcmp in the new string */
68
0
      memcpy(tmp, replace, replace_len);
69
0
    }
70
1.27k
    search_idx += replace_len;
71
1.27k
    count++;
72
1.27k
  } while (TRUE);
73
74
0
  return count;
75
6.55M
}
76
77
/**
78
 * xb_string_append_union:
79
 * @xpath: The #GString to operate on
80
 * @fmt: The format string
81
 * @...: varargs for @fmt
82
 *
83
 * Appends an XPath query into the string, automatically adding the union
84
 * operator (`|`) if required.
85
 *
86
 * Since: 0.1.2
87
 **/
88
void
89
xb_string_append_union(GString *xpath, const gchar *fmt, ...)
90
0
{
91
0
  va_list args;
92
93
0
  g_return_if_fail(xpath != NULL);
94
0
  g_return_if_fail(fmt != NULL);
95
96
0
  if (xpath->len > 0)
97
0
    g_string_append(xpath, "|");
98
0
  va_start(args, fmt);
99
0
#pragma clang diagnostic ignored "-Wformat-nonliteral"
100
0
  g_string_append_vprintf(xpath, fmt, args);
101
0
#pragma clang diagnostic pop
102
0
  va_end(args);
103
0
}
104
105
/**
106
 * xb_string_contains: (skip)
107
 * @text: The source string
108
 * @search: The text to search for
109
 *
110
 * Searches for a substring match.
111
 *
112
 * Returns: %TRUE if the string @search is contained in @text.
113
 **/
114
gboolean
115
xb_string_contains(const gchar *text, const gchar *search)
116
0
{
117
0
  guint search_sz;
118
0
  guint text_sz;
119
120
  /* can't possibly match */
121
0
  if (text == NULL || search == NULL)
122
0
    return FALSE;
123
124
  /* sanity check */
125
0
  text_sz = strlen(text);
126
0
  search_sz = strlen(search);
127
0
  if (search_sz > text_sz)
128
0
    return FALSE;
129
0
  for (guint i = 0; i < text_sz - search_sz + 1; i++) {
130
0
    if (strncmp(text + i, search, search_sz) == 0)
131
0
      return TRUE;
132
0
  }
133
0
  return FALSE;
134
0
}
135
136
/**
137
 * xb_string_search: (skip)
138
 * @text: The source string
139
 * @search: The text to search for
140
 *
141
 * Searches for a fuzzy search match, ignoring search matches that are not at
142
 * the start of the token.
143
 *
144
 * For example, `foo` and `baz` would match `foobar baz` but `bar` would not
145
 * match the same string.
146
 *
147
 * Returns: %TRUE if the string @search is contained in @text.
148
 *
149
 * Since: 0.3.0
150
 **/
151
gboolean
152
xb_string_search(const gchar *text, const gchar *search)
153
0
{
154
0
  guint search_sz;
155
0
  guint text_sz;
156
0
  gboolean is_sow = TRUE;
157
158
  /* can't possibly match */
159
0
  if (text == NULL || text[0] == '\0')
160
0
    return FALSE;
161
0
  if (search == NULL || search[0] == '\0')
162
0
    return FALSE;
163
164
  /* sanity check */
165
0
  text_sz = strlen(text);
166
0
  search_sz = strlen(search);
167
0
  if (search_sz > text_sz)
168
0
    return FALSE;
169
0
  for (guint i = 0; i < text_sz - search_sz + 1; i++) {
170
0
    if (!g_ascii_isalnum(text[i])) {
171
0
      is_sow = TRUE;
172
0
      continue;
173
0
    }
174
0
    if (!is_sow)
175
0
      continue;
176
0
    if (g_ascii_strncasecmp(text + i, search, search_sz) == 0)
177
0
      return TRUE;
178
    /* no longer the start of the word */
179
0
    is_sow = FALSE;
180
0
  }
181
0
  return FALSE;
182
0
}
183
184
/**
185
 * xb_string_searchv: (skip)
186
 * @text: NULL-terminated source strings
187
 * @search: NULL-terminated text tokens to search for
188
 *
189
 * Searches for a fuzzy search match, ignoring search matches that are not at
190
 * the start of the token.
191
 *
192
 * For example `{"foo", "baz"}` would match with `{"wizz", "foobar"}` but
193
 * `{"baz"}` would not match the same set of strings.
194
 *
195
 * Returns: %TRUE if the string @search is contained in @text.
196
 *
197
 * Since: 0.3.0
198
 **/
199
gboolean
200
xb_string_searchv(const gchar **text, const gchar **search)
201
0
{
202
0
  if (text == NULL || text[0] == NULL || text[0][0] == '\0')
203
0
    return FALSE;
204
0
  if (search == NULL || search[0] == NULL || search[0][0] == '\0')
205
0
    return FALSE;
206
0
  for (guint j = 0; text[j] != NULL; j++) {
207
0
    for (guint i = 0; search[i] != NULL; i++) {
208
0
      if (g_str_has_prefix(text[j], search[i]))
209
0
        return TRUE;
210
0
    }
211
0
  }
212
0
  return FALSE;
213
0
}
214
215
/**
216
 * xb_string_token_valid: (skip)
217
 * @text: The potential token
218
 * @see_also: xb_builder_node_tokenize_text(), xb_builder_node_get_tokens()
219
 *
220
 * This is typically used to eliminate tokens which are not useful for search
221
 * matching.
222
 *
223
 * For instance, tokens like `in` and `at` are less than three characters in
224
 * size and should be rejected.
225
 *
226
 * Returns: %TRUE if the token should be used for searching.
227
 **/
228
gboolean
229
xb_string_token_valid(const gchar *text)
230
0
{
231
0
  if (text == NULL)
232
0
    return FALSE;
233
0
  if (text[0] == '\0' || text[1] == '\0' || text[2] == '\0')
234
0
    return FALSE;
235
0
  return TRUE;
236
0
}
237
238
/**
239
 * xb_string_escape:
240
 * @str: string, e.g. `app/org.gnome.ghex/x86_64/stable`
241
 *
242
 * Escapes XPath control sequences such as newlines, tabs, and forward slashes.
243
 *
244
 * Returns: (transfer full): new string that is safe to use for queries
245
 *
246
 * Since: 0.1.2
247
 **/
248
gchar *
249
xb_string_escape(const gchar *str)
250
0
{
251
0
  GString *tmp = g_string_new(str);
252
0
  xb_string_replace(tmp, "/", "\\/");
253
0
  xb_string_replace(tmp, "\t", "\\t");
254
0
  xb_string_replace(tmp, "\n", "\\n");
255
0
  return g_string_free(tmp, FALSE);
256
0
}
257
258
gchar *
259
xb_string_xml_escape(const gchar *str)
260
1.63M
{
261
1.63M
  GString *tmp = g_string_new(str);
262
1.63M
  xb_string_replace(tmp, "&", "&amp;");
263
1.63M
  xb_string_replace(tmp, "<", "&lt;");
264
1.63M
  xb_string_replace(tmp, ">", "&gt;");
265
1.63M
  xb_string_replace(tmp, "\"", "&quot;");
266
1.63M
  return g_string_free(tmp, FALSE);
267
1.63M
}
268
269
/* private */
270
gboolean
271
xb_string_isspace(const gchar *str, gssize strsz)
272
667k
{
273
667k
  gsize strsz_safe;
274
667k
  if (str == NULL)
275
0
    return TRUE;
276
667k
  strsz_safe = strsz >= 0 ? (gsize)strsz : strlen(str);
277
669k
  for (gsize i = 0; i < strsz_safe; i++) {
278
669k
    if (!g_ascii_isspace(str[i]))
279
667k
      return FALSE;
280
669k
  }
281
0
  return TRUE;
282
667k
}
283
284
void
285
xb_guid_compute_for_data(XbGuid *out, const guint8 *buf, gsize bufsz)
286
0
{
287
0
  guint8 buf_tmp[20] = {0x0};
288
0
  gsize buf_tmpsz = sizeof(buf_tmp);
289
0
  g_autoptr(GChecksum) checksum = g_checksum_new(G_CHECKSUM_SHA1);
290
0
  if (buf != NULL && bufsz != 0)
291
0
    g_checksum_update(checksum, (const guchar *)buf, bufsz);
292
0
  g_checksum_get_digest(checksum, buf_tmp, &buf_tmpsz);
293
0
  memcpy(out, buf_tmp, sizeof(XbGuid));
294
0
}
295
296
gchar *
297
xb_guid_to_string(XbGuid *guid)
298
0
{
299
0
  return g_strdup_printf("%08x-%04x-%04x-%04x-%02x%02x%02x%02x%02x%02x",
300
0
             (guint)GUINT32_TO_BE(guid->tlo),
301
0
             (guint)GUINT16_TO_BE(guid->tmi),
302
0
             (guint)GUINT16_TO_BE(guid->thi),
303
0
             (guint)GUINT16_TO_BE(guid->clo),
304
0
             guid->nde[0],
305
0
             guid->nde[1],
306
0
             guid->nde[2],
307
0
             guid->nde[3],
308
0
             guid->nde[4],
309
0
             guid->nde[5]);
310
0
}