Publi

Conocer el tipo MIME de un archivo gracias a GIO en lenguaje C

3210986710_d3cbbdac55_b

La biblioteca GIO nos proporciona una capa más sobre el manejo de archivos. Nos aíslan un poco de las llamadas al sistema operativo para hacer muchas operaciones sobre archivos facilitando el uso de la biblioteca en aplicaciones multiplataforma. Así como dándonos algunas funciones interesantes que ya vienen hechas.

La gran ventaja de GIO frente al acceso normal a un archivo es que hace transparente al programador el acceso a sistemas de archivos compartidos (en Windows, Mac o Linux) sin tener que gastar mucho tiempo en todo eso.

Saber el tipo MIME de un archivo

Si tu aplicación trabaja con archivos, en muchas ocasiones es necesario conocer el tipo MIME, sobre todo cuando vamos a transferir datos a través de la red. Nos ayudan a conocer el tipo de contenido de un archivo (saber si es una imagen, un xml, un archivo comprimido, etc), mucho más allá de la extensión del mismo. Es más, podemos utilizar esta técnica para saber de verdad qué contiene un archivo porque, en muchas ocasiones, no podemos confiar en que el usuario final haya puesto bien la extensión.

El uso más común que se me ocurre para esto es cuando se sube una foto de perfil a una web. Muchos usuarios suben un jpg, otros un png, algunos un gif, bueno, hasta ahora bien, pero encontramos un primer problema, las mayúsculas, lo podemos salvar, lo pasamos todo a minúscula y comparamos, ahora otro problema, algunos jpg, podrán tener la extensión jpeg, aunque también lo podemos salvar. El problema complicado viene cuando un usuario tiene un png y lo renombra a jpg, o incluso tiene un bmp que lo renombra a gif, y claro, el renombrar los archivos no implica que se conviertan los datos que tiene dentro. Deberíamos tener una forma de poder analizar el archivo por dentro y en función de eso determinar de qué tipo es.

Tipo MIME asociado a partir del nombre de archivo

En principio, vamos a intentarlo por el nombre de archivo. Normalmente, para esto, necesitaríamos una base de datos con asociaciones entre nombre de archivo y tipo MIME, pero GIO ya hace eso por nosotros, podemos hacerlo de la siguiente forma:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <gio/gio.h>

int main (int argc, char *argv[])
{
  if (argc<1)
    {
      /* Caso de error */
      fprintf (stderr, "No se ha especificado el archivo\n");
      fprintf (stderr, " Uso : %s nombre_de_archivo\n", argv[0]);
      exit(1);
    }

  const char *file_name = argv[1];
  gboolean result_uncertain = TRUE;

  char *content_type = g_content_type_guess (file_name, NULL, 0, &result_uncertain);

  if (content_type != NULL)
    {
      char *mime_type = g_content_type_get_mime_type (content_type);
      if (mime_type)
    printf ("MIME Type: '%s'\n", mime_type);
      else
    printf ("MIME Type no disponible\n");

      printf ("Content Type: '%s' (en Linux coincide con el MIME Type)\n", content_type);

      if (mime_type)
    g_free (mime_type);
    }
  else
    fprintf (stderr, "No se pudo obtener el tipo de contenido\n");
  g_free (content_type);

  return EXIT_SUCCESS;
}

Para compilar:

$ gcc -o mime mime.c $(pkg-config –libs –cflags gio-2.0)

La clave está en la función g_content_type_guess() a la que le pasamos:

  1. El nombre de archivo (o NULL si no queremos basarnos en el nombre)
  2. Cotenido del archivo (o NULL si no queremos analizar el contenido)
  3. Bytes del contenido (o 0 si no queremos analizar el contenido)
  4. Un boolean por referencia donde dirá si el resultado es incierto o no. Es decir, cómo de seguros podemos estar con el dato. Por ejemplo, un archivo sin extensión, a priori no podemos saber qué es, por lo que nos devolverá un tipo genérico y nos dirá que result_uncertain es TRUE.

La función nos devolverá una cadena de caracteres con el tipo de contenido del archivo. Este tipo de contenido variará entre diferentes sistemas operativos, ya que cada uno llama a los archivos de una forma distinta, por ejemplo en Linux este content_type será igual que el tipo MIME (no sé si habrá alguna excepción), en Windows se usan nombres de aplicación o extensiones.

El caso es que, si content_type ha devuelto una cadena (vamos, que no es NULL), preguntamos el tipo MIME con g_content_type_get_mime_type() que nos devolverá directamente el valor que queremos.

Analizando el contenido del archivo

Vamos a completar un poco más el ejemplo anterior. Por un lado, vamos a devolver más datos acerca del tipo de archivo. Por ejemplo, una descripción, decir si el archivo es ejecutable o no, comprobar si el archivo existe (tal y como está el ejemplo anterior, como sólo mira el nombre del archivo, si éste no existe, no pasa nada, nos devuelve un tipo.

Para analizar el contenido, lo que vamos a hacer es leer el archivo, almacenarlo en una variable, y pasárselo a g_content_type_guess(). En el ejemplo he utilizado g_file_load_contents() para cargar los contenidos del archivo. Aunque si prefieres ser más clásico puedes hacer:

1
2
3
4
5
6
7
8
9
10
11
int readData(const char* file_name, char* buffer, unsigned bufferSize)
{
  FILE* fd = fopen(file_name, "r");
  if (!fd)
    return -1;

  int nread = fread(buffer, 1, bufferSize, fd);
  fclose(fd);

  return nread;
}

De esta forma, podemos ver que no es necesario leer el archivo completo para averiguar el MIME type, es más, si visualizáis el contenido bruto de un archivo jpeg, png, un ejecutable, un vídeo, etc, veréis cómo se identifican fácilmente con los primeros bytes (para una parte legible que tienen…)

Allá va el ejemplo completo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <gio/gio.h>

int main (int argc, char *argv[])
{
  if (argc<1)
    {
      /* Caso de error */
      fprintf (stderr, "No se ha especificado el archivo\n");
      fprintf (stderr, " Uso : %s nombre_de_archivo\n", argv[0]);
      exit(1);
    }

  const char *file_name = argv[1];
  gboolean result_uncertain = TRUE;
  char* buffer;
  gsize bufferSize;
  if (!g_file_load_contents(g_file_new_for_path(file_name), NULL, &buffer, &bufferSize, NULL, NULL))
    {
      fprintf(stderr, "Hubo un problema leyendo el archivo.\n");
      exit(2);
    }

  char *content_type = g_content_type_guess (NULL, buffer, bufferSize, &result_uncertain);
  g_free(buffer);

  if (content_type != NULL)
    {
      char *mime_type = g_content_type_get_mime_type (content_type);
      char *description = g_content_type_get_description(content_type);
      printf ("Archivo: '%s'\n", file_name);

      if (g_content_type_is_unknown(content_type))
    fprintf (stderr, "El tipo de archivo es desconocido\n");
      else
    {
      if (mime_type)
        printf ("MIME Type: '%s'\n", mime_type);
      else
        printf ("MIME Type no disponible\n");

      printf ("Content Type: '%s' (en Linux coincide con el MIME Type)\n", content_type);

      if (description)
        printf ("Descripcion: %s\n", description);
      else
        printf ("Descripcion no disponible\n");

      printf ("¿Resultado certero? %s\n", (result_uncertain)?"no":"si");

      printf ("¿Se puede ejecutar? %s\n", (g_content_type_can_be_executable(content_type))?"si":"no");
    }
      if (description)
    g_free (description);
      if (mime_type)
    g_free (mime_type);
    }
  else
    fprintf (stderr, "No se pudo obtener el tipo de contenido\n");
  g_free (content_type);

  return EXIT_SUCCESS;
}

Vemos, en la llamada a g_content_type_guess() que he eliminado el nombre del archivo, no es que no podamos ponerlo, pero quiero darle preferencia al contenido del mismo ya que, en el nombre del archivo, si el tipo es evidente dada su extensión, no analizará el contenido.

Podemos ver este pequeño ejemplo para que veáis que el programa no se deja timar:
Screenshot 12-02-2016-020216

Una cosa más

Estamos con una biblioteca parte de glib, por lo que siempre que reservemos algo, debemos utilizar g_free() para liberar. Y tenemos funciones que empiezan por “g_” o tipos como gboolean o gsize.
¡ Y otra más ! Podemos encontrar GIO disponible para otros lenguajes como C++, Python, Ruby, Haskell y más.

Foto: Jan Lewandowski

También podría interesarte....

Leave a Reply