content-type.c 5.21 KB
Newer Older
1
/* SPDX-License-Identifier: Zlib */
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

#include "content-type.h"
#include "macros.h"

#include <girara/utils.h>
#ifdef WITH_MAGIC
#include <magic.h>
#else
#include <sys/types.h>
#include <sys/wait.h>
#endif
#include <stdio.h>
#include <glib.h>
#include <gio/gio.h>

17 18
struct zathura_content_type_context_s
{
19
#ifdef WITH_MAGIC
20 21 22 23 24 25 26 27 28 29 30 31
  magic_t magic;
#endif
};

zathura_content_type_context_t*
zathura_content_type_new(void)
{
  zathura_content_type_context_t* context =
    g_try_malloc0(sizeof(zathura_content_type_context_t));
  if (context == NULL) {
    return NULL;
  }
32

33
#ifdef WITH_MAGIC
34
  /* creat magic cookie */
Sebastian Ramacher's avatar
Sebastian Ramacher committed
35
  static const int flags =
36 37 38 39 40 41 42 43 44
    MAGIC_MIME_TYPE |
    MAGIC_SYMLINK |
    MAGIC_NO_CHECK_APPTYPE |
    MAGIC_NO_CHECK_CDF |
    MAGIC_NO_CHECK_ELF |
    MAGIC_NO_CHECK_ENCODING;
  magic_t magic = magic_open(flags);
  if (magic == NULL) {
    girara_debug("failed creating the magic cookie");
45
    return context;
46 47 48 49 50
  }

  /* ... and load mime database */
  if (magic_load(magic, NULL) < 0) {
    girara_debug("failed loading the magic database: %s", magic_error(magic));
51 52
    magic_close(magic);
    return context;
53 54
  }

55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
  context->magic = magic;
#endif

  return context;
}

void
zathura_content_type_free(zathura_content_type_context_t* context)
{
  if (context == NULL) {
    return;
  }

#ifdef WITH_MAGIC
  if (context->magic != NULL) {
    magic_close(context->magic);
  }
#endif

  g_free(context);
}


/** Read a most GT_MAX_READ bytes before falling back to file. */
static const size_t GT_MAX_READ = 1 << 16;

#ifdef WITH_MAGIC
Sebastian Ramacher's avatar
Sebastian Ramacher committed
82
static char*
83 84 85 86 87 88 89 90
guess_type_magic(zathura_content_type_context_t* context, const char* path)
{
  if (context == NULL || context->magic == NULL) {
    return NULL;
  }

  const char* mime_type = NULL;

91
  /* get the mime type */
92
  mime_type = magic_file(context->magic, path);
93
  if (mime_type == NULL) {
94 95
    girara_debug("failed guessing filetype: %s", magic_error(context->magic));
    return NULL;
96 97 98
  }
  girara_debug("magic detected filetype: %s", mime_type);

99 100 101 102 103 104 105 106
  char* content_type = g_content_type_from_mime_type(mime_type);
  if (content_type == NULL) {
    girara_warning("failed to convert mime type to content type: %s", mime_type);
    /* dup so we own the memory */
    return g_strdup(mime_type);
  }

  return content_type;
107 108
}

Sebastian Ramacher's avatar
Sebastian Ramacher committed
109
static char*
110 111 112 113 114
guess_type_file(const char* UNUSED(path))
{
  return NULL;
}
#else
Sebastian Ramacher's avatar
Sebastian Ramacher committed
115
static char*
116 117 118
guess_type_magic(zathura_content_type_context_t* UNUSED(context),
                 const char* UNUSED(path))
{
119 120 121
  return NULL;
}

Sebastian Ramacher's avatar
Sebastian Ramacher committed
122
static char*
123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
guess_type_file(const char* path)
{
  GString* command = g_string_new("file -b --mime-type ");
  char* tmp        = g_shell_quote(path);

  g_string_append(command, tmp);
  g_free(tmp);

  GError* error = NULL;
  char* out = NULL;
  int ret = 0;
  g_spawn_command_line_sync(command->str, &out, NULL, &ret, &error);
  g_string_free(command, TRUE);
  if (error != NULL) {
    girara_warning("failed to execute command: %s", error->message);
    g_error_free(error);
    g_free(out);
    return NULL;
  }
  if (WEXITSTATUS(ret) != 0) {
    girara_warning("file failed with error code: %d", WEXITSTATUS(ret));
    g_free(out);
    return NULL;
  }

  g_strdelimit(out, "\n\r", '\0');
149 150 151 152 153 154 155 156 157 158
  girara_debug("file detected filetype: %s", out);

  char* content_type = g_content_type_from_mime_type(out);
  if (content_type == NULL) {
    girara_warning("failed to convert mime type to content type: %s", out);
    return out;
  }

  g_free(out);
  return content_type;
159 160 161
}
#endif

Sebastian Ramacher's avatar
Sebastian Ramacher committed
162
static char*
163 164 165
guess_type_glib(const char* path)
{
  gboolean uncertain = FALSE;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
166
  char* content_type = g_content_type_guess(path, NULL, 0, &uncertain);
167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189
  if (content_type == NULL) {
    girara_debug("g_content_type failed\n");
  } else {
    if (uncertain == FALSE) {
      girara_debug("g_content_type detected filetype: %s", content_type);
      return content_type;
    }
    girara_debug("g_content_type is uncertain, guess: %s", content_type);
  }

  FILE* f = fopen(path, "rb");
  if (f == NULL) {
    return NULL;
  }

  const int fd = fileno(f);
  guchar* content = NULL;
  size_t length = 0u;
  ssize_t bytes_read = -1;
  while (uncertain == TRUE && length < GT_MAX_READ && bytes_read != 0) {
    g_free((void*)content_type);
    content_type = NULL;

190 191 192
    guchar* temp_content = g_try_realloc(content, length + BUFSIZ);
    if (temp_content == NULL) {
      break;
193
    }
194
    content = temp_content;
195

196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215
    bytes_read = read(fd, content + length, BUFSIZ);
    if (bytes_read == -1) {
      break;
    }

    length += bytes_read;
    content_type = g_content_type_guess(NULL, content, length, &uncertain);
    girara_debug("new guess: %s uncertain: %d, read: %zu", content_type, uncertain, length);
  }

  fclose(f);
  g_free(content);
  if (uncertain == FALSE) {
    return content_type;
  }

  g_free((void*)content_type);
  return NULL;
}

Sebastian Ramacher's avatar
Sebastian Ramacher committed
216
char*
217 218
zathura_content_type_guess(zathura_content_type_context_t* context,
                           const char* path)
219 220
{
  /* try libmagic first */
Sebastian Ramacher's avatar
Sebastian Ramacher committed
221
  char* content_type = guess_type_magic(context, path);
222 223 224 225 226 227 228 229 230 231 232
  if (content_type != NULL) {
    return content_type;
  }
  /* else fallback to g_content_type_guess method */
  content_type = guess_type_glib(path);
  if (content_type != NULL) {
    return content_type;
  }
  /* and if libmagic is not available, try file as last resort */
  return guess_type_file(path);
}