aboutsummaryrefslogtreecommitdiff
path: root/lib/mime.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/mime.c')
-rw-r--r--lib/mime.c156
1 files changed, 156 insertions, 0 deletions
diff --git a/lib/mime.c b/lib/mime.c
new file mode 100644
index 0000000..9e4bdce
--- /dev/null
+++ b/lib/mime.c
@@ -0,0 +1,156 @@
+/**
+ * @file mime.c
+ */
+#include "spm.h"
+#include <fnmatch.h>
+
+/**
+ * Execute OS `file` command
+ * @param _filename path to file
+ * @return Process structure
+ */
+Process *file_command(const char *_filename) {
+ char *filename = strdup(_filename);
+ Process *proc_info = NULL;
+ char sh_cmd[PATH_MAX];
+ sh_cmd[0] = '\0';
+#ifdef __APPLE__
+ const char *fmt_cmd = "file -I \"%s\" 2>&1";
+#else // GNU
+ const char *fmt_cmd = "file -i \"%s\" 2>&1";
+#endif
+ const char *fail_pattern = ": cannot open";
+
+ strchrdel(filename, SHELL_INVALID);
+ sprintf(sh_cmd, fmt_cmd, filename);
+ shell(&proc_info, SHELL_OUTPUT, sh_cmd);
+
+ // POSIXly ridiculous. Return non-zero when a file can't be found, or isn't accessible
+ if (strstr(proc_info->output, fail_pattern) != NULL) {
+ proc_info->returncode = 1;
+ }
+ free(filename);
+ return proc_info;
+}
+
+/**
+ * Execute the `file` command, parse its output, and return the data in a `Mime` structure
+ * @param filename path to file
+ * @return Mime structure
+ */
+Mime *file_mimetype(const char *filename) {
+ char **output = NULL;
+ char **parts = NULL;
+ Mime *type = NULL;
+ Process *proc = file_command(filename);
+
+ if (proc->returncode != 0) {
+ fprintf(stderr, "%s\n", proc->output);
+ fprintf(stderr, "file command returned: %d\n", proc->returncode);
+ fprintf(SYSERROR);
+ shell_free(proc);
+ return NULL;
+ }
+ output = split(proc->output, ":");
+ if (!output || output[1] == NULL) {
+ shell_free(proc);
+ return NULL;
+ }
+ parts = split(output[1], ";");
+ if (!parts || !parts[0] || !parts[1]) {
+ shell_free(proc);
+ return NULL;
+ }
+
+ char *what = strdup(parts[0]);
+ what = lstrip(what);
+
+ char *charset = strdup(strchr(parts[1], '=') + 1);
+ charset = lstrip(charset);
+ charset[strlen(charset) - 1] = '\0';
+
+ char *origin = realpath(filename, NULL);
+
+ type = (Mime *)calloc(1, sizeof(Mime));
+ type->origin = origin;
+ type->type = what;
+ type->charset = charset;
+
+ split_free(output);
+ split_free(parts);
+ shell_free(proc);
+ return type;
+}
+
+/**
+ * Free a `Mime` structure
+ * @param m
+ */
+void mime_free(Mime *m) {
+ if (m != NULL) {
+ free(m->origin);
+ free(m->type);
+ free(m->charset);
+ free(m);
+ }
+}
+
+/**
+ * Determine if a file is a text file
+ * @param filename
+ * @return yes=1, no=0
+ */
+int file_is_text(const char *filename) {
+ int result = 0;
+ char *path = normpath(filename);
+ Mime *type = file_mimetype(path);
+ if (type == NULL) {
+ fprintf(stderr, "type detection failed: %s\n", filename);
+ return -1;
+ }
+ if (startswith(type->type, "text/")) {
+ result = 1;
+ }
+ free(path);
+ mime_free(type);
+ return result;
+}
+
+/**
+ * Determine if a file is a binary data file
+ * @param filename
+ * @return yes=1, no=0
+ */
+int file_is_binary(const char *filename) {
+ int result = 0;
+ char *path = normpath(filename);
+ Mime *type = file_mimetype(path);
+ if (type == NULL) {
+ fprintf(stderr, "type detection failed: %s\n", filename);
+ return -1;
+ }
+ if (startswith(type->type, "application/") && strcmp(type->charset, "binary") == 0) {
+ result = 1;
+ }
+ free(path);
+ mime_free(type);
+ return result;
+}
+
+int file_is_binexec(const char *filename) {
+ int result = 0;
+ char *path = normpath(filename);
+ Mime *type = file_mimetype(path);
+ if (type == NULL) {
+ fprintf(stderr, "type detection failed: %s\n", filename);
+ return -1;
+ }
+ // file-5.38: changed mime name associated with executables
+ // TODO: implement compatibility function to return the correct search pattern
+ if (fnmatch("application/x-[pic|pie|ex|sh]*", type->type, FNM_PATHNAME) != FNM_NOMATCH && strcmp(type->charset, "binary") == 0) {
+ result = 1;
+ }
+ free(path);
+ mime_free(type);
+ return result;
+}