Blob Blame History Raw
/*
 * Copyright (C) 2004 Roberto Majadas
 * Copyright (C) 2005-2013 Bastien Nocera
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more av.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301  USA.
 *
 * Author:  Roberto Majadas <roberto.majadas@openshine.com>
 *          Bastien Nocera <hadess@hadess.net>
 */

#include "config.h"
#include <string.h>
#include <stdlib.h>
#include <glib/gi18n.h>
#include <locale.h>
#include <glib/gstdio.h>
#include <gio/gio.h>

/* Options */
static char **filenames = NULL;
static gboolean run_from_build_dir = FALSE;
static gint64 xid = 0;
static gboolean show_version = FALSE;

typedef enum {
	MAILER_UNKNOWN,
	MAILER_EVO,
	MAILER_BALSA,
	MAILER_SYLPHEED,
	MAILER_THUNDERBIRD,
} MailerType;

typedef struct {
	GList *file_list;
	guint num_dirs;
	MailerType type;
	char *mail_cmd;
} NautilusSendto;

static const GOptionEntry entries[] = {
	{ "run-from-build-dir", 'b', 0, G_OPTION_ARG_NONE, &run_from_build_dir, N_("Run from build directory (ignored)"), NULL },
	{ "xid", 'x', 0, G_OPTION_ARG_INT64, &xid, N_("Use XID as parent to the send dialogue (ignored)"), NULL },
	{ G_OPTION_REMAINING, '\0', 0, G_OPTION_ARG_FILENAME_ARRAY, &filenames, N_("Files to send"), "[FILES...]" },
	{"version", 'v', 0, G_OPTION_ARG_NONE, &show_version, N_("Output version information and exit"), NULL},
	{ NULL }
};

static char *
get_evo_cmd (void)
{
	char *tmp, *retval;

	tmp = g_find_program_in_path ("evolution");
	if (tmp == NULL)
		return NULL;

	retval = g_strdup_printf ("%s --component=mail %%s", tmp);
	g_free (tmp);

	return retval;
}

static gboolean
init_mailer (NautilusSendto *nst)
{
	GAppInfo *app_info;
	char *needle;

	nst->type = MAILER_UNKNOWN;

	app_info = g_app_info_get_default_for_uri_scheme ("mailto");
	if (app_info) {
		nst->mail_cmd = g_strdup (g_app_info_get_commandline (app_info));
		g_object_unref (app_info);
	} else {
		nst->mail_cmd = NULL;
	}

	if (nst->mail_cmd == NULL || *nst->mail_cmd == '\0') {
		g_free (nst->mail_cmd);
		nst->mail_cmd = get_evo_cmd ();
		nst->type = MAILER_EVO;
	} else {
		/* Find what the default mailer is */
		if (strstr (nst->mail_cmd, "balsa"))
			nst->type = MAILER_BALSA;
		else if (strstr (nst->mail_cmd, "thunder") || strstr (nst->mail_cmd, "seamonkey") || strstr (nst->mail_cmd, "icedove")) {
			char **strv;

			nst->type = MAILER_THUNDERBIRD;

			/* Thunderbird sucks, see
			 * https://bugzilla.gnome.org/show_bug.cgi?id=614222 */
			strv = g_strsplit (nst->mail_cmd, " ", -1);
			g_free (nst->mail_cmd);
			nst->mail_cmd = g_strdup_printf ("%s %%s", strv[0]);
			g_strfreev (strv);
		} else if (strstr (nst->mail_cmd, "sylpheed") || strstr (nst->mail_cmd, "claws"))
			nst->type = MAILER_SYLPHEED;
		else if (strstr (nst->mail_cmd, "anjal"))
			nst->type = MAILER_EVO;
	}

	if (nst->mail_cmd == NULL)
		return FALSE;

	/* Replace %U by %s */
	while ((needle = g_strrstr (nst->mail_cmd, "%U")) != NULL)
		needle[1] = 's';
	while ((needle = g_strrstr (nst->mail_cmd, "%u")) != NULL)
		needle[1] = 's';

	return TRUE;
}

