Coverage Report

Created: 2025-07-18 06:26

/src/libxmlb/src/xb-common.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright 2020 Richard Hughes <richard@hughsie.com>
3
 *
4
 * SPDX-License-Identifier: LGPL-2.1-or-later
5
 */
6
7
0
#define G_LOG_DOMAIN "XbCommon"
8
9
#include "config.h"
10
11
#include <string.h>
12
13
#include "xb-common-private.h"
14
15
static const gchar *
16
xb_content_type_guess_from_fn(const gchar *filename)
17
0
{
18
0
  gchar *ext; /* no ownership */
19
20
0
  g_return_val_if_fail(filename != NULL, NULL);
21
22
  /* get file extension with dot */
23
0
  ext = g_strrstr(filename, ".");
24
0
  if (ext == NULL)
25
0
    return NULL;
26
27
  /* map Windows "mime-type" to a content type */
28
0
  if (g_strcmp0(ext, ".gz") == 0)
29
0
    return "application/gzip";
30
0
  if (g_strcmp0(ext, ".xz") == 0)
31
0
    return "application/x-xz";
32
0
  if (g_strcmp0(ext, ".zst") == 0)
33
0
    return "application/zstd";
34
0
  if (g_strcmp0(ext, ".xml") == 0)
35
0
    return "application/xml";
36
0
  if (g_strcmp0(ext, ".desktop") == 0)
37
0
    return "application/x-desktop";
38
0
  if (g_strcmp0(ext, ".quirk") == 0)
39
0
    return "text/plain";
40
0
  return NULL;
41
0
}
42
43
static gboolean
44
xb_content_type_match(const guchar *buf,
45
          gsize bufsz,
46
          gsize offset,
47
          const gchar *magic,
48
          gsize magic_size)
49
0
{
50
  /* document too small */
51
0
  if (offset + magic_size > bufsz)
52
0
    return FALSE;
53
0
  return memcmp(buf + offset, magic, magic_size) == 0;
54
0
}
55
56
/**
57
 * xb_content_type_guess: (skip)
58
 * @filename: (nullable): filename
59
 * @buf: (nullable): file data buffer
60
 * @bufsz: size of file data buffer
61
 *
62
 * Guesses the content type based on example data. Either @filename or @buf may
63
 * be %NULL, in which case the guess will be based solely on the other argument.
64
 *
65
 * Returns: a string indicating a guessed content type
66
 **/
67
gchar *
68
xb_content_type_guess(const gchar *filename, const guchar *buf, gsize bufsz)
69
0
{
70
0
  g_autofree gchar *content_type = NULL;
71
72
  /* check for bad results, e.g. from Chrome OS */
73
0
  content_type = g_content_type_guess(filename, buf, bufsz, NULL);
74
0
  if (g_strstr_len(content_type, -1, "/") == NULL ||
75
0
      g_strcmp0(content_type, "application/octet-stream") == 0 ||
76
0
      g_strcmp0(content_type, "text/plain") == 0) {
77
    /* magic */
78
0
    if (bufsz > 0) {
79
0
      if (xb_content_type_match(buf, bufsz, 0x0, "\x1f\x8b", 2))
80
0
        return g_strdup("application/gzip");
81
0
      if (xb_content_type_match(buf, bufsz, 0x0, "\xfd\x37\x7a\x58\x5a\x00", 6))
82
0
        return g_strdup("application/x-xz");
83
0
      if (xb_content_type_match(buf, bufsz, 0x0, "\x28\xb5\x2f\xfd", 4))
84
0
        return g_strdup("application/zstd");
85
0
      if (xb_content_type_match(buf, bufsz, 0x0, "<?xml", 5))
86
0
        return g_strdup("application/xml");
87
0
      if (xb_content_type_match(buf, bufsz, 0x0, "[Desktop Entry]", 15))
88
0
        return g_strdup("application/x-desktop");
89
0
    }
90
91
    /* file extensions */
92
0
    if (filename != NULL) {
93
0
      const gchar *tmp = xb_content_type_guess_from_fn(filename);
94
0
      if (tmp != NULL)
95
0
        return g_strdup(tmp);
96
0
    }
97
0
  }
98
99
#ifdef _WIN32
100
  /* fall back harder as there is no mime data at all */
101
  if (filename != NULL) {
102
    const gchar *tmp = xb_content_type_guess_from_fn(filename);
103
    if (tmp != NULL)
104
      return g_strdup(tmp);
105
  }
106
#endif
107
108
0
  return g_steal_pointer(&content_type);
109
0
}
110
111
/**
112
 * xb_file_set_contents: (skip)
113
 * @file: (nullable): file to write
114
 * @buf: (nullable): data buffer
115
 * @bufsz: size of @buf
116
 * @cancellable: (nullable): optional #GCancellable
117
 * @error: (nullable): optional return location for an error
118
 *
119
 * Writes data to a file.
120
 *
121
 * Returns: %TRUE for success
122
 **/
123
gboolean
124
xb_file_set_contents(GFile *file,
125
         const guint8 *buf,
126
         gsize bufsz,
127
         GCancellable *cancellable,
128
         GError **error)
129
0
{
130
0
  g_return_val_if_fail(G_IS_FILE(file), FALSE);
131
0
  g_return_val_if_fail(buf != NULL, FALSE);
132
0
  g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), FALSE);
133
0
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
134
0
  return g_file_replace_contents(file,
135
0
               (const gchar *)buf,
136
0
               (gsize)bufsz,
137
0
               NULL,
138
0
               FALSE,
139
0
               G_FILE_CREATE_NONE,
140
0
               NULL,
141
0
               cancellable,
142
0
               error);
143
0
}