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