static char *
get_filename_from_list (GList *file_list)
{
	GList *l;
	GString *common_part = NULL;
	gboolean matches = TRUE;
	guint offset = 0;
	const char *encoding;
	gboolean use_utf8 = TRUE;

	encoding = g_getenv ("G_FILENAME_ENCODING");

	if (encoding != NULL && strcasecmp (encoding, "UTF-8") != 0)
		use_utf8 = FALSE;

	if (file_list == NULL)
		return NULL;

	common_part = g_string_new ("");

	while (TRUE) {
		gunichar cur_char = '\0';
		for (l = file_list; l ; l = l->next) {
			char *path = NULL, *name = NULL;
			char *offset_name = NULL;

			path = g_filename_from_uri ((char *) l->data, NULL, NULL);
			if (!path)
				break;

			name = g_path_get_basename (path);

			if (!use_utf8) {
				char *tmp;

				tmp = g_filename_to_utf8 (name, -1, NULL, NULL, NULL);
				g_free (name);
				name = tmp;
			}

			if (!name) {
				g_free (path);
				break;
			}

			if (offset >= g_utf8_strlen (name, -1)) {
				g_free(name);
				g_free(path);
				matches = FALSE;
				break;
			}

			offset_name = g_utf8_offset_to_pointer (name, offset);

			if (offset_name == g_utf8_strrchr (name, -1, '.')) {
				g_free (name);
				g_free (path);
				matches = FALSE;
				break;
			}
			if (cur_char == '\0') {
				cur_char = g_utf8_get_char (offset_name);
			} else if (cur_char != g_utf8_get_char (offset_name)) {
				g_free (name);
				g_free (path);
				matches = FALSE;
				break;
			}
			g_free (name);
			g_free (path);
		}
		if (matches == TRUE &&
		    cur_char != '\0' &&
		    cur_char != '-' &&
		    cur_char != '_') {
			offset++;
			common_part = g_string_append_unichar (common_part,
					cur_char);
		} else {
			break;
		}
	}

	if (g_utf8_strlen (common_part->str, -1) < 4) {
		g_string_free (common_part, TRUE);
		return NULL;
	}

	return g_string_free (common_part, FALSE);
}

static char *
pack_filename_from_names (GList *file_list)
{
	char *filename;
	gboolean one_file;

	if (file_list != NULL && file_list->next != NULL)
		one_file = FALSE;
	else if (file_list != NULL)
		one_file = TRUE;

	if (one_file) {
		char *filepath;

		filepath = g_filename_from_uri ((char *)file_list->data,
						NULL, NULL);
		filename = g_path_get_basename (filepath);
		g_free (filepath);
	} else {
		filename = get_filename_from_list (file_list);
	}

	if (filename == NULL) {
		/* Translators: the default archive name if it
		 * could not be deduced from the provided files */
		filename = g_strdup (_("Archive"));
	}

	return filename;
}

static char *
pack_files (GList *file_list)
{
	char *file_roller_cmd;
	const char *filename;
	GList *l;
	GString *cmd, *tmp;
	char *pack_type, *tmp_work_dir, *packed_file;

	file_roller_cmd = g_find_program_in_path ("file-roller");
	filename = pack_filename_from_names (file_list);

	g_assert (filename != NULL && *filename != '\0');

	tmp_work_dir = g_build_filename (g_get_tmp_dir (),
					 "nautilus-sendto-XXXXXX",
					 NULL);
	tmp_work_dir = g_mkdtemp (tmp_work_dir);

	pack_type = g_strdup (".zip");

	cmd = g_string_new ("");
	g_string_printf (cmd, "%s --add-to=\"%s/%s%s\"",
			 file_roller_cmd, tmp_work_dir,
			 filename,
			 pack_type);

	/* file-roller doesn't understand URIs */
	for (l = file_list ; l; l=l->next){
		char *file;

		file = g_filename_from_uri (l->data, NULL, NULL);
		g_string_append_printf (cmd," \"%s\"", file);
		g_free (file);
	}

	g_spawn_command_line_sync (cmd->str, NULL, NULL, NULL, NULL);
	g_string_free (cmd, TRUE);
	tmp = g_string_new("");
	g_string_printf (tmp,"%s/%s%s", tmp_work_dir,
			 filename,
			 pack_type);
	g_free (tmp_work_dir);
	packed_file = g_filename_to_uri (tmp->str, NULL, NULL);
	g_string_free(tmp, TRUE);

	return packed_file;
}

