+/*
+ * 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 details.
+ *
+ * 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.
+ *
+ * Copyright © 2006 Pierre Habouzit
+ */
+/*
+ * Copyright notice from original mutt:
+ * Copyright (C) 1996-2000 Michael R. Elkins <me@mutt.org>
+ * Copyright (C) 1999-2000 Thomas Roessler <roessler@does-not-exist.org>
+ *
+ * This file is part of mutt-ng, see http://www.muttng.org/.
+ * It's licensed under the GNU General Public License,
+ * please see the file GPL in the top level source directory.
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <unistd.h>
+#include <limits.h>
+
+#include "macros.h"
+#include "mem.h"
+#include "str.h"
+#include "file.h"
+
+#include "../lib/debug.h"
+#include "../lib/str.h"
+
+#ifndef O_NOFOLLOW
+# define O_NOFOLLOW 0
+#endif
+
+/* FIXME: ugly to the max */
+extern short Umask;
+
+static int compare_stat(struct stat *osb, struct stat *nsb)
+{
+ if (osb->st_dev != nsb->st_dev || osb->st_ino != nsb->st_ino
+ || osb->st_rdev != nsb->st_rdev)
+ {
+ return -1;
+ }
+
+ return 0;
+}
+
+/****************************************************************************/
+/* fd ops */
+/****************************************************************************/
+
+int safe_open(const char *path, int flags)
+{
+ struct stat osb, nsb;
+ int fd;
+
+ umask(Umask);
+
+ if ((fd = open(path, flags, 0666)) < 0)
+ return fd;
+
+ /* make sure the file is not symlink */
+ if (lstat (path, &osb) < 0 || fstat(fd, &nsb) < 0
+ || compare_stat(&osb, &nsb) == -1)
+ {
+ debug_print(1, ("%s is a symlink!\n", path));
+ close(fd);
+ return -1;
+ }
+
+ return (fd);
+}
+
+
+int safe_symlink(const char *oldpath, const char *newpath)
+{
+ struct stat osb, nsb;
+
+ if (!oldpath || !newpath)
+ return -1;
+
+ if (unlink(newpath) < 0 && errno != ENOENT)
+ return -1;
+
+ if (oldpath[0] == '/') {
+ if (symlink(oldpath, newpath) < 0)
+ return -1;
+ } else {
+ char abs_oldpath[_POSIX_PATH_MAX];
+ ssize_t len;
+
+ if (!getcwd(abs_oldpath, sizeof(abs_oldpath)))
+ return -1;
+
+ len = m_strcat(abs_oldpath, sizeof(abs_oldpath), "/");
+ if (len >= sizeof(abs_oldpath))
+ return -1;
+
+ len += m_strcpy(abs_oldpath + len, sizeof(abs_oldpath) - len, oldpath);
+ if (len >= sizeof(abs_oldpath))
+ return -1;
+
+ if (symlink (abs_oldpath, newpath) < 0)
+ return -1;
+ }
+
+ if (stat(oldpath, &osb) < 0
+ || stat(newpath, &nsb) < 0
+ || compare_stat(&osb, &nsb) < 0)
+ {
+ unlink (newpath);
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
+ * This function is supposed to do nfs-safe renaming of files.
+ *
+ * Warning: We don't check whether src and target are equal.
+ */
+int safe_rename(const char *src, const char *target)
+{
+ struct stat ssb, tsb;
+
+ if (!src || !target)
+ return -1;
+
+ if (link(src, target) != 0) {
+ /*
+ * Coda does not allow cross-directory links, but tells
+ * us it's a cross-filesystem linking attempt.
+ *
+ * However, the Coda rename call is allegedly safe to use.
+ *
+ * With other file systems, rename should just fail when
+ * the files reside on different file systems, so it's safe
+ * to try it here.
+ *
+ */
+
+ if (errno == EXDEV)
+ return rename(src, target);
+
+ return -1;
+ }
+
+ /* Stat both links and check if they are equal. */
+
+ if (stat(src, &ssb) < 0 || stat(target, &tsb) < 0) {
+ return -1;
+ }
+
+ /*
+ * pretend that the link failed because the target file
+ * did already exist.
+ */
+
+ if (compare_stat(&ssb, &tsb) == -1) {
+ errno = EEXIST;
+ return -1;
+ }
+
+ /*
+ * Unlink the original link. Should we really ignore the return
+ * value here? XXX
+ */
+
+ unlink(src);
+ return 0;
+}
+
+void mutt_unlink(const char *s)
+{
+ struct stat sb, sb2;
+
+ if (lstat(s, &sb) == 0 && S_ISREG(sb.st_mode)) {
+ int fd;
+ FILE *f;
+
+ /* Defend against symlink attacks */
+
+ if ((fd = open(s, O_RDWR | O_NOFOLLOW)) < 0)
+ return;
+
+ if ((fstat(fd, &sb2) != 0) || !S_ISREG(sb2.st_mode)
+ || (sb.st_dev != sb2.st_dev) || (sb.st_ino != sb2.st_ino))
+ {
+ close (fd);
+ return;
+ }
+
+ if ((f = fdopen(fd, "r+"))) {
+ char buf[BUFSIZ];
+ unlink(s);
+
+ p_clear(buf, sizeof(buf));
+ while (sb.st_size > 0) {
+ fwrite(buf, 1, MIN(sizeof(buf), sb.st_size), f);
+ sb.st_size -= MIN(sizeof(buf), sb.st_size);
+ }
+ fclose (f);
+ }
+ }
+}
+
+
+/****************************************************************************/
+/* FILE* ops */
+/****************************************************************************/
+
+/* when opening files for writing, make sure the file doesn't already exist
+ to avoid race conditions. */
+FILE *safe_fopen(const char *path, const char *mode)
+{
+ /* first set the current umask */
+ umask(Umask);
+
+ if (mode[0] == 'w') {
+ int fd;
+ int flags = O_CREAT | O_EXCL | O_NOFOLLOW;
+
+ flags |= (mode[1] == '+' ? O_RDWR : O_WRONLY);
+
+ if ((fd = safe_open(path, flags)) < 0)
+ return (NULL);
+
+ return fdopen (fd, mode);
+ }
+
+ return fopen(path, mode);
+}
+
+int safe_fclose(FILE **f)
+{
+ int r = 0;
+
+ if (*f)
+ r = fclose (*f);
+ *f = NULL;
+ return r;
+}
+
+/* Read a line from ``fp'' into the dynamically allocated ``s'',
+ * increasing ``s'' if necessary. The ending "\n" or "\r\n" is removed.
+ * If a line ends with "\", this char and the linefeed is removed,
+ * and the next line is read too.
+ */
+char *mutt_read_line(char *s, size_t *size, FILE * fp, int *line)
+{
+ size_t offset = 0;
+ char *ch;
+
+ if (!s) {
+ s = p_new(char, STRING);
+ *size = STRING;
+ }
+
+ for (;;) {
+ if (fgets(s + offset, *size - offset, fp) == NULL) {
+ p_delete(&s);
+ return NULL;
+ }
+ if ((ch = strchr(s + offset, '\n')) != NULL) {
+ (*line)++;
+ *ch = 0;
+ if (ch > s && *(ch - 1) == '\r')
+ *--ch = 0;
+ if (ch == s || *(ch - 1) != '\\')
+ return s;
+ offset = ch - s - 1;
+ } else {
+ int c;
+
+ c = getc (fp); /* This is kind of a hack. We want to know if the
+ char at the current point in the input stream is EOF.
+ feof() will only tell us if we've already hit EOF, not
+ if the next character is EOF. So, we need to read in
+ the next character and manually check if it is EOF. */
+ if (c == EOF) {
+ /* The last line of fp isn't \n terminated */
+ (*line)++;
+ return s;
+ } else {
+ ungetc (c, fp); /* undo our dammage */
+ /* There wasn't room for the line -- increase ``s'' */
+ offset = *size - 1; /* overwrite the terminating 0 */
+ *size += STRING;
+ p_realloc(&s, *size);
+ }
+ }
+ }
+}
+
+int mutt_copy_stream(FILE *fin, FILE *fout)
+{
+ char buf[BUFSIZ];
+ ssize_t l;
+
+ while ((l = fread(buf, 1, sizeof (buf), fin)) > 0) {
+ if (fwrite(buf, 1, l, fout) != l)
+ return -1;
+ }
+
+ return 0;
+}
+
+int mutt_copy_bytes(FILE *in, FILE *out, size_t size)
+{
+ char buf[BUFSIZ];
+
+ while (size > 0) {
+ ssize_t chunk = MIN(size, sizeof(buf));
+
+ if ((chunk = fread(buf, 1, chunk, in)) < 1)
+ break;
+ if (fwrite(buf, 1, chunk, out) != chunk) {
+ debug_print(1, ("fwrite() returned short byte count\n"));
+ return -1;
+ }
+ size -= chunk;
+ }
+
+ return 0;
+}
+
+
+/****************************************************************************/
+/* ligben-like funcs */
+/****************************************************************************/
+
+const char *mutt_basename(const char *f)
+{
+ const char *p = strrchr(f, '/');
+ return p ? p + 1 : f;
+}
+
+
+char *
+mutt_concat_path(char *d, ssize_t n, const char *dir, const char *fname)
+{
+ const char *fmt = "%s/%s";
+
+ if (!*fname || (*dir && dir[m_strlen(dir) - 1] == '/'))
+ fmt = "%s%s";
+
+ snprintf(d, n, fmt, dir, fname);
+ return d;
+}
+
+
+void mutt_sanitize_filename(char *s, short slash)
+{
+ static char safe_chars[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+@{}._-:%/";
+
+ if (!s)
+ return;
+
+ for (; *s; s++) {
+ if ((slash && *s == '/') || !strchr (safe_chars, *s))
+ *s = '_';
+ }
+}
+
+/* prepare a file name to survive the shell's quoting rules.
+ * From the Unix programming FAQ by way of Liviu.
+ */
+
+/* FIXME: API is very wrong */
+ssize_t mutt_quote_filename(char *d, ssize_t l, const char *s)
+{
+ size_t i, j = 0;
+
+ if (!s) {
+ *d = '\0';
+ return 0;
+ }
+
+ /* leave some space for the trailing characters. */
+ l -= 6;
+
+ d[j++] = '\'';
+
+ for (i = 0; j < l && s[i]; i++) {
+ if (s[i] == '\'' || s[i] == '`') {
+ d[j++] = '\'';
+ d[j++] = '\\';
+ d[j++] = s[i];
+ d[j++] = '\'';
+ } else {
+ d[j++] = s[i];
+ }
+ }
+
+ d[j++] = '\'';
+ d[j] = '\0';
+
+ return j;
+}