content-type.c 4.83 KB
Newer Older
1 2
/* See LICENSE file for license and copyright information */

3
#define _DEFAULT_SOURCE
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
#define _XOPEN_SOURCE 700

#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>

20 21
struct zathura_content_type_context_s
{
22
#ifdef WITH_MAGIC
23 24 25 26 27 28 29 30 31 32 33 34
  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;
  }
35

36
#ifdef WITH_MAGIC
37 38 39 40 41 42 43 44 45 46 47
  /* creat magic cookie */
  const int flags =
    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");
48
    return context;
49 50 51 52 53
  }

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

58 59 60 61 62 63 64 65 66 67 68
  context->magic = magic;
#endif

  return context;
}

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

71 72 73 74 75
#ifdef WITH_MAGIC
  if (context->magic != NULL) {
    magic_close(context->magic);
  }
#endif
76

77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
  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
static char*
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;

  /* get the mime type */
  mime_type = magic_file(context->magic, path);
  if (mime_type == NULL) {
    girara_debug("failed guessing filetype: %s", magic_error(context->magic));
    return NULL;
99
  }
100
  girara_debug("magic detected filetype: %s", mime_type);
101

102 103
  /* dup so we own the memory */
  return g_strdup(mime_type);;
104 105
}

106
static char*
107 108 109 110 111 112
guess_type_file(const char* UNUSED(path))
{
  return NULL;
}
#else
static const char*
113 114 115
guess_type_magic(zathura_content_type_context_t* UNUSED(context),
                 const char* UNUSED(path))
{
116 117 118
  return NULL;
}

119
static char*
120 121 122 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 149
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');
  return out;
}
#endif

150
static char*
151 152 153
guess_type_glib(const char* path)
{
  gboolean uncertain = FALSE;
154
  char* content_type = g_content_type_guess(path, NULL, 0, &uncertain);
155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203
  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;

    guchar* temp_content = g_try_realloc(content, length + BUFSIZ);
    if (temp_content == NULL) {
      break;
    }
    content = temp_content;

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

204 205 206
char*
zathura_content_type_guess(zathura_content_type_context_t* context,
                           const char* path)
207 208
{
  /* try libmagic first */
209
  char* content_type = guess_type_magic(context, path);
210 211 212 213 214 215 216 217 218 219 220
  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);
}