summaryrefslogtreecommitdiff
path: root/libmailutils/base/mutil.c
diff options
context:
space:
mode:
Diffstat (limited to 'libmailutils/base/mutil.c')
-rw-r--r--libmailutils/base/mutil.c1536
1 files changed, 1536 insertions, 0 deletions
diff --git a/libmailutils/base/mutil.c b/libmailutils/base/mutil.c
new file mode 100644
index 000000000..2b091ce70
--- /dev/null
+++ b/libmailutils/base/mutil.c
@@ -0,0 +1,1536 @@
+/* GNU Mailutils -- a suite of utilities for electronic mail
+ Copyright (C) 1999, 2000, 2001, 2002, 2003, 2005, 2006, 2007, 2009,
+ 2010 Free Software Foundation, Inc.
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 3 of the License, or (at your option) any later version.
+
+ This library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General
+ Public License along with this library; if not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301 USA */
+
+#if HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <errno.h>
+#include <limits.h>
+#include <netdb.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/wait.h>
+#include <sys/select.h>
+
+#ifdef HAVE_STRINGS_H
+#include <strings.h>
+#endif
+
+#include <mailutils/address.h>
+#include <mailutils/assoc.h>
+#include <mailutils/argcv.h>
+#include <mailutils/error.h>
+#include <mailutils/errno.h>
+#include <mailutils/iterator.h>
+#include <mailutils/mutil.h>
+#include <mailutils/parse822.h>
+#include <mailutils/mu_auth.h>
+#include <mailutils/header.h>
+#include <mailutils/message.h>
+#include <mailutils/envelope.h>
+#include <mailutils/nls.h>
+#include <mailutils/stream.h>
+#include <mailutils/filter.h>
+#include <mailutils/sql.h>
+#include <mailutils/url.h>
+#include <mailutils/io.h>
+#include <mailutils/cctype.h>
+#include <mailutils/cstr.h>
+
+#include <mailutils/sys/registrar.h>
+
+/* convert a sequence of hex characters into an integer */
+
+unsigned long
+mu_hex2ul (char hex)
+{
+ if (hex >= '0' && hex <= '9')
+ return hex - '0';
+
+ if (hex >= 'a' && hex <= 'z')
+ return hex - 'a' + 10;
+
+ if (hex >= 'A' && hex <= 'Z')
+ return hex - 'A' + 10;
+
+ return -1;
+}
+
+size_t
+mu_hexstr2ul (unsigned long *ul, const char *hex, size_t len)
+{
+ size_t r;
+
+ *ul = 0;
+
+ for (r = 0; r < len; r++)
+ {
+ unsigned long v = mu_hex2ul (hex[r]);
+
+ if (v == (unsigned long)-1)
+ return r;
+
+ *ul = *ul * 16 + v;
+ }
+ return r;
+}
+
+
+char *
+mu_get_homedir (void)
+{
+ char *homedir = getenv ("HOME");
+ if (homedir)
+ homedir = strdup (homedir);
+ else
+ {
+ struct mu_auth_data *auth = mu_get_auth_by_uid (geteuid ());
+ if (!auth)
+ return NULL;
+ homedir = strdup (auth->dir);
+ mu_auth_data_free (auth);
+ }
+ return homedir;
+}
+
+char *
+mu_getcwd ()
+{
+ char *ret;
+ unsigned path_max;
+ char buf[128];
+
+ errno = 0;
+ ret = getcwd (buf, sizeof (buf));
+ if (ret != NULL)
+ return strdup (buf);
+
+ if (errno != ERANGE)
+ return NULL;
+
+ path_max = 128;
+ path_max += 2; /* The getcwd docs say to do this. */
+
+ for (;;)
+ {
+ char *cwd = (char *) malloc (path_max);
+
+ errno = 0;
+ ret = getcwd (cwd, path_max);
+ if (ret != NULL)
+ return ret;
+ if (errno != ERANGE)
+ {
+ int save_errno = errno;
+ free (cwd);
+ errno = save_errno;
+ return NULL;
+ }
+
+ free (cwd);
+
+ path_max += path_max / 16;
+ path_max += 32;
+ }
+ /* oops? */
+ return NULL;
+}
+
+char *
+mu_get_full_path (const char *file)
+{
+ char *p = NULL;
+
+ if (!file)
+ p = mu_getcwd ();
+ else if (*file != '/')
+ {
+ char *cwd = mu_getcwd ();
+ if (cwd)
+ {
+ p = calloc (strlen (cwd) + 1 + strlen (file) + 1, 1);
+ if (p)
+ sprintf (p, "%s/%s", cwd, file);
+ free (cwd);
+ }
+ }
+
+ if (!p)
+ p = strdup (file);
+ return p;
+}
+
+/* NOTE: Allocates Memory. */
+/* Expand: ~ --> /home/user and to ~guest --> /home/guest. */
+char *
+mu_tilde_expansion (const char *ref, const char *delim, const char *homedir)
+{
+ char *base = strdup (ref);
+ char *home = NULL;
+ char *proto = NULL;
+ size_t proto_len = 0;
+ char *p;
+
+ for (p = base; *p && isascii (*p) && isalnum (*p); p++)
+ ;
+
+ if (*p == ':')
+ {
+ p++;
+ proto_len = p - base;
+ proto = malloc (proto_len + 1);
+ if (!proto)
+ return NULL;
+ memcpy (proto, base, proto_len);
+ proto[proto_len] = 0;
+ /* Allow for extra pair of slashes after the protocol specifier */
+ if (*p == delim[0])
+ p++;
+ if (*p == delim[0])
+ p++;
+ }
+ else
+ p = base;
+
+ if (*p == '~')
+ {
+ p++;
+ if (*p == delim[0] || *p == '\0')
+ {
+ char *s;
+ if (!homedir)
+ {
+ home = mu_get_homedir ();
+ if (!home)
+ return base;
+ homedir = home;
+ }
+ s = calloc (proto_len + strlen (homedir) + strlen (p) + 1, 1);
+ if (proto_len)
+ strcpy (s, proto);
+ else
+ s[0] = 0;
+ strcat (s, homedir);
+ strcat (s, p);
+ free (base);
+ base = s;
+ }
+ else
+ {
+ struct mu_auth_data *auth;
+ char *s = p;
+ char *name;
+ while (*s && *s != delim[0])
+ s++;
+ name = calloc (s - p + 1, 1);
+ memcpy (name, p, s - p);
+ name[s - p] = '\0';
+
+ auth = mu_get_auth_by_name (name);
+ free (name);
+ if (auth)
+ {
+ char *buf = calloc (proto_len + strlen (auth->dir)
+ + strlen (s) + 1, 1);
+ if (proto_len)
+ strcpy (buf, proto);
+ else
+ buf[0] = 0;
+ strcat (buf, auth->dir);
+ strcat (buf, s);
+ free (base);
+ base = buf;
+ mu_auth_data_free (auth);
+ }
+ }
+ }
+ if (home)
+ free (home);
+ return base;
+}
+
+/* Smart strncpy that always add the null and returns the number of bytes
+ written. */
+size_t
+mu_cpystr (char *dst, const char *src, size_t size)
+{
+ size_t len = src ? strlen (src) : 0 ;
+ if (dst == NULL || size == 0)
+ return len;
+ if (len >= size)
+ len = size - 1;
+ memcpy (dst, src, len);
+ dst[len] = '\0';
+ return len;
+}
+
+#ifndef MAXHOSTNAMELEN
+# define MAXHOSTNAMELEN 64
+#endif
+
+int
+mu_get_host_name (char **host)
+{
+ char *hostname = NULL;
+ size_t size = 0;
+ char *p;
+
+ while (1)
+ {
+ if (size == 0)
+ {
+ size = MAXHOSTNAMELEN;
+ p = malloc (size);
+ }
+ else
+ {
+ size_t ns = size * 2;
+ if (ns < size)
+ {
+ free (hostname);
+ return ENOMEM;
+ }
+ size = ns;
+ p = realloc (hostname, size);
+ }
+ if (!p)
+ {
+ free (hostname);
+ return ENOMEM;
+ }
+ hostname = p;
+ hostname[size - 1] = 0;
+ if (gethostname (hostname, size - 1) == 0)
+ {
+ if (!hostname[size - 1])
+ break;
+ }
+ else if (errno != 0 && errno != ENAMETOOLONG && errno != EINVAL
+ && errno != ENOMEM)
+ {
+ int rc = errno;
+ free (hostname);
+ return rc;
+ }
+ }
+
+ /* Try to return fully qualified host name */
+ if (!strchr (hostname, '.'))
+ {
+ struct hostent *hp = gethostbyname (hostname);
+ if (hp)
+ {
+ size_t len = strlen (hp->h_name);
+ if (size < len + 1)
+ {
+ p = realloc (hostname, len + 1);
+ if (!p)
+ {
+ free (hostname);
+ return ENOMEM;
+ }
+ hostname = p;
+ }
+ strcpy (hostname, hp->h_name);
+ }
+ }
+
+ *host = hostname;
+ return 0;
+}
+
+/*
+ * Functions used to convert unix mailbox/user names into RFC822 addr-specs.
+ */
+
+static char *mu_user_email = 0;
+
+int
+mu_set_user_email (const char *candidate)
+{
+ int err = 0;
+ mu_address_t addr = NULL;
+ size_t emailno = 0;
+ char *email = NULL;
+ const char *domain = NULL;
+
+ if ((err = mu_address_create (&addr, candidate)) != 0)
+ return err;
+
+ if ((err = mu_address_get_email_count (addr, &emailno)) != 0)
+ goto cleanup;
+
+ if (emailno != 1)
+ {
+ errno = EINVAL;
+ goto cleanup;
+ }
+
+ if ((err = mu_address_aget_email (addr, 1, &email)) != 0)
+ goto cleanup;
+
+ if (mu_user_email)
+ free (mu_user_email);
+
+ mu_user_email = email;
+
+ if ((err = mu_address_sget_domain (addr, 1, &domain)) == 0)
+ mu_set_user_email_domain (domain);
+
+cleanup:
+ mu_address_destroy (&addr);
+
+ return err;
+}
+
+static char *mu_user_email_domain = 0;
+
+int
+mu_set_user_email_domain (const char *domain)
+{
+ char *d = NULL;
+
+ if (!domain)
+ return EINVAL;
+
+ d = strdup (domain);
+
+ if (!d)
+ return ENOMEM;
+
+ if (mu_user_email_domain)
+ free (mu_user_email_domain);
+
+ mu_user_email_domain = d;
+
+ return 0;
+}
+
+/* FIXME: must be called _sget_ */
+int
+mu_get_user_email_domain (const char **domain)
+{
+ int err = 0;
+
+ if (!mu_user_email_domain)
+ {
+ if ((err = mu_get_host_name (&mu_user_email_domain)))
+ return err;
+ }
+
+ *domain = mu_user_email_domain;
+
+ return 0;
+}
+
+int
+mu_aget_user_email_domain (char **pdomain)
+{
+ const char *domain;
+ int status = mu_get_user_email_domain (&domain);
+ if (status)
+ return status;
+ if (domain == NULL)
+ *pdomain = NULL;
+ else
+ {
+ *pdomain = strdup (domain);
+ if (*pdomain == NULL)
+ return ENOMEM;
+ }
+ return 0;
+}
+
+/* Note: allocates memory */
+char *
+mu_get_user_email (const char *name)
+{
+ int status = 0;
+ char *localpart = NULL;
+ const char *domainpart = NULL;
+ char *email = NULL;
+ char *tmpname = NULL;
+
+ if (!name && mu_user_email)
+ {
+ email = strdup (mu_user_email);
+ if (!email)
+ errno = ENOMEM;
+ return email;
+ }
+
+ if (!name)
+ {
+ struct mu_auth_data *auth = mu_get_auth_by_uid (getuid ());
+ if (!auth)
+ {
+ errno = EINVAL;
+ return NULL;
+ }
+ name = tmpname = strdup(auth->name);
+ if (auth)
+ mu_auth_data_free (auth);
+ }
+
+ status = mu_get_user_email_domain (&domainpart);
+
+ if (status)
+ {
+ free(tmpname);
+ errno = status;
+ return NULL;
+ }
+
+ if ((status = mu_parse822_quote_local_part (&localpart, name)))
+ {
+ free(tmpname);
+ errno = status;
+ return NULL;
+ }
+
+
+ email = malloc (strlen (localpart) + 1
+ + strlen (domainpart) + 1);
+ if (!email)
+ errno = ENOMEM;
+ else
+ sprintf (email, "%s@%s", localpart, domainpart);
+
+ free(tmpname);
+ free (localpart);
+
+ return email;
+}
+
+/* mu_normalize_path: convert pathname containig relative paths specs (../)
+ into an equivalent absolute path. Strip trailing delimiter if present,
+ unless it is the only character left. E.g.:
+
+ /home/user/../smith --> /home/smith
+ /home/user/../.. --> /
+
+*/
+char *
+mu_normalize_path (char *path)
+{
+ int len;
+ char *p;
+
+ if (!path)
+ return path;
+
+ len = strlen (path);
+
+ /* Empty string is returned as is */
+ if (len == 0)
+ return path;
+
+ /* delete trailing delimiter if any */
+ if (len && path[len-1] == '/')
+ path[len-1] = 0;
+
+ /* Eliminate any /../ */
+ for (p = strchr (path, '.'); p; p = strchr (p, '.'))
+ {
+ if (p > path && p[-1] == '/')
+ {
+ if (p[1] == '.' && (p[2] == 0 || p[2] == '/'))
+ /* found */
+ {
+ char *q, *s;
+
+ /* Find previous delimiter */
+ for (q = p-2; *q != '/' && q >= path; q--)
+ ;
+
+ if (q < path)
+ break;
+ /* Copy stuff */
+ s = p + 2;
+ p = q;
+ while ((*q++ = *s++))
+ ;
+ continue;
+ }
+ }
+
+ p++;
+ }
+
+ if (path[0] == 0)
+ {
+ path[0] = '/';
+ path[1] = 0;
+ }
+
+ return path;
+}
+
+/* Create and open a temporary file. Be very careful about it, since we
+ may be running with extra privilege i.e setgid().
+ Returns file descriptor of the open file.
+ If namep is not NULL, the pointer to the malloced file name will
+ be stored there. Otherwise, the file is unlinked right after open,
+ i.e. it will disappear after close(fd). */
+
+#ifndef P_tmpdir
+# define P_tmpdir "/tmp"
+#endif
+
+int
+mu_tempfile (const char *tmpdir, char **namep)
+{
+ char *filename;
+ int fd;
+
+ if (!tmpdir)
+ tmpdir = (getenv ("TMPDIR")) ? getenv ("TMPDIR") : P_tmpdir;
+
+ filename = malloc (strlen (tmpdir) + /*'/'*/1 + /* "muXXXXXX" */8 + 1);
+ if (!filename)
+ return -1;
+ sprintf (filename, "%s/muXXXXXX", tmpdir);
+
+#ifdef HAVE_MKSTEMP
+ {
+ int save_mask = umask (077);
+ fd = mkstemp (filename);
+ umask (save_mask);
+ }
+#else
+ if (mktemp (filename))
+ fd = open (filename, O_CREAT|O_EXCL|O_RDWR, 0600);
+ else
+ fd = -1;
+#endif
+
+ if (fd == -1)
+ {
+ mu_error (_("cannot open temporary file: %s"), mu_strerror (errno));
+ free (filename);
+ return -1;
+ }
+
+ if (namep)
+ *namep = filename;
+ else
+ {
+ unlink (filename);
+ free (filename);
+ }
+
+ return fd;
+}
+
+/* Create a unique temporary file name in tmpdir. The function
+ creates an empty file with this name to avoid possible race
+ conditions. Returns a pointer to the malloc'ed file name.
+ If tmpdir is NULL, the value of the environment variable
+ TMPDIR or the hardcoded P_tmpdir is used, whichever is defined. */
+
+char *
+mu_tempname (const char *tmpdir)
+{
+ char *filename = NULL;
+ int fd = mu_tempfile (tmpdir, &filename);
+ close (fd);
+ return filename;
+}
+
+/* See Advanced Programming in the UNIX Environment, Stevens,
+ * program 10.20 for the rational for the signal handling. I
+ * had to look it up, so if somebody else is curious, thats where
+ * to find it.
+ */
+int
+mu_spawnvp (const char *prog, char *av[], int *stat)
+{
+ pid_t pid;
+ int err = 0;
+ int progstat;
+ struct sigaction ignore;
+ struct sigaction saveintr;
+ struct sigaction savequit;
+ sigset_t chldmask;
+ sigset_t savemask;
+
+ if (!prog || !av)
+ return EINVAL;
+
+ ignore.sa_handler = SIG_IGN; /* ignore SIGINT and SIGQUIT */
+ ignore.sa_flags = 0;
+ sigemptyset (&ignore.sa_mask);
+
+ if (sigaction (SIGINT, &ignore, &saveintr) < 0)
+ return errno;
+ if (sigaction (SIGQUIT, &ignore, &savequit) < 0)
+ {
+ sigaction (SIGINT, &saveintr, NULL);
+ return errno;
+ }
+
+ sigemptyset (&chldmask); /* now block SIGCHLD */
+ sigaddset (&chldmask, SIGCHLD);
+
+ if (sigprocmask (SIG_BLOCK, &chldmask, &savemask) < 0)
+ {
+ sigaction (SIGINT, &saveintr, NULL);
+ sigaction (SIGQUIT, &savequit, NULL);
+ return errno;
+ }
+
+#ifdef HAVE_VFORK
+ pid = vfork ();
+#else
+ pid = fork ();
+#endif
+
+ if (pid < 0)
+ {
+ err = errno;
+ }
+ else if (pid == 0)
+ { /* child */
+ /* restore previous signal actions & reset signal mask */
+ sigaction (SIGINT, &saveintr, NULL);
+ sigaction (SIGQUIT, &savequit, NULL);
+ sigprocmask (SIG_SETMASK, &savemask, NULL);
+
+ execvp (prog, av);
+#ifdef HAVE__EXIT
+ _exit (127); /* exec error */
+#else
+ exit (127);
+#endif
+ }
+ else
+ { /* parent */
+ while (waitpid (pid, &progstat, 0) < 0)
+ if (errno != EINTR)
+ {
+ err = errno; /* error other than EINTR from waitpid() */
+ break;
+ }
+ if (err == 0 && stat)
+ *stat = progstat;
+ }
+
+ /* restore previous signal actions & reset signal mask */
+ /* preserve first error number, but still try and reset the signals */
+ if (sigaction (SIGINT, &saveintr, NULL) < 0)
+ err = err ? err : errno;
+ if (sigaction (SIGQUIT, &savequit, NULL) < 0)
+ err = err ? err : errno;
+ if (sigprocmask (SIG_SETMASK, &savemask, NULL) < 0)
+ err = err ? err : errno;
+
+ return err;
+}
+
+/* The result of readlink() may be a path relative to that link,
+ * qualify it if necessary.
+ */
+static void
+mu_qualify_link (const char *path, const char *link, char *qualified)
+{
+ const char *lb = NULL;
+ size_t len;
+
+ /* link is full path */
+ if (*link == '/')
+ {
+ mu_cpystr (qualified, link, _POSIX_PATH_MAX);
+ return;
+ }
+
+ if ((lb = strrchr (path, '/')) == NULL)
+ {
+ /* no path in link */
+ mu_cpystr (qualified, link, _POSIX_PATH_MAX);
+ return;
+ }
+
+ len = lb - path + 1;
+ memcpy (qualified, path, len);
+ mu_cpystr (qualified + len, link, _POSIX_PATH_MAX - len);
+}
+
+#ifndef _POSIX_SYMLOOP_MAX
+# define _POSIX_SYMLOOP_MAX 255
+#endif
+
+int
+mu_unroll_symlink (char *out, size_t outsz, const char *in)
+{
+ char path[_POSIX_PATH_MAX];
+ int symloops = 0;
+
+ while (symloops++ < _POSIX_SYMLOOP_MAX)
+ {
+ struct stat s;
+ char link[_POSIX_PATH_MAX];
+ char qualified[_POSIX_PATH_MAX];
+ int len;
+
+ if (lstat (in, &s) == -1)
+ return errno;
+
+ if (!S_ISLNK (s.st_mode))
+ {
+ mu_cpystr (path, in, sizeof (path));
+ break;
+ }
+
+ if ((len = readlink (in, link, sizeof (link))) == -1)
+ return errno;
+
+ link[(len >= sizeof (link)) ? (sizeof (link) - 1) : len] = '\0';
+
+ mu_qualify_link (in, link, qualified);
+
+ mu_cpystr (path, qualified, sizeof (path));
+
+ in = path;
+ }
+
+ mu_cpystr (out, path, outsz);
+
+ return 0;
+}
+
+/* Expand a PATTERN to the pathname. PATTERN may contain the following
+ macro-notations:
+ ---------+------------
+ notation | expands to
+ ---------+------------
+ %u user name
+ %h user's home dir
+ ~ Likewise
+ ---------+------------
+
+ Allocates memory.
+*/
+char *
+mu_expand_path_pattern (const char *pattern, const char *username)
+{
+ const char *p;
+ char *q;
+ char *path;
+ size_t len = 0;
+ struct mu_auth_data *auth = NULL;
+
+ for (p = pattern; *p; p++)
+ {
+ if (*p == '~')
+ {
+ if (!auth)
+ {
+ auth = mu_get_auth_by_name (username);
+ if (!auth)
+ return NULL;
+ }
+ len += strlen (auth->dir);
+ }
+ else if (*p == '%')
+ switch (*++p)
+ {
+ case 'u':
+ len += strlen (username);
+ break;
+
+ case 'h':
+ if (!auth)
+ {
+ auth = mu_get_auth_by_name (username);
+ if (!auth)
+ return NULL;
+ }
+ len += strlen (auth->dir);
+ break;
+
+ case '%':
+ len++;
+ break;
+
+ default:
+ len += 2;
+ }
+ else
+ len++;
+ }
+
+ path = malloc (len + 1);
+ if (!path)
+ return NULL;
+
+ p = pattern;
+ q = path;
+ while (*p)
+ {
+ size_t off = strcspn (p, "~%");
+ memcpy (q, p, off);
+ q += off;
+ p += off;
+ if (*p == '~')
+ {
+ strcpy (q, auth->dir);
+ q += strlen (auth->dir);
+ p++;
+ }
+ else if (*p == '%')
+ {
+ switch (*++p)
+ {
+ case 'u':
+ strcpy (q, username);
+ q += strlen (username);
+ break;
+
+ case 'h':
+ strcpy (q, auth->dir);
+ q += strlen (auth->dir);
+ break;
+
+ case '%':
+ *q++ = '%';
+ break;
+
+ default:
+ *q++ = '%';
+ *q++ = *p;
+ }
+ p++;
+ }
+ }
+
+ *q = 0;
+ if (auth)
+ mu_auth_data_free (auth);
+ return path;
+}
+
+#define ST_INIT 0
+#define ST_MSGID 1
+
+static int
+strip_message_id (char *msgid, char **pval)
+{
+ char *p, *q;
+ int state;
+
+ *pval = strdup (msgid);
+ if (!*pval)
+ return ENOMEM;
+ state = ST_INIT;
+ for (p = q = *pval; *p; p++)
+ {
+ switch (state)
+ {
+ case ST_INIT:
+ if (*p == '<')
+ {
+ *q++ = *p;
+ state = ST_MSGID;
+ }
+ else if (isspace (*p))
+ *q++ = *p;
+ break;
+
+ case ST_MSGID:
+ *q++ = *p;
+ if (*p == '>')
+ state = ST_INIT;
+ break;
+ }
+ }
+ *q = 0;
+ return 0;
+}
+
+int
+get_msgid_header (mu_header_t hdr, const char *name, char **val)
+{
+ char *p;
+ int status = mu_header_aget_value (hdr, name, &p);
+ if (status)
+ return status;
+ status = strip_message_id (p, val);
+ free (p);
+ return status;
+}
+
+static char *
+concat (const char *s1, const char *s2)
+{
+ int len = (s1 ? strlen (s1) : 0) + (s2 ? strlen (s2) : 0) + 2;
+ char *s = malloc (len);
+ if (s)
+ {
+ char *p = s;
+
+ if (s1)
+ {
+ strcpy (p, s1);
+ p += strlen (s1);
+ *p++ = ' ';
+ }
+ if (s2)
+ strcpy (p, s2);
+ }
+ return s;
+}
+
+/* rfc2822:
+
+ The "References:" field will contain the contents of the parent's
+ "References:" field (if any) followed by the contents of the parent's
+ "Message-ID:" field (if any). If the parent message does not contain
+ a "References:" field but does have an "In-Reply-To:" field
+ containing a single message identifier, then the "References:" field
+ will contain the contents of the parent's "In-Reply-To:" field
+ followed by the contents of the parent's "Message-ID:" field (if
+ any). If the parent has none of the "References:", "In-Reply-To:",
+ or "Message-ID:" fields, then the new message will have no
+ References:" field. */
+
+int
+mu_rfc2822_references (mu_message_t msg, char **pstr)
+{
+ char *ref = NULL, *msgid = NULL;
+ mu_header_t hdr;
+ int rc;
+
+ rc = mu_message_get_header (msg, &hdr);
+ if (rc)
+ return rc;
+ get_msgid_header (hdr, MU_HEADER_MESSAGE_ID, &msgid);
+ if (get_msgid_header (hdr, MU_HEADER_REFERENCES, &ref))
+ get_msgid_header (hdr, MU_HEADER_IN_REPLY_TO, &ref);
+
+ if (ref || msgid)
+ {
+ *pstr = concat (ref, msgid);
+ free (ref);
+ free (msgid);
+ return 0;
+ }
+ return MU_ERR_FAILURE;
+}
+
+int
+mu_rfc2822_msg_id (int subpart, char **pval)
+{
+ char date[4+2+2+2+2+2+1];
+ time_t t = time (NULL);
+ struct tm *tm = localtime (&t);
+ char *host;
+ char *p;
+
+ mu_strftime (date, sizeof date, "%Y%m%d%H%M%S", tm);
+ mu_get_host_name (&host);
+
+ if (subpart)
+ {
+ struct timeval tv;
+ gettimeofday (&tv, NULL);
+ mu_asprintf (&p, "<%s.%lu.%d@%s>",
+ date,
+ (unsigned long) getpid (),
+ subpart,
+ host);
+ }
+ else
+ mu_asprintf (&p, "<%s.%lu@%s>", date, (unsigned long) getpid (), host);
+ free (host);
+ *pval = p;
+ return 0;
+}
+
+#define DATEBUFSIZE 128
+#define COMMENT "Your message of "
+
+/*
+ The "In-Reply-To:" field will contain the contents of the "Message-
+ ID:" field of the message to which this one is a reply (the "parent
+ message"). If there is more than one parent message, then the "In-
+ Reply-To:" field will contain the contents of all of the parents'
+ "Message-ID:" fields. If there is no "Message-ID:" field in any of
+ the parent messages, then the new message will have no "In-Reply-To:"
+ field.
+*/
+int
+mu_rfc2822_in_reply_to (mu_message_t msg, char **pstr)
+{
+ const char *value = NULL;
+ char *s1 = NULL, *s2 = NULL;
+ mu_header_t hdr;
+ int rc;
+
+ rc = mu_message_get_header (msg, &hdr);
+ if (rc)
+ return rc;
+
+ if (mu_header_sget_value (hdr, MU_HEADER_DATE, &value))
+ {
+ mu_envelope_t envelope = NULL;
+ mu_message_get_envelope (msg, &envelope);
+ mu_envelope_sget_date (envelope, &value);
+ }
+
+ if (value)
+ {
+ s1 = malloc (sizeof (COMMENT) + strlen (value));
+ if (!s1)
+ return ENOMEM;
+ strcat (strcpy (s1, COMMENT), value);
+ }
+
+ if (mu_header_sget_value (hdr, MU_HEADER_MESSAGE_ID, &value) == 0)
+ {
+ s2 = malloc (strlen (value) + 3);
+ if (!s2)
+ {
+ free (s1);
+ return ENOMEM;
+ }
+ strcat (strcpy (s2, "\n\t"), value);
+ }
+
+ if (s1 || s2)
+ {
+ *pstr = concat (s1, s2);
+ free (s1);
+ free (s2);
+ return 0;
+ }
+ return MU_ERR_FAILURE;
+}
+
+/* Based on strstr from GNU libc (Stephen R. van den Berg,
+ berg@pool.informatik.rwth-aachen.de) */
+
+char *
+mu_strcasestr (const char *a_haystack, const char *a_needle)
+{
+ register const unsigned char *haystack = (unsigned char*) a_haystack,
+ *needle = (unsigned char*) a_needle;
+ register unsigned int b, c;
+
+#define U(c) mu_toupper (c)
+ if ((b = U (*needle)))
+ {
+ haystack--;
+ do
+ {
+ if (!(c = *++haystack))
+ goto ret0;
+ }
+ while (U (c) != b);
+
+ if (!(c = *++needle))
+ goto foundneedle;
+
+ c = U (c);
+ ++needle;
+ goto jin;
+
+ for (;;)
+ {
+ register unsigned int a;
+ register const unsigned char *rhaystack, *rneedle;
+
+ do
+ {
+ if (!(a = *++haystack))
+ goto ret0;
+ if (U (a) == b)
+ break;
+ if (!(a = *++haystack))
+ goto ret0;
+shloop: ;
+ }
+ while (U (a) != b);
+
+jin: if (!(a = *++haystack))
+ goto ret0;
+
+ if (U (a) != c)
+ goto shloop;
+
+ if (U (*(rhaystack = haystack-- + 1)) ==
+ (a = U (*(rneedle = needle))))
+ do
+ {
+ if (!a)
+ goto foundneedle;
+ if (U (*++rhaystack) != (a = U (*++needle)))
+ break;
+ if (!a)
+ goto foundneedle;
+ }
+ while (U (*++rhaystack) == (a = U (*++needle)));
+
+ needle = rneedle;
+
+ if (!a)
+ break;
+ }
+ }
+foundneedle:
+ return (char*)haystack;
+ret0:
+ return NULL;
+
+#undef U
+}
+
+int
+mu_string_unfold (char *text, size_t *plen)
+{
+ char *p, *q;
+ enum uf_state { uf_init, uf_nl, uf_fold } state = uf_init;
+#define ISSPACE(c) (c == '\r' || c == ' ' || c == '\t')
+
+ if (!text)
+ return EINVAL;
+
+ for (p = q = text; *q; q++)
+ {
+ switch (state)
+ {
+ case uf_init:
+ if (*q == '\n')
+ state = uf_nl;
+ else
+ *p++ = *q;
+ break;
+
+ case uf_nl:
+ if (ISSPACE (*q))
+ state = uf_fold;
+ else
+ {
+ state = uf_init;
+ *p++ = *q;
+ }
+ break;
+
+ case uf_fold:
+ if (!ISSPACE (*q))
+ {
+ *p++ = ' ';
+ *p++ = *q;
+ state = uf_init;
+ }
+ break;
+ }
+ }
+
+ *p++ = 0;
+ if (plen)
+ *plen = p - text;
+ return 0;
+}
+
+int
+mu_true_answer_p (const char *p)
+{
+ if (!p)
+ return -1;
+
+ while (*p && isspace (*p))
+ p++;
+
+ if (*p)
+ {
+ /* TRANSLATORS: This is a list of characters which start
+ an affirmative answer. Whenever possible, please preserve
+ 'yY' in your translation, e.g., for Euskara:
+
+ msgstr "yYbB";
+ */
+ if (strchr (_("yY"), *p))
+ return 1;
+
+ /* TRANSLATORS: This is a list of characters which start
+ a negative answer. Whenever possible, please preserve
+ 'nN' in your translation, e.g., for Euskara:
+
+ msgstr "nNeE";
+ */
+ else if (strchr (_("nN"), *p))
+ return 0;
+ }
+ return -1;
+}
+
+/* Returns true if SCHEME represents a local (autodetect) mail folder. */
+int
+mu_scheme_autodetect_p (mu_url_t url)
+{
+ if (mu_url_is_scheme (url, "file"))
+ {
+ mu_url_expand_path (url);
+ return 1;
+ }
+ return 0;
+}
+
+int
+mu_fd_wait (int fd, int *pflags, struct timeval *tvp)
+{
+ fd_set rdset, wrset, exset;
+ int rc;
+
+ FD_ZERO (&rdset);
+ FD_ZERO (&wrset);
+ FD_ZERO (&exset);
+ if ((*pflags) & MU_STREAM_READY_RD)
+ FD_SET (fd, &rdset);
+ if ((*pflags) & MU_STREAM_READY_WR)
+ FD_SET (fd, &wrset);
+ if ((*pflags) & MU_STREAM_READY_EX)
+ FD_SET (fd, &exset);
+
+ do
+ {
+ if (tvp)
+ {
+ struct timeval tv = *tvp;
+ rc = select (fd + 1, &rdset, &wrset, &exset, &tv);
+ }
+ else
+ rc = select (fd + 1, &rdset, &wrset, &exset, NULL);
+ }
+ while (rc == -1 && errno == EINTR);
+
+ if (rc < 0)
+ return errno;
+
+ *pflags = 0;
+ if (rc > 0)
+ {
+ if (FD_ISSET (fd, &rdset))
+ *pflags |= MU_STREAM_READY_RD;
+ if (FD_ISSET (fd, &wrset))
+ *pflags |= MU_STREAM_READY_WR;
+ if (FD_ISSET (fd, &exset))
+ *pflags |= MU_STREAM_READY_EX;
+ }
+ return 0;
+}
+
+enum mu_iconv_fallback_mode mu_default_fallback_mode = mu_fallback_copy_octal;
+
+int
+mu_set_default_fallback (const char *str)
+{
+ if (strcmp (str, "none") == 0)
+ mu_default_fallback_mode = mu_fallback_none;
+ else if (strcmp (str, "copy-pass") == 0)
+ mu_default_fallback_mode = mu_fallback_copy_pass;
+ else if (strcmp (str, "copy-octal") == 0)
+ mu_default_fallback_mode = mu_fallback_copy_octal;
+ else
+ return EINVAL;
+ return 0;
+}
+
+int
+mu_decode_filter (mu_stream_t *pfilter, mu_stream_t input,
+ const char *filter_type,
+ const char *fromcode, const char *tocode)
+{
+ mu_str