1 #if !defined(lint) && !defined(DOS)
2 static char rcsid[] = "$Id: mimetype.c 955 2008-03-06 23:52:36Z hubert@u.washington.edu $";
3 #endif
4
5 /*
6 * ========================================================================
7 * Copyright 2006-2008 University of Washington
8 * Copyright 2013-2021 Eduardo Chappa
9 *
10 * Licensed under the Apache License, Version 2.0 (the "License");
11 * you may not use this file except in compliance with the License.
12 * You may obtain a copy of the License at
13 *
14 * http://www.apache.org/licenses/LICENSE-2.0
15 *
16 * ========================================================================
17 */
18
19 #include "../pith/headers.h"
20 #include "../pith/mimetype.h"
21 #include "../pith/mimedesc.h"
22 #include "../pith/state.h"
23 #include "../pith/conf.h"
24 #include "../pith/mailcap.h"
25 #include "../pith/util.h"
26
27 /*
28 * We've decided not to implement the RFC1524 standard minimum path, because
29 * some of us think it is harder to debug a problem when you may be misled
30 * into looking at the wrong mailcap entry. Likewise for MIME.Types files.
31 */
32 #if defined(DOS) || defined(OS2)
33 #define MT_PATH_SEPARATOR ';'
34 #define MT_USER_FILE "MIMETYPE"
35 #define MT_STDPATH NULL
36 #else /* !DOS */
37 #define MT_PATH_SEPARATOR ':'
38 #define MT_USER_FILE NULL
39 #define MT_STDPATH \
40 ".mime.types:/etc/mime.types:/usr/local/lib/mime.types"
41 #endif /* !DOS */
42
43 #define LINE_BUF_SIZE 2000
44
45
46 /*
47 * Types used to pass parameters and operator functions to the
48 * mime.types searching routines.
49 */
50 #define MT_MAX_FILE_EXTENSION 3
51
52
53 /*
54 * Internal prototypes
55 */
56 int mt_browse_types_file(MT_OPERATORPROC, MT_MAP_T *, char *);
57 int mt_srch_by_type(MT_MAP_T *, FILE *);
58
59
60
61 /*
62 * Exported function that does the work of sniffing the mime.types
63 * files and filling in the body pointer if found. Returns 1 (TRUE) if
64 * extension found, and body pointer filled in, 0 (FALSE) otherwise.
65 */
66 int
set_mime_type_by_extension(struct mail_bodystruct * body,char * filename)67 set_mime_type_by_extension(struct mail_bodystruct *body, char *filename)
68 {
69 MT_MAP_T e2b;
70
71 if(mt_get_file_ext(filename, &e2b.from.ext)
72 && mt_srch_mime_type(mt_srch_by_ext, &e2b)){
73 body->type = e2b.to.mime.type;
74 body->subtype = e2b.to.mime.subtype; /* NOTE: subtype was malloc'd */
75 return(1);
76 }
77
78 return(0);
79 }
80
81
82 /*
83 * Exported function that maps from mime types to file extensions.
84 */
85 int
set_mime_extension_by_type(char * ext,char * mtype)86 set_mime_extension_by_type (char *ext, char *mtype)
87 {
88 MT_MAP_T t2e;
89
90 t2e.from.mime_type = mtype;
91 t2e.to.ext = ext;
92 return (mt_srch_mime_type (mt_srch_by_type, &t2e));
93 }
94
95
96 int
check_mime_type_by_extension(char * ext,char * mtype)97 check_mime_type_by_extension (char *ext, char *mtype)
98 {
99 MT_MAP_T e2t;
100 char mimet[128];
101
102 if((e2t.from.ext = ext) != NULL && *e2t.from.ext
103 && mt_srch_mime_type(mt_srch_by_ext, &e2t)){
104 snprintf(mimet, sizeof(mimet), "%s/%s",
105 body_type_names(e2t.to.mime.type), e2t.to.mime.subtype);
106 mimet[sizeof(mimet) - 1] = '\0';
107 fs_give((void **)& e2t.to.mime.subtype);
108 return strucmp(mimet, mtype) ? 0 : 1;
109 }
110
111 return(0);
112 }
113
114
115 /*
116 * Separate and return a pointer to the first character in the 'filename'
117 * character buffer that comes after the rightmost '.' character in the
118 * filename. (What I mean is a pointer to the filename - extension).
119 *
120 * Returns 1 if an extension is found, 0 otherwise.
121 */
122 int
mt_get_file_ext(char * filename,char ** extension)123 mt_get_file_ext(char *filename, char **extension)
124 {
125 dprint((5, "mt_get_file_ext : filename=\"%s\", ",
126 filename ? filename : "?"));
127
128 for(*extension = NULL; filename && *filename; filename++)
129 if(*filename == '.')
130 *extension = filename + 1;
131
132 dprint((5, "extension=\"%s\"\n",
133 (extension && *extension) ? *extension : "?"));
134
135 return(*extension ? 1 : 0);
136 }
137
138
139 /*
140 * Build a list of possible mime.type files. For each one that exists
141 * call the mt_operator function.
142 * Loop terminates when mt_operator returns non-zero.
143 */
144 int
mt_srch_mime_type(MT_OPERATORPROC mt_operator,MT_MAP_T * mt_map)145 mt_srch_mime_type(MT_OPERATORPROC mt_operator, MT_MAP_T *mt_map)
146 {
147 char *s, *pathcopy, *path;
148 int rv = 0;
149
150 dprint((5, "- mt_srch_mime_type -\n"));
151
152 pathcopy = mc_conf_path(ps_global->VAR_MIMETYPE_PATH, getenv("MIMETYPES"),
153 MT_USER_FILE, MT_PATH_SEPARATOR, MT_STDPATH);
154
155 path = pathcopy; /* overloaded "path" */
156
157 dprint((7, "mime_types: path: %s\n", path ? path : "?"));
158 while(path){
159 if((s = strindex(path, MT_PATH_SEPARATOR)) != NULL)
160 *s++ = '\0';
161
162 if((rv = mt_browse_types_file(mt_operator, mt_map, path)) != 0)
163 break;
164
165 path = s;
166 }
167
168 if(pathcopy)
169 fs_give((void **)&pathcopy);
170
171 if(!rv && mime_os_specific_access()){
172 if(mt_operator == mt_srch_by_ext){
173 char buf[256];
174
175 buf[0] = '\0';
176 if(mime_get_os_mimetype_from_ext(mt_map->from.ext, buf, 256)){
177 if((s = strindex(buf, '/')) != NULL){
178 *s++ = '\0';
179 mt_map->to.mime.type = mt_translate_type(buf);
180 mt_map->to.mime.subtype = cpystr(s);
181 rv = 1;
182 }
183 }
184 }
185 else if(mt_operator == mt_srch_by_type){
186 if(mime_get_os_ext_from_mimetype(mt_map->from.mime_type,
187 mt_map->to.ext, 32)){
188 /* the 32 comes from var ext[] in display_attachment() */
189 if(*(s = mt_map->to.ext) == '.')
190 while((*s = *(s+1)) != '\0')
191 s++;
192
193 rv = 1;
194 }
195 }
196 else
197 alpine_panic("Unhandled mime type search");
198 }
199
200 /* if we still can not find the type, but it is a .docx (or alike) extension
201 set the type here. Do not use the grope function.
202 */
203 if(rv == 0){
204 rv = 1; /* assume success */
205 mt_map->to.mime.type = TYPEAPPLICATION;
206 if(!strucmp(mt_map->from.ext, "docx"))
207 mt_map->to.mime.subtype = cpystr("VND.OPENXMLFORMATS-OFFICEDOCUMENT.WORDPROCESSINGML.DOCUMENT");
208 else if(!strucmp(mt_map->from.ext, "xslx"))
209 mt_map->to.mime.subtype = cpystr("VND.OPENXMLFORMATS-OFFICEDOCUMENT.SPREADSHEETML.SHEET");
210 else if(!strucmp(mt_map->from.ext, "xltx"))
211 mt_map->to.mime.subtype = cpystr("VND.OPENXMLFORMATS-OFFICEDOCUMENT.SPREADSHEETML.TEMPLATE");
212 else if(!strucmp(mt_map->from.ext, "potx"))
213 mt_map->to.mime.subtype = cpystr("VND.OPENXMLFORMATS-OFFICEDOCUMENT.PRESENTATIONML.TEMPLATE");
214 else if(!strucmp(mt_map->from.ext, "ppsx"))
215 mt_map->to.mime.subtype = cpystr("VND.OPENXMLFORMATS-OFFICEDOCUMENT.PRESENTATIONML.SLIDESHOW");
216 else if(!strucmp(mt_map->from.ext, "pptx"))
217 mt_map->to.mime.subtype = cpystr("VND.OPENXMLFORMATS-OFFICEDOCUMENT.PRESENTATIONML.PRESENTATION");
218 else if(!strucmp(mt_map->from.ext, "sldx"))
219 mt_map->to.mime.subtype = cpystr("VND.OPENXMLFORMATS-OFFICEDOCUMENT.PRESENTATIONML.SLIDE");
220 else if(!strucmp(mt_map->from.ext, "dotx"))
221 mt_map->to.mime.subtype = cpystr("VND.OPENXMLFORMATS-OFFICEDOCUMENT.WORDPROCESSINGML.TEMPLATE");
222 else if(!strucmp(mt_map->from.ext, "xlam"))
223 mt_map->to.mime.subtype = cpystr("VND.MS-EXCEL.ADDIN.MACROENABLED.12");
224 else if(!strucmp(mt_map->from.ext, "xslb"))
225 mt_map->to.mime.subtype = cpystr("VND.MS-EXCEL.SHEET.BINARY.MACROENABLED.12");
226 else rv = 0; /* else, failure */
227 }
228
229
230 return(rv);
231 }
232
233
234 /*
235 * Try to match a file extension against extensions found in the file
236 * ``filename'' if that file exists. return 1 if a match
237 * was found and 0 in all other cases.
238 */
239 int
mt_browse_types_file(MT_OPERATORPROC mt_operator,MT_MAP_T * mt_map,char * filename)240 mt_browse_types_file(MT_OPERATORPROC mt_operator, MT_MAP_T *mt_map, char *filename)
241 {
242 int rv = 0;
243 FILE *file;
244
245 dprint((7, "mt_browse_types_file(%s)\n", filename ? filename : "?"));
246 if((file = our_fopen(filename, "rb")) != NULL){
247 rv = (*mt_operator)(mt_map, file);
248 fclose(file);
249 }
250 else{
251 dprint((1, "mt_browse: FAILED open(%s) : %s.\n",
252 filename ? filename : "?", error_description(errno)));
253 }
254
255 return(rv);
256 }
257
258
259 /*
260 * scan each line of the file. Treat each line as a mime type definition.
261 * The first word is a type/subtype specification. All following words
262 * are file extensions belonging to that type/subtype. Words are separated
263 * bij whitespace characters.
264 * If a file extension occurs more than once, then the first definition
265 * determines the file type and subtype.
266 */
267 int
mt_srch_by_ext(MT_MAP_T * e2b,FILE * file)268 mt_srch_by_ext(MT_MAP_T *e2b, FILE *file)
269 {
270 char buffer[LINE_BUF_SIZE];
271
272 /* construct a loop reading the file line by line. Then check each
273 * line for a matching definition.
274 */
275 while(fgets(buffer,LINE_BUF_SIZE,file) != NULL){
276 char *typespec;
277 char *try_extension;
278
279 if(buffer[0] == '#')
280 continue; /* comment */
281
282 /* divide the input buffer into words separated by whitespace.
283 * The first words is the type and subtype. All following words
284 * are file extensions.
285 */
286 dprint((5, "traverse: buffer=\"%s\"\n", buffer));
287 typespec = strtok(buffer," \t"); /* extract type,subtype */
288 if(!typespec)
289 continue;
290
291 dprint((5, "typespec=\"%s\"\n", typespec ? typespec : "?"));
292 while((try_extension = strtok(NULL, " \t\n\r")) != NULL){
293 /* compare the extensions, and assign the type if a match
294 * is found.
295 */
296 dprint((5,"traverse: trying ext \"%s\"\n",try_extension));
297 if(strucmp(try_extension, e2b->from.ext) == 0){
298 /* split the 'type/subtype' specification */
299 char *type, *subtype = NULL;
300
301 type = strtok(typespec,"/");
302 if(type)
303 subtype = strtok(NULL,"/");
304
305 dprint((5, "traverse: type=%s, subtype=%s.\n",
306 type ? type : "<null>",
307 subtype ? subtype : "<null>"));
308 /* The type is encoded as a small integer. we have to
309 * translate the character string naming the type into
310 * the corresponding number.
311 */
312 e2b->to.mime.type = mt_translate_type(type);
313 e2b->to.mime.subtype = cpystr(subtype ? subtype : "x-unknown");
314 return 1; /* a match has been found */
315 }
316 }
317 }
318
319 dprint((5, "traverse: search failed.\n"));
320 return 0;
321 }
322
323
324 /*
325 * scan each line of the file. Treat each line as a mime type definition.
326 * Here we are looking for a matching type. When that is found return the
327 * first extension that is three chars or less.
328 */
329 int
mt_srch_by_type(MT_MAP_T * t2e,FILE * file)330 mt_srch_by_type(MT_MAP_T *t2e, FILE *file)
331 {
332 char buffer[LINE_BUF_SIZE];
333
334 /* construct a loop reading the file line by line. Then check each
335 * line for a matching definition.
336 */
337 while(fgets(buffer,LINE_BUF_SIZE,file) != NULL){
338 char *typespec;
339 char *try_extension;
340
341 if(buffer[0] == '#')
342 continue; /* comment */
343
344 /* divide the input buffer into words separated by whitespace.
345 * The first words is the type and subtype. All following words
346 * are file extensions.
347 */
348 dprint((5, "traverse: buffer=%s.\n", buffer));
349 typespec = strtok(buffer," \t"); /* extract type,subtype */
350 dprint((5, "typespec=%s.\n", typespec ? typespec : "?"));
351 if (strucmp (typespec, t2e->from.mime_type) == 0) {
352 while((try_extension = strtok(NULL, " \t\n\r")) != NULL){
353 if (strlen (try_extension) <= MT_MAX_FILE_EXTENSION) {
354 strncpy (t2e->to.ext, try_extension, 32);
355 /*
356 * not sure of the 32, so don't write to byte 32
357 * on purpose with
358 * t2e->to.ext[31] = '\0';
359 * in case that breaks something
360 */
361
362 return (1);
363 }
364 }
365 }
366 }
367
368 dprint((5, "traverse: search failed.\n"));
369 return 0;
370 }
371
372
373 /*
374 * Translate a character string representing a content type into a short
375 * integer number, according to the coding described in c-client/mail.h
376 * List of content types taken from rfc1521, September 1993.
377 */
378 int
mt_translate_type(char * type)379 mt_translate_type(char *type)
380 {
381 int i;
382
383 for (i=0;(i<=TYPEMAX) && body_types[i] && strucmp(type,body_types[i]);i++)
384 ;
385
386 if (i > TYPEMAX)
387 i = TYPEOTHER;
388 else if (!body_types[i]) /* if empty slot, assign it to this type */
389 body_types[i] = cpystr (type);
390
391 return(i);
392 }
393