static void
get_evo_mailto (GString         *mailto,
		GList           *file_list)
{
	GList *l;

	g_string_append (mailto, "mailto:");
	g_string_append (mailto, "\"\"");
	g_string_append_printf (mailto,"?attach=\"%s\"", (char *)file_list->data);
	for (l = file_list->next ; l; l=l->next)
		g_string_append_printf (mailto,"&attach=\"%s\"", (char *)l->data);
}

static void
get_balsa_mailto (NautilusSendto  *nst,
		  GString         *mailto,
		  GList           *file_list)
{
	GList *l;

	if (strstr (nst->mail_cmd, " -m ") == NULL && strstr (nst->mail_cmd, " --compose=") == NULL)
		g_string_append (mailto, " --compose=");
	g_string_append (mailto, "\"\"");
	g_string_append_printf (mailto," --attach=\"%s\"", (char *)file_list->data);
	for (l = file_list->next ; l; l=l->next)
		g_string_append_printf (mailto," --attach=\"%s\"", (char *)l->data);
}

static void
get_thunderbird_mailto (GString         *mailto,
			GList           *file_list)
{
	GList *l;

	g_string_append (mailto, "-compose \"");
	g_string_append_printf (mailto,"attachment='%s", (char *)file_list->data);
	for (l = file_list->next ; l; l=l->next)
		g_string_append_printf (mailto,",%s", (char *)l->data);
	g_string_append (mailto, "'\"");
}

static void
get_sylpheed_mailto (GString         *mailto,
		     GList           *file_list)
{
	GList *l;

	g_string_append (mailto, "--compose ");
	g_string_append (mailto, "\"\"");
	g_string_append_printf (mailto,"--attach \"%s\"", (char *)file_list->data);
	for (l = file_list->next ; l; l=l->next)
		g_string_append_printf (mailto," \"%s\"", (char *)l->data);
}

static void
send_files (NautilusSendto *nst)
{
	GString *mailto;
	char *cmd;

	if (nst->num_dirs > 0) {
		char *zip;
		zip = pack_files (nst->file_list);
		g_list_free_full (nst->file_list, g_free);
		nst->file_list = g_list_append (NULL, zip);
	}

	mailto = g_string_new ("");
	switch (nst->type) {
	case MAILER_BALSA:
		get_balsa_mailto (nst, mailto, nst->file_list);
		break;
	case MAILER_SYLPHEED:
		get_sylpheed_mailto (mailto, nst->file_list);
		break;
	case MAILER_THUNDERBIRD:
		get_thunderbird_mailto (mailto, nst->file_list);
		break;
	case MAILER_EVO:
	default:
		get_evo_mailto (mailto, nst->file_list);
	}

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
	cmd = g_strdup_printf (nst->mail_cmd, mailto->str);
	g_string_free (mailto, TRUE);
#pragma GCC diagnostic pop

	g_debug ("Mailer type: %d", nst->type);
	g_debug ("Command: %s", cmd);

	/* FIXME: collect errors from this call */
	g_spawn_command_line_async (cmd, NULL);
	g_free (cmd);
}

static char *
escape_ampersands_and_commas (const char *url)
{
	int i;
	char *str, *ptr;

	/* Count the number of ampersands & commas */
	i = 0;
	ptr = (char *) url;
	while ((ptr = strchr (ptr, '&')) != NULL) {
		i++;
		ptr++;
	}
	ptr = (char *) url;
	while ((ptr = strchr (ptr, ',')) != NULL) {
		i++;
		ptr++;
	}

	/* No ampersands or commas ? */
	if (i == 0)
		return NULL;

	/* Replace the '&' */
	str = g_malloc0 (strlen (url) - i + 3 * i + 1);
	ptr = str;
	for (i = 0; url[i] != '\0'; i++) {
		if (url[i] == '&') {
			*ptr++ = '%';
			*ptr++ = '2';
			*ptr++ = '6';
		} else if (url[i] == ',') {
			*ptr++ = '%';
			*ptr++ = '2';
			*ptr++ = 'C';
		} else {
			*ptr++ = url[i];
		}
	}

	return str;
}

static char *
get_target_filename (GFile *file)
{
	GFileInfo *info;
	const char *target;
	GFile *new_file;
	char *ret;

	info = g_file_query_info (file, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI, G_FILE_QUERY_INFO_NONE, NULL, NULL);
	if (info == NULL)
		return NULL;

	target = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI);
	if (target == NULL) {
		g_object_unref (info);
		return NULL;
	}

	new_file = g_file_new_for_uri (target);
	g_object_unref (info);

	ret = g_file_get_path (new_file);
	g_object_unref (new_file);

	return ret;
}


static void
nautilus_sendto_init (NautilusSendto *nst)
{
	int i;

	/* Clean up the URIs passed, and collect whether we have directories */
	for (i = 0; filenames != NULL && filenames[i] != NULL; i++) {
		GFile *file;
		char *filename, *escaped, *uri;
		GFileInfo *info;
		const char *mimetype;

		/* We need a filename */
		file = g_file_new_for_commandline_arg (filenames[i]);
		filename = g_file_get_path (file);
		if (filename == NULL) {
			filename = get_target_filename (file);
			if (filename == NULL) {
				g_object_unref (file);
				g_debug ("Could not get a filename for '%s'", filenames[i]);
				continue;
			}
		}

		/* Get the mime-type, and whether the file is readable */
		info = g_file_query_info (file,
					  G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE","G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE","G_FILE_ATTRIBUTE_ACCESS_CAN_READ,
					  G_FILE_QUERY_INFO_NONE,
					  NULL,
					  NULL);
		g_object_unref (file);

		if (info == NULL) {
			g_debug ("Could not get info for '%s'", filenames[i]);
			continue;
		}

		if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_ACCESS_CAN_READ)) {
			if (g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_READ) == FALSE) {
				g_debug ("'%s' is not readable", filenames[i]);
				g_object_unref (info);
				continue;
			}
		} else {
			g_debug ("No can-read attribute for '%s', assuming it is", filenames[i]);
		}

		mimetype = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE);
		if (!mimetype)
			mimetype = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE);
		if (!mimetype) {
			g_object_unref (info);
			g_debug ("Could not get mime-type for '%s'", filenames[i]);
			continue;
		}
		if (g_str_equal (mimetype, "inode/directory"))
			nst->num_dirs++;

		g_object_unref (info);

		uri = g_filename_to_uri (filename, NULL, NULL);
		g_free (filename);
		escaped = escape_ampersands_and_commas (uri);

		if (escaped == NULL) {
			nst->file_list = g_list_prepend (nst->file_list, uri);
		} else {
			nst->file_list = g_list_prepend (nst->file_list, escaped);
			g_free (uri);
		}
	}

	nst->file_list = g_list_reverse (nst->file_list);
}

int main (int argc, char **argv)
{
	GOptionContext *context;
	GError *error = NULL;
	NautilusSendto *nst;
	int ret = 0;

	setlocale (LC_ALL, "");
	bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
	bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
	textdomain (GETTEXT_PACKAGE);

	context = g_option_context_new ("");
	g_option_context_add_main_entries (context, entries, GETTEXT_PACKAGE);
	if (g_option_context_parse (context, &argc, &argv, &error) == FALSE) {
		g_print (_("Could not parse command-line options: %s\n"), error->message);
		g_error_free (error);
		return 1;
	}

        if (show_version) {
		g_print ("%s %s\n", PACKAGE_NAME, PACKAGE_VERSION);
		return 0;
	}

	nst = g_new0 (NautilusSendto, 1);
	nautilus_sendto_init (nst);
	if (!init_mailer (nst)) {
		g_print (_("No mail client installed, not sending files\n"));
		goto out;
	}


	if (nst->file_list == NULL) {
		g_print (_("Expects URIs or filenames to be passed as options\n"));
		ret = 1;
		goto out;
	}

	send_files (nst);

out:
	g_free (nst);

	return ret;
}