summaryrefslogtreecommitdiffstats
path: root/src/mime.c
blob: 599128105956f33bf293fe4cfdec355cf029f98c (plain) (blame)
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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
#include <assert.h>
#include <errno.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "mime.h"

struct mime_info {
	char *mimetype;
	char *extension;
};

size_t mimedb_ln;
struct mime_info *mimedb;

static int
mimedb_compar(const void *va, const void *vb)
{
	const struct mime_info *a = va;
	const struct mime_info *b = vb;
	return strcmp(a->extension, b->extension);
}

void
mime_init()
{
	size_t mimedb_sz = 4096;
	mimedb = malloc(mimedb_sz * sizeof(struct mime_info));

	FILE *f = fopen(MIMEDB, "r");
	if (!f) {
		fprintf(stderr, "Unable to open MIME database for reading: %s\n",
			strerror(errno));
		fprintf(stderr, "Is " MIMEDB " installed?\n");
		assert(0);
	}

	char *line = NULL;
	size_t n = 0;
	while (getline(&line, &n, f) != -1) {
		if (line[0] == '#') {
			continue;
		}
		size_t l = strlen(line);
		if (l > 1 && line[l - 1] == '\n') {
			line[l - 1] = '\0';
		}

		if (mimedb_ln >= mimedb_sz) {
			mimedb_sz *= 2;
			struct mime_info *new = realloc(mimedb,
					mimedb_sz * sizeof(struct mime_info));
			assert(new);
			mimedb = new;
		}

		char *mime = strdup(strtok(line, " \t"));
		char *ext = strtok(NULL, " \t");
		if (!ext) {
			free(mime);
			continue;
		}

		do {
			mimedb[mimedb_ln].mimetype = strdup(mime);
			mimedb[mimedb_ln].extension = strdup(ext);
			++mimedb_ln;
		} while ((ext = strtok(NULL, " \t")));

		free(mime);
	}
	free(line);
	fclose(f);

	qsort(mimedb, mimedb_ln, sizeof(mimedb[0]), mimedb_compar);
}

void
mime_finish()
{
	for (size_t i = 0; i < mimedb_ln; ++i) {
		free(mimedb[i].mimetype);
		free(mimedb[i].extension);
	}
	free(mimedb);
}

static bool
has_suffix(const char *str, const char *suff)
{
	size_t a = strlen(str), b = strlen(suff);
	if (a < b) {
		return false;
	}
	return strncmp(&str[a - b], suff, b) == 0;
}

static int
path_compar(const void *va, const void *vb)
{
	char *ext = (char *)va;
	struct mime_info *mime = (struct mime_info *)vb;
	return strcmp(ext, mime->extension);
}

const char *
mimetype_for_path(const char *path)
{
	if (has_suffix(path, ".gmi") || has_suffix(path, ".gemini")) {
		return "text/gemini";
	}

	char *ext = strrchr(path, '.');
	if (!ext || !ext[1]) {
		return "application/octet-stream";
	}
	++ext;

	struct mime_info *mime = bsearch(
		ext, mimedb, mimedb_ln, sizeof(mimedb[0]), path_compar);
	if (!mime) {
		return "application/octet-stream";
	}
	return mime->mimetype;
}