/*
** path.c -- return or convert paths
**
** This code is Copyright (c) 2002, by the authors of nmh.  See the
** COPYRIGHT file in the root directory of the nmh distribution for
** complete copyright information.
*/

#include <sysexits.h>
#include <h/mh.h>
#include <pwd.h>
#include <unistd.h>


/*
** Find the location of a format or configuration
** file, and return its absolute pathname.
**
** 1) If it begins with ~user, then expand it.
** 2) Next, if already absolute pathname, then leave unchanged.
** 3) Next, check in mmh directory.
** 4) Next, check in mmh `etc' directory.
** 5) As fall-back, return `file' unchanged.
*/
char *
etcpath(char *file)
{
	static char epath[PATH_MAX];
	char *cp;
	char *pp;
	struct passwd *pw;

	/* XXX: here was: ``context_read();'' -- why? */
	if (*file == '~') {
		/* Expand `~user' */
		if ((cp = strchr(pp = file + 1, '/')))
			*cp++ = '\0';
		if (*pp == '\0') {
			pp = mypath;
		} else {
			if ((pw = getpwnam(pp)))
				pp = pw->pw_dir;
			else {
				if (cp)
					*--cp = '/';
				goto try_it;
			}
		}

		snprintf(epath, sizeof epath, "%s/%s", pp, cp ? cp : "");
		if (cp)
			*--cp = '/';

		if (access(epath, R_OK) != NOTOK)
			return epath;  /* else fall */
	}

try_it:
	if (*file == '/') {
		/* absolute pathname, return it */
		return file;
	}

	/* Check mmh directory */
	snprintf(epath, sizeof epath, "%s/%s", mmhpath, file);
	if (access(epath, R_OK) != NOTOK)
		return epath;

	/* Check nmh `etc' directory */
	snprintf(epath, sizeof epath, "%s/%s", mhetcdir, file);
	if  (access(epath, R_OK) != NOTOK)
		return epath;

	/* The fall-back */
	return file;
}


/*
**  Compactify a path name by removing unnecessary parts.
**  Removes trailing slashes. Cares to never remove all characters.
**  Modifies f (never enlarges it).
**
**  FIXME: Cannot use strcpy() as the areas overlap!
*/
static void
packpath(char *f)
{
	char *cp, *dp;
	int abspath;

	if (!f || !*f) {
		return;
	}
	abspath = (*f == '/');

	for (cp=f; *cp; ) {
		if (*cp != '/') {
			/* Skip. Interesting places are only after slashes. */
			/* We don't care about "./" beginnings */
			cp++;
			continue;
		}

		/* Let's see what follows the slash ... */
		switch (*++cp) {
		case '\0':
			*--cp = '\0';
			continue;  /* ... and thus exit the loop */

		case '/':
			/* reduce subsequent slashes to one */
			for (dp = cp; *dp == '/'; dp++) {
				continue;
			}
			strcpy(cp, dp);
			cp--;
			continue;  /* ... at the slash */

		case '.':
			if (cp[1] == '/' || cp[1] == '\0') {
				/* one-dot element */
				strcpy(cp-1, cp+1);
				cp--;
				continue;
			} else if ((strncmp(cp, "../", 3) == 0) ||
					(strcmp(cp, "..") == 0)) {
				/* dot-dot element */
				/* crop out previous path element */
				for (dp=cp-2; dp>f && *dp!='/'; dp--) {
					continue;
				}
				if (dp < f) {
					/* path starts with "/.." */
					dp = f;
				}
				strcpy(dp, cp+2);
				cp = dp;
				continue;
			} else {
				/* a normal hidden file */
				cp++;
				continue;
			}

		default:
			/* nothing special */
			cp++;
			continue;
		}
	}

	if (!strlen(f)) {
		/* We have removed everything, but need something. */
		strcpy(f, abspath ? "/" : ".");
	}
}




/*
**  Get the default folder
**  Return the Inbox profile entry or, as fallback, the compile time default
**  Returns a pointer to the abs folpath
*/
char *
getdeffol(void)
{
	char *folder = context_find(inbox);

	if (!folder || !*folder) {
		folder = defaultfolder;  /* the compile time default */
	}
	if (*folder == '+') {
		folder++;
	}
	return folder;
}


/*
**  Get the current folder
**  Return the Current-Folder context entry or, as fallback, the default folder
**  Returns a pointer to the abs folpath
**
**  Equivalent to: expandfol("@")
*/
char *
getcurfol(void)
{
	char *folder = context_find(curfolder);

	if (!folder || !*folder) {
		folder = getdeffol();
	}
	return folder;
}


/*
**  Expand folder path
**  Convert rel folpaths (@) into abs folpaths
**  dir paths are simply passed through
**  Returns the abs folpath (without prefix), in static mem
**
**  TODO: Always copy into the static buffer, or just return the pointer?
*/
char *
expandfol(char *f)
{
	static char buf[BUFSIZ];

	if (*f == '@') {
		/* f = concat(getcurfol(), "/", f+1, NULL); */
		snprintf(buf, sizeof buf, "%s/%s", getcurfol(), f+1);

	} else if (*f == '+') {
		strcpy(buf, f+1);

	} else {
		strcpy(buf, f);
	}
	packpath(buf);
	return buf;
}


/*
**  Expand directory path
**  Convert rel dirpath into abs dirpath
**  The argument is assumed to be a dir path relative to the cwd,
**  except when beginning with '/' (then it will be passed through).
**  Returns the abs dirpath, in static mem
**
**  TODO: Always copy into the static buffer, or just return the pointer?
*/
char *
expanddir(char *d)
{
	static char buf[BUFSIZ];
	int len;

	if (*d == '/') {
		strcpy(buf, d);
	} else {
		getcwd(buf, sizeof buf);
		len = strlen(buf);
		snprintf(buf+len, sizeof buf - len, "/%s", d);
	}
	packpath(buf);
	return buf;
}


/*
**  Anypath to absolute directory path
**  Convert any kind of path into an abs dirpath
**  A path without distinguishing prefix is assumed to be an abs folpath
**  Abs dirpaths are passed unchanged
**  Rel dirpaths ('.') get prefixed with the (abs) cwd
**  Return pointer to static memory
**
**  To get the dir path of the mail storage root, call: toabsdir("+")
**
**  TODO: check lengths for copies
*/
char *
toabsdir(char *path)
{
	static char buf[BUFSIZ];

	if (*path == '/') {
		/* nothing to do */
		strncpy(buf, path, sizeof buf);
		packpath(buf);
		return buf;

	} else if (*path == '.') {
		/* rel dir path */
		strncpy(buf, expanddir(path), sizeof buf);
		return buf;

	} else {
		/* folder path */
		char *cp=buf, *pp;

		if (!(pp = context_find("path")) || !*pp) {
			adios(EX_CONFIG, NULL, "Non-empty profile entry `Path' required");
		}
		if (*pp != '/') {
			/* Path is relative to $HOME */
			snprintf(buf, sizeof buf, "%s/", mypath);
			cp += strlen(buf);
		}
		strcpy(cp, pp);
		packpath(buf);
		/* append the mail folder */
		cp = buf + strlen(buf);
		*cp++ = '/';
		strcpy(cp, expandfol(path));
		return buf;
	}
}
