]> diplodocus.org Git - nmh/blob - sbr/mime_type.c
uip/flist.c: Make locally defined and used functions static.
[nmh] / sbr / mime_type.c
1 /* mime_type.c -- routine to determine the MIME Content-Type of a file
2 *
3 * This code is Copyright (c) 2014, by the authors of nmh. See the
4 * COPYRIGHT file in the root directory of the nmh distribution for
5 * complete copyright information.
6 */
7
8 #include <h/mh.h>
9 #include <h/utils.h>
10 #include <h/tws.h>
11
12 #ifdef MIMETYPEPROC
13 static char *get_file_info(const char *, const char *);
14 #endif /* MIMETYPEPROC */
15
16 /*
17 * Try to use external command to determine mime type, and possibly
18 * encoding. If that fails try using the filename extension. Caller
19 * is responsible for free'ing returned memory.
20 */
21 char *
22 mime_type(const char *file_name) {
23 char *content_type = NULL; /* mime content type */
24 char *p;
25 static int loaded_defaults = 0;
26
27 #ifdef MIMETYPEPROC
28 char *mimetype;
29
30 if ((mimetype = get_file_info(MIMETYPEPROC, file_name))) {
31 #ifdef MIMEENCODINGPROC
32 /* Try to append charset for text content. */
33 char *mimeencoding;
34
35 if (strncasecmp(mimetype, "text", 4) == 0) {
36 if ((mimeencoding = get_file_info(MIMEENCODINGPROC, file_name))) {
37 content_type = concat(mimetype, "; charset=", mimeencoding,
38 NULL);
39 free (mimetype);
40 } else {
41 content_type = mimetype;
42 }
43 } else {
44 content_type = mimetype;
45 }
46 #else /* MIMEENCODINGPROC */
47 content_type = mimetype;
48 #endif /* MIMEENCODINGPROC */
49 }
50 #endif /* MIMETYPEPROC */
51
52 /*
53 * If we didn't get the MIME type from the contents (or we don't support
54 * the necessary command) then use the mhshow suffix.
55 */
56
57 if (content_type == NULL) {
58 struct node *np; /* Content scan node pointer */
59 FILE *fp; /* File pointer for mhn.defaults */
60
61 if (! loaded_defaults &&
62 (fp = fopen(p = etcpath("mhn.defaults"), "r"))) {
63 loaded_defaults = 1;
64 readconfig(NULL, fp, p, 0);
65 fclose(fp);
66 }
67
68 if ((p = strrchr(file_name, '.')) != NULL) {
69 for (np = m_defs; np; np = np->n_next) {
70 if (strncasecmp(np->n_name, "mhshow-suffix-", 14) == 0 &&
71 strcasecmp(p, FENDNULL(np->n_field)) == 0) {
72 content_type = strdup(np->n_name + 14);
73 break;
74 }
75 }
76 }
77
78 /*
79 * If we didn't match any filename extension, try to infer the
80 * content type. If we have binary, assume application/octet-stream;
81 * otherwise, assume text/plain.
82 */
83
84 if (content_type == NULL) {
85 FILE *fp;
86 int binary = 0, c;
87
88 if (!(fp = fopen(file_name, "r"))) {
89 inform("unable to access file \"%s\"", file_name);
90 return NULL;
91 }
92
93 while ((c = getc(fp)) != EOF) {
94 if (! isascii(c) || c == 0) {
95 binary = 1;
96 break;
97 }
98 }
99
100 fclose(fp);
101
102 content_type =
103 strdup(binary ? "application/octet-stream" : "text/plain");
104 }
105 }
106
107 return content_type;
108 }
109
110
111 #ifdef MIMETYPEPROC
112 /*
113 * Get information using proc about a file.
114 */
115 static char *
116 get_file_info(const char *proc, const char *file_name) {
117 char *cmd, *cp;
118 char *quotec = "'";
119
120 if ((cp = strchr(file_name, '\''))) {
121 /* file_name contains a single quote. */
122 if (strchr(file_name, '"')) {
123 inform("filenames containing both single and double quotes "
124 "are unsupported for attachment");
125 return NULL;
126 }
127 quotec = "\"";
128 }
129
130 if ((cmd = concat(proc, " ", quotec, file_name, quotec, NULL))) {
131 FILE *fp;
132
133 if ((fp = popen(cmd, "r")) != NULL) {
134 char buf[max(BUFSIZ, 2048)];
135
136 buf[0] = '\0';
137 if (fgets(buf, sizeof buf, fp)) {
138 char *eol;
139
140 /* Skip leading <filename>:<whitespace>, if present. */
141 if ((cp = strchr(buf, ':')) != NULL) {
142 ++cp;
143 while (*cp && isblank((unsigned char) *cp)) {
144 ++cp;
145 }
146 } else {
147 cp = buf;
148 }
149
150 /* Truncate at newline (LF or CR), if present. */
151 if ((eol = strpbrk(cp, "\n\r")) != NULL) {
152 *eol = '\0';
153 }
154 } else if (buf[0] == '\0') {
155 /* This can happen on Cygwin if the popen()
156 mysteriously fails. Return NULL so that the caller
157 will use another method to determine the info. */
158 free (cp);
159 cp = NULL;
160 }
161
162 (void) pclose(fp);
163 } else {
164 inform("no output from %s", cmd);
165 }
166
167 free(cmd);
168 } else {
169 inform("concat with \"%s\" failed, out of memory?", proc);
170 }
171
172 return cp ? strdup(cp) : NULL;
173 }
174 #endif /* MIMETYPEPROC */