/* mime_type.c -- routine to determine the MIME Content-Type of a file * * This code is Copyright (c) 2014, by the authors of nmh. See the * COPYRIGHT file in the root directory of the nmh distribution for * complete copyright information. */ #include #include #include #include "mime_type.h" #ifdef MIMETYPEPROC static char *get_file_info(const char *, const char *); #endif /* MIMETYPEPROC */ /* * Try to use external command to determine mime type, and possibly * encoding. If that fails try using the filename extension. Caller * is responsible for free'ing returned memory. */ char * mime_type(const char *file_name) { char *content_type = NULL; /* mime content type */ char *p; static int loaded_defaults = 0; #ifdef MIMETYPEPROC char *mimetype; if ((mimetype = get_file_info(MIMETYPEPROC, file_name))) { #ifdef MIMEENCODINGPROC /* Try to append charset for text content. */ char *mimeencoding; if (strncasecmp(mimetype, "text", 4) == 0) { if ((mimeencoding = get_file_info(MIMEENCODINGPROC, file_name))) { content_type = concat(mimetype, "; charset=", mimeencoding, NULL); free (mimetype); } else { content_type = mimetype; } } else { content_type = mimetype; } #else /* MIMEENCODINGPROC */ content_type = mimetype; #endif /* MIMEENCODINGPROC */ } #endif /* MIMETYPEPROC */ /* * If we didn't get the MIME type from the contents (or we don't support * the necessary command) then use the mhshow suffix. */ if (content_type == NULL) { struct node *np; /* Content scan node pointer */ FILE *fp; /* File pointer for mhn.defaults */ if (! loaded_defaults && (fp = fopen(p = etcpath("mhn.defaults"), "r"))) { loaded_defaults = 1; readconfig(NULL, fp, p, 0); fclose(fp); } if ((p = strrchr(file_name, '.')) != NULL) { for (np = m_defs; np; np = np->n_next) { if (strncasecmp(np->n_name, "mhshow-suffix-", 14) == 0 && strcasecmp(p, FENDNULL(np->n_field)) == 0) { content_type = strdup(np->n_name + 14); break; } } } /* * If we didn't match any filename extension, try to infer the * content type. If we have binary, assume application/octet-stream; * otherwise, assume text/plain. */ if (content_type == NULL) { FILE *fp; int binary = 0, c; if (!(fp = fopen(file_name, "r"))) { inform("unable to access file \"%s\"", file_name); return NULL; } while ((c = getc(fp)) != EOF) { if (! isascii(c) || c == 0) { binary = 1; break; } } fclose(fp); content_type = strdup(binary ? "application/octet-stream" : "text/plain"); } } return content_type; } #ifdef MIMETYPEPROC /* * Get information using proc about a file. */ static char * get_file_info(const char *proc, const char *file_name) { char *cmd, *cp; char *quotec = "'"; if ((cp = strchr(file_name, '\''))) { /* file_name contains a single quote. */ if (strchr(file_name, '"')) { inform("filenames containing both single and double quotes " "are unsupported for attachment"); return NULL; } quotec = "\""; } if ((cmd = concat(proc, " ", quotec, file_name, quotec, NULL))) { FILE *fp; if ((fp = popen(cmd, "r")) != NULL) { char buf[max(BUFSIZ, 2048)]; buf[0] = '\0'; if (fgets(buf, sizeof buf, fp)) { char *eol; /* Skip leading :, if present. */ if ((cp = strchr(buf, ':')) != NULL) { ++cp; while (*cp && isblank((unsigned char) *cp)) { ++cp; } } else { cp = buf; } /* Truncate at newline (LF or CR), if present. */ if ((eol = strpbrk(cp, "\n\r")) != NULL) { *eol = '\0'; } } else if (buf[0] == '\0') { /* This can happen on Cygwin if the popen() mysteriously fails. Return NULL so that the caller will use another method to determine the info. */ free (cp); cp = NULL; } (void) pclose(fp); } else { inform("no output from %s", cmd); } free(cmd); } else { inform("concat with \"%s\" failed, out of memory?", proc); } return cp ? strdup(cp) : NULL; } #endif /* MIMETYPEPROC */