/* bookmark.c 
 *
 * Copyright (c) 1992-2016 by Mike Gleason.
 * All rights reserved.
 * 
 */

#include "syshdrs.h"
#ifdef PRAGMA_HDRSTOP
#	pragma hdrstop
#endif

#include "bookmark.h"
#include "util.h"

/*
 * The ~/.ncftp/bookmarks file contains a list of sites
 * the user wants to remember.
 *
 * Unlike previous versions of the program, we now open/close
 * the file every time we need it;  That way we can have
 * multiple ncftp processes changing the file.  There is still
 * a possibility that two different processes could be modifying
 * the file at the same time.
 */

Bookmark gBm;
int gLoadedBm = 0;
int gBookmarkMatchMode = 0;
int gNumBookmarks = 0;
BookmarkPtr gBookmarkTable = NULL;

extern char gOurDirectoryPath[];

/* Converts a pre-loaded Bookmark structure into a RFC 1738
 * Uniform Resource Locator.
 */
void
BookmarkToURL(BookmarkPtr bmp, char *url, size_t urlsize)
{
	char pbuf[32];

	/* //<user>:<password>@<host>:<port>/<url-path> */
	/* Note that if an absolute path is given,
	 * you need to escape the first entry, i.e. /pub -> %2Fpub
	 */
	(void) Strncpy(url, "ftp://", urlsize);
	if (bmp->user[0] != '\0') {
		(void) Strncat(url, bmp->user, urlsize);
		if (bmp->pass[0] != '\0') {
			(void) Strncat(url, ":", urlsize);
			(void) Strncat(url, "PASSWORD", urlsize);
		}
		(void) Strncat(url, "@", urlsize);
	}
	(void) Strncat(url, bmp->name, urlsize);
	if (bmp->port != 21) {
		(void) sprintf(pbuf, ":%u", (unsigned int) bmp->port);
		(void) Strncat(url, pbuf, urlsize);
	}
	if (bmp->dir[0] == '/') {
		/* Absolute URL path, must escape first slash. */
		(void) Strncat(url, "/%2F", urlsize);
		(void) Strncat(url, bmp->dir + 1, urlsize);
		(void) Strncat(url, "/", urlsize);
	} else if (bmp->dir[0] != '\0') {
		(void) Strncat(url, "/", urlsize);
		(void) Strncat(url, bmp->dir, urlsize);
		(void) Strncat(url, "/", urlsize);
	}
}	/* BookmarkToURL */




void
SetBookmarkDefaults(BookmarkPtr bmp)
{
	(void) memset(bmp, 0, sizeof(Bookmark));

	bmp->xferType = 'I';
	bmp->xferMode = 'S';	/* Use FTP protocol default as ours too. */
	bmp->hasSIZE = kCommandAvailabilityUnknown;
	bmp->hasMDTM = kCommandAvailabilityUnknown;
	bmp->hasUTIME = kCommandAvailabilityUnknown;
	bmp->hasPASV = kCommandAvailabilityUnknown;
	bmp->isUnix = 1;
	bmp->lastCall = (time_t) 0;
	bmp->deleted = 0;
}	/* SetBookmarkDefaults */




/* Used when converting hex strings to integral types. */
static int
HexCharToNibble(int c)
{
	switch (c) {
		case '0':
		case '1':
		case '2':
		case '3':
		case '4':
		case '5':
		case '6':
		case '7':
		case '8':
		case '9':
			return (c - '0');
		case 'a':
		case 'b':
		case 'c':
		case 'd':
		case 'e':
		case 'f':
			return (c - 'a' + 10);
		case 'A':
		case 'B':
		case 'C':
		case 'D':
		case 'E':
		case 'F':
			return (c - 'A' + 10);

	}
	return (-1);	/* Error. */
}	/* HexCharToNibble */





/* Fills in a Bookmark structure based off of a line from the NcFTP
 * "bookmarks" file.
 */
int
ParseHostLine(char *line, BookmarkPtr bmp)
{
	char token[128];
	char pass[128];
	char *s, *d;
	char *tokenend;
	long L;
	int i;
	int result;
	int n, n1, n2;

	SetBookmarkDefaults(bmp);
	s = line;
	tokenend = token;
	tokenend += sizeof(token);
	--tokenend;
	result = -1;
	for (i=1; ; i++) {
		if (*s == '\0')
			break;
		/* Some tokens may need to have a comma in them.  Since this is a
		 * field delimiter, these fields use \, to represent a comma, and
		 * \\ for a backslash.  This chunk gets the next token, paying
		 * attention to the escaped stuff.
		 */
		for (d = token; *s != '\0'; ) {
			if ((*s == '\\') && (s[1] != '\0')) {
				if (d < tokenend)
					*d++ = s[1];
				s += 2;
			} else if (*s == ',') {
				++s;
				break;
			} else if ((*s == '$') && (s[1] != '\0') && (s[2] != '\0')) {
				n1 = HexCharToNibble(s[1]);
				n2 = HexCharToNibble(s[2]);
				if ((n1 >= 0) && (n2 >= 0)) {
					n = (n1 << 4) | n2;
					if (d < tokenend)
						*(unsigned char *)d++ = (unsigned char) n;
				}
				s += 3;
			} else {
				if (d < tokenend)
					*d++ = *s;
				++s;
			}
		}
		*d = '\0';
		switch(i) {
			case 1: (void) STRNCPY(bmp->bookmarkName, token); break;
			case 2: (void) STRNCPY(bmp->name, token); break;
			case 3: (void) STRNCPY(bmp->user, token); break;
			case 4: (void) STRNCPY(bmp->pass, token); break;
			case 5: (void) STRNCPY(bmp->acct, token); break;
			case 6: (void) STRNCPY(bmp->dir, token);
					result = 0;		/* Good enough to have these fields. */
					break;
			case 7:
				if (token[0] != '\0')
					bmp->xferType = (int) token[0];
				break;
			case 8:
				/* Most of the time, we won't have a port. */
				if (token[0] == '\0')
					bmp->port = (unsigned int) kDefaultFTPPort;
				else
					bmp->port = (unsigned int) atoi(token);
				break;
			case 9:
				(void) sscanf(token, "%lx", &L);
				bmp->lastCall = (time_t) L;
				break;
			case 10: bmp->hasSIZE = atoi(token); break;
			case 11: bmp->hasMDTM = atoi(token); break;
			case 12: bmp->hasPASV = atoi(token); break;
			case 13: bmp->isUnix = atoi(token);
					result = 3;		/* Version 3 had all fields to here. */
					break;
			case 14: (void) STRNCPY(bmp->lastIP, token); break;
			case 15: (void) STRNCPY(bmp->comment, token); break;
			case 16:
			case 17:
			case 18:
			case 19:
				break;
			case 20: bmp->xferMode = token[0];
					result = 7;		/* Version 7 has all fields to here. */
					break;
			case 21: bmp->hasUTIME = atoi(token);
					break;
			case 22: (void) STRNCPY(bmp->ldir, token);
					result = 8;		/* Version 8 has all fields to here. */
					break;
			default:
					result = 99;	/* Version >8 ? */
					goto done;
		}
	}
done:

	/* Decode password, if it was base-64 encoded. */
	if (strncmp(bmp->pass, kPasswordMagic, kPasswordMagicLen) == 0) {
		FromBase64(pass, bmp->pass + kPasswordMagicLen, strlen(bmp->pass + kPasswordMagicLen), 1);
		(void) STRNCPY(bmp->pass, pass);
	}
	return (result);
}	/* ParseHostLine */




void
CloseBookmarkFile(FILE *fp)
{
	if (fp != NULL)
		(void) fclose(fp);
}	/* CloseBookmarkFile */





int
GetNextBookmark(FILE *fp, Bookmark *bmp)
{
	char line[512];

	while (FGets(line, sizeof(line), fp) != NULL) {
		if (ParseHostLine(line, bmp) >= 0)
			return (0);
	}
	return (-1);
}	/* GetNextBookmark */




/* Opens a NcFTP 2.x or 3.x style bookmarks file, and sets the file pointer
 * so that it is ready to read the first data line.
 */
FILE *
OpenBookmarkFile(int *numBookmarks0)
{
	char pathName[256], path2[256];
	char line[256];
	FILE *fp;
	int version;
	int numBookmarks;
	Bookmark junkbm;

	if (gOurDirectoryPath[0] == '\0')
		return NULL;		/* Don't create in root directory. */
	(void) OurDirectoryPath(pathName, sizeof(pathName), kBookmarkFileName);
	fp = fopen(pathName, FOPEN_READ_TEXT);
	if (fp == NULL) {
		/* See if it exists under the old name. */
		(void) OurDirectoryPath(path2, sizeof(path2), kOldBookmarkFileName);
		if (rename(path2, pathName) == 0) {
			/* Rename succeeded, now open it. */
			fp = fopen(pathName, FOPEN_READ_TEXT);
			if (fp == NULL)
				return NULL;
		}
		return NULL;		/* Okay to not have one yet. */
	}
	
#if (defined(WIN32) || defined(_WINDOWS)) && !defined(__CYGWIN__)
#else
	(void) chmod(pathName, 00600);
#endif
	if (FGets(line, sizeof(line), fp) == NULL) {
		(void) fprintf(stderr, "%s: invalid format.\n", pathName);
		(void) fclose(fp);
		return NULL;
	}
	
	/* Sample line we're looking for:
	 * "NcFTP bookmark-file version: 8"
	 */
	version = -1;
	(void) sscanf(line, "%*s %*s %*s %d", &version);
	if (version < kBookmarkMinVersion) {
		if (version < 0) {
			(void) fprintf(stderr, "%s: invalid format, or bad version.\n", pathName);
			(void) fclose(fp);
			return NULL;
		}
		(void) STRNCPY(path2, pathName);
		(void) sprintf(line, ".v%d", version);
		(void) STRNCAT(path2, line);
		(void) rename(pathName, path2);
		(void) fprintf(stderr, "%s: old version.\n", pathName);
		(void) fclose(fp);
		return NULL;
	}

	/* Sample line we're looking for:
	 * "Number of entries: 28" or "# # # 1"
	 */
	numBookmarks = -1;
	
	/* At the moment, we can't trust the number stored in the
	 * file.  It's there for future use.
	 */
	if (FGets(line, sizeof(line), fp) == NULL) {
		(void) fprintf(stderr, "%s: invalid format.\n", pathName);
		(void) fclose(fp);
		return NULL;
	}

	if (numBookmarks0 == (int *) 0) {
		/* If the caller doesn't care how many bookmarks are *really*
		 * in the file, then we can return now.
		 */
		return(fp);
	}

	/* Otherwise, we have to read through the whole file because
	 * unfortunately the header line can't be trusted.
	 */
	for (numBookmarks = 0; ; numBookmarks++) {
		if (GetNextBookmark(fp, &junkbm) < 0)
			break;
	}

	/* Now we have to re-open and re-position the file.
	 * We don't use rewind() because it doesn't always work.
	 * This introduces a race condition, but the bookmark
	 * functionality wasn't designed to be air-tight.
	 */
	CloseBookmarkFile(fp);
	fp = fopen(pathName, FOPEN_READ_TEXT);
	if (fp == NULL)
		return (NULL);
	if (FGets(line, sizeof(line), fp) == NULL) {
		(void) fprintf(stderr, "%s: invalid format.\n", pathName);
		(void) fclose(fp);
		return NULL;
	}

	if (FGets(line, sizeof(line), fp) == NULL) {
		(void) fprintf(stderr, "%s: invalid format.\n", pathName);
		(void) fclose(fp);
		return NULL;
	}

	/* NOW we're done. */
	*numBookmarks0 = numBookmarks;
	return (fp);
}	/* OpenBookmarkFile */




/* Looks for a saved bookmark by the abbreviation given. */
int
GetBookmark(const char *const bmabbr, Bookmark *bmp)
{
	FILE *fp;
	char line[512];
	Bookmark byHostName;
	Bookmark byHostAbbr;
	Bookmark byBmAbbr;
	size_t byBmNameFlag = 0;
	size_t byBmAbbrFlag = 0;
	size_t byHostNameFlag = 0;
	size_t byHostAbbrFlag = 0;
	int result = -1;
	int exactMatch = 0;
	size_t bmabbrLen;
	char *cp;
	char bmabbrtrunc[sizeof(bmp->bookmarkName)];

	fp = OpenBookmarkFile(NULL);
	if (fp == NULL)
		return (-1);

	memset(&byHostName, 0, sizeof(Bookmark));
	memset(&byHostAbbr, 0, sizeof(Bookmark));
	memset(&byBmAbbr, 0, sizeof(Bookmark));

	STRNCPY(bmabbrtrunc, bmabbr);
	bmabbrLen = strlen(bmabbr);
	while (FGets(line, sizeof(line), fp) != NULL) {
		if (ParseHostLine(line, bmp) < 0)
			continue;
		if (ISTREQ(bmp->bookmarkName, bmabbrtrunc)) {
			/* Exact match, done. */
			byBmNameFlag = bmabbrLen;
			exactMatch = 1;
			break;
		} else if (ISTRNEQ(bmp->bookmarkName, bmabbr, bmabbrLen)) {
			/* Remember this one, it matched an abbreviated
			 * bookmark name.
			 */
			byBmAbbr = *bmp;
			byBmAbbrFlag = bmabbrLen;
		} else if (ISTREQ(bmp->name, bmabbr)) {
			/* Remember this one, it matched a full
			 * host name.
			 */
			byHostName = *bmp;
			byHostNameFlag = bmabbrLen;
		} else if ((cp = strchr(bmp->name, '.')) != NULL) {
			/* See if it matched part of the hostname. */
			if (ISTRNEQ(bmp->name, "ftp", 3)) {
				cp = cp + 1;
			} else if (ISTRNEQ(bmp->name, "www", 3)) {
				cp = cp + 1;
			} else {
				cp = bmp->name;
			}
			if (ISTRNEQ(cp, bmabbr, bmabbrLen)) {
				/* Remember this one, it matched a full
				 * host name.
				 */
				byHostAbbr = *bmp;
				byHostAbbrFlag = bmabbrLen;
			}
		}
	}

	if (gBookmarkMatchMode == 0) {
		/* Only use a bookmark when the exact
		 * bookmark name was used.
		 */
		if (exactMatch != 0) {
			result = 0;
		}
	} else {
		/* Pick the best match, if any. */
		if (byBmNameFlag != 0) {
			/* *bmp is already set. */
			result = 0;
		} else if (byBmAbbrFlag != 0) {
			result = 0;
			*bmp = byBmAbbr;
		} else if (byHostNameFlag != 0) {
			result = 0;
			*bmp = byHostName;
		} else if (byHostAbbrFlag != 0) {
			result = 0;
			*bmp = byHostAbbr;
		}
	}

	if (result != 0)
		memset(bmp, 0, sizeof(Bookmark));

	CloseBookmarkFile(fp);
	return (result);
}	/* GetBookmark */




static int
BookmarkSortProc(const void *a, const void *b)
{
	return (ISTRCMP((*(const Bookmark *)a).bookmarkName, (*(const Bookmark *)b).bookmarkName));	
}	/* BookmarkSortProc */



static int
BookmarkSearchProc(const void *key, const void *b)
{
	return (ISTRCMP((const char *) key, (*(const Bookmark *)b).bookmarkName));	
}	/* BookmarkSearchProc */



BookmarkPtr
SearchBookmarkTable(const char *key)
{
	return ((BookmarkPtr) bsearch(key, gBookmarkTable, (size_t) gNumBookmarks, sizeof(Bookmark), BookmarkSearchProc));
}	/* SearchBookmarkTable */




void
SortBookmarks(void)
{
	if ((gBookmarkTable == NULL) || (gNumBookmarks < 2))
		return;

	/* Sorting involves swapping entire Bookmark structures.
	 * Normally the proper thing to do is to use an array
	 * of pointers to Bookmarks and sort them, but even
	 * these days a large bookmark list can be sorted in
	 * the blink of an eye.
	 */
	qsort(gBookmarkTable, (size_t) gNumBookmarks, sizeof(Bookmark), BookmarkSortProc);
}	/* SortBookmarks */



int
LoadBookmarkTable(void)
{
	int i, nb;
	FILE *infp;

	infp = OpenBookmarkFile(&nb);
	if (infp == NULL) {
		nb = 0;
	}
	if ((nb != gNumBookmarks) && (gBookmarkTable != NULL)) {
		/* Re-loading the table from disk. */
		gBookmarkTable = (Bookmark *) realloc(gBookmarkTable, (size_t) (nb + 1) * sizeof(Bookmark));
		memset(gBookmarkTable, 0, (size_t) (nb + 1) * sizeof(Bookmark));
	} else {
		gBookmarkTable = calloc((size_t) (nb + 1), (size_t) sizeof(Bookmark));
	}

	if (gBookmarkTable == NULL) {
		CloseBookmarkFile(infp);
		return (-1);
	}

	for (i=0; i<nb; i++) {
		if (GetNextBookmark(infp, gBookmarkTable + i) < 0) {
			break;
		}
	}
	gNumBookmarks = i;

	CloseBookmarkFile(infp);
	SortBookmarks();
	return (0);
}	/* LoadBookmarkTable */




/* Some characters need to be escaped so the file is editable and can
 * be parsed correctly the next time it is read.
 */
static char *
BmEscapeTok(char *dst, size_t dsize, char *src)
{
	char *dlim = dst + dsize - 1;
	char *dst0 = dst;
	int c;

	while ((c = *src) != '\0') {
		src++;
		if ((c == '\\') || (c == ',') || (c == '$')) {
			/* These need to be escaped. */
			if ((dst + 1) < dlim) {
				*dst++ = '\\';
				*dst++ = (char) c;
			}
		} else if (!isprint(c)) {
			/* Escape non-printing characters. */
			if ((dst + 2) < dlim) {
				(void) sprintf(dst, "$%02x", c);
				dst += 3;
			}
		} else {
			if (dst < dlim)
				*dst++ = (char) c;
		}
	}
	*dst = '\0';
	return (dst0);
}	/* BmEscapeTok */




/* Converts a Bookmark into a text string, and writes it to the saved
 * bookmarks file.
 */
static int
WriteBmLine(Bookmark *bmp, FILE *outfp, int savePassword)
{
	char tok[256];
	char pass[160];

	if (fprintf(outfp, "%s", bmp->bookmarkName) < 0) return (-1) ;/*1*/
	if (fprintf(outfp, ",%s", BmEscapeTok(tok, sizeof(tok), bmp->name)) < 0) return (-1) ;/*2*/
	if (fprintf(outfp, ",%s", BmEscapeTok(tok, sizeof(tok), bmp->user)) < 0) return (-1) ;/*3*/
	if ((bmp->pass[0] != '\0') && (savePassword == 1)) {
		(void) memcpy(pass, kPasswordMagic, kPasswordMagicLen);
		ToBase64(pass + kPasswordMagicLen, bmp->pass, strlen(bmp->pass), 1);
		if (fprintf(outfp, ",%s", pass) < 0) return (-1) ;/*4*/
	} else {
		if (fprintf(outfp, ",%s", "") < 0) return (-1) ;/*4*/
	}
	if (fprintf(outfp, ",%s", BmEscapeTok(tok, sizeof(tok), bmp->acct)) < 0) return (-1) ;/*5*/
	if (fprintf(outfp, ",%s", BmEscapeTok(tok, sizeof(tok), bmp->dir)) < 0) return (-1) ;/*6*/
	if (fprintf(outfp, ",%c", bmp->xferType) < 0) return (-1) ;/*7*/
	if (fprintf(outfp, ",%u", (unsigned int) bmp->port) < 0) return (-1) ;/*8*/
	if (fprintf(outfp, ",%lu", (unsigned long) bmp->lastCall) < 0) return (-1) ;/*9*/
	if (fprintf(outfp, ",%d", bmp->hasSIZE) < 0) return (-1) ;/*10*/
	if (fprintf(outfp, ",%d", bmp->hasMDTM) < 0) return (-1) ;/*11*/
	if (fprintf(outfp, ",%d", bmp->hasPASV) < 0) return (-1) ;/*12*/
	if (fprintf(outfp, ",%d", bmp->isUnix) < 0) return (-1) ;/*13*/
	if (fprintf(outfp, ",%s", bmp->lastIP) < 0) return (-1) ;/*14*/
	if (fprintf(outfp, ",%s", BmEscapeTok(tok, sizeof(tok), bmp->comment)) < 0) return (-1) ;/*15*/
	if (fprintf(outfp, ",%s", "") < 0) return (-1) ;/*16*/
	if (fprintf(outfp, ",%s", "") < 0) return (-1) ;/*17*/
	if (fprintf(outfp, ",%s", "") < 0) return (-1) ;/*18*/
	if (fprintf(outfp, ",%s", "") < 0) return (-1) ;/*19*/
	if (fprintf(outfp, ",%c", bmp->xferMode) < 0) return (-1) ;/*20*/
	if (fprintf(outfp, ",%d", bmp->hasUTIME) < 0) return (-1) ;/*21*/
	if (fprintf(outfp, ",%s", BmEscapeTok(tok, sizeof(tok), bmp->ldir)) < 0) return (-1) ;/*22*/
	if (fprintf(outfp, "\n") < 0) return (-1) ;
	if (fflush(outfp) < 0) return (-1);
	return (0);
}	/* WriteBmLine */



static int
SwapBookmarkFiles(void)
{
	char pidStr[32];
	char pathName[256], path2[256];

	(void) OurDirectoryPath(path2, sizeof(path2), kBookmarkFileName);
	(void) OurDirectoryPath(pathName, sizeof(pathName), kTmpBookmarkFileName);
#if (defined(WIN32) || defined(_WINDOWS)) && !defined(__CYGWIN__) && !defined(getpid)
#	define getpid _getpid
#endif
	(void) sprintf(pidStr, "-%u.txt", (unsigned int) getpid());
	(void) STRNCAT(pathName, pidStr);

	(void) remove(path2);
	if (rename(pathName, path2) < 0) {
		return (-1);
	}
	return (0);
}	/* SwapBookmarkFiles */






/* Saves a Bookmark structure into the bookmarks file. */
FILE *
OpenTmpBookmarkFile(int nb)
{
	FILE *outfp;
	char pidStr[32];
	char pathName[256], path2[256];

	if (gOurDirectoryPath[0] == '\0')
		return (NULL);		/* Don't create in root directory. */

	(void) OurDirectoryPath(path2, sizeof(path2), kBookmarkFileName);
	(void) OurDirectoryPath(pathName, sizeof(pathName), kTmpBookmarkFileName);
	(void) sprintf(pidStr, "-%u.txt", (unsigned int) getpid());
	(void) STRNCAT(pathName, pidStr);

	outfp = fopen(pathName, FOPEN_WRITE_TEXT);
	if (outfp == NULL) {
		(void) fprintf(stderr, "Could not save bookmark.\n");
		perror(pathName);
		return (NULL);
	}
#if (defined(WIN32) || defined(_WINDOWS)) && !defined(__CYGWIN__)
#else
	(void) chmod(pathName, 00600);
#endif
	if (nb > 0) {
		if (fprintf(outfp, "NcFTP bookmark-file version: %d\nNumber of bookmarks: %d\n", kBookmarkVersion, nb) < 0) {
			(void) fprintf(stderr, "Could not save bookmark.\n");
			perror(pathName);
			(void) fclose(outfp);
			return (NULL);
		}
	} else {
		if (fprintf(outfp, "NcFTP bookmark-file version: %d\nNumber of bookmarks: ??\n", kBookmarkVersion) < 0) {
			(void) fprintf(stderr, "Could not save bookmark.\n");
			perror(pathName);
			(void) fclose(outfp);
			return (NULL);
		}
	}

	return (outfp);
}	/* OpenTmpBookmarkFile */




int
SaveBookmarkTable(void)
{
	int i;
	FILE *outfp;
	int nb;

	if ((gNumBookmarks < 1) || (gBookmarkTable == NULL))
		return (0);	/* Nothing to save. */

	/* Get a count of live bookmarks. */
	for (i=0, nb=0; i<gNumBookmarks; i++) {
		if (gBookmarkTable[i].deleted == 0)
			nb++;
	}
	outfp = OpenTmpBookmarkFile(nb);
	if (outfp == NULL) {
		return (-1);
	}

	for (i=0; i<gNumBookmarks; i++) {
		if (gBookmarkTable[i].deleted == 0) {
			if (WriteBmLine(gBookmarkTable + i, outfp, 1) < 0) {
				CloseBookmarkFile(outfp);
				return (-1);
			}
		}
	}
	CloseBookmarkFile(outfp);
	if (SwapBookmarkFiles() < 0) {
		return (-1);
	}
	return (0);
}	/* SaveBookmarkTable */



/* Saves a Bookmark structure into the bookmarks file. */
int
PutBookmark(Bookmark *bmp, int savePassword)
{
	FILE *infp, *outfp;
	char line[256];
	char bmAbbr[64];
	int replaced = 0;
	size_t len;

	outfp = OpenTmpBookmarkFile(0);
	if (outfp == NULL)
		return (-1);

	(void) STRNCPY(bmAbbr, bmp->bookmarkName);
	(void) STRNCAT(bmAbbr, ",");
	len = strlen(bmAbbr);

	/* This may fail the first time we ever save a bookmark. */
	infp = OpenBookmarkFile(NULL);
	if (infp != NULL) {
		while (FGets(line, sizeof(line), infp) != NULL) {
			if (strncmp(line, bmAbbr, len) == 0) {
				/* Replace previous entry. */
				if (WriteBmLine(bmp, outfp, savePassword) < 0) {
					(void) fprintf(stderr, "Could not save bookmark.\n");
					perror("reason");
					(void) fclose(outfp);
				}
				replaced = 1;
			} else {
				if (fprintf(outfp, "%s\n", line) < 0) {
					(void) fprintf(stderr, "Could not save bookmark.\n");
					perror("reason");
					(void) fclose(outfp);
					return (-1);
				}
			}
		}
		CloseBookmarkFile(infp);
	}

	if (replaced == 0) {
		/* Add it as a new bookmark. */
		if (WriteBmLine(bmp, outfp, savePassword) < 0) {
			(void) fprintf(stderr, "Could not save bookmark.\n");
			perror("reason");
			(void) fclose(outfp);
			return (-1);
		}
	}

	if (fclose(outfp) < 0) {
		(void) fprintf(stderr, "Could not save bookmark.\n");
		perror("reason");
		return (-1);
	}

	if (SwapBookmarkFiles() < 0) {
		(void) fprintf(stderr, "Could not rename bookmark file.\n");
		perror("reason");
		return (-1);
	}
	return (0);
}	/* PutBookmark */




/* Tries to generate a bookmark abbreviation based off of the hostname. */
void
DefaultBookmarkName(char *dst, size_t siz, char *src)
{
	char str[128];
	const char *token;
	const char *cp;

	(void) STRNCPY(str, src);
	
	/* Pick the first "significant" part of the hostname.  Usually
	 * this is the first word in the name, but if it's something like
	 * ftp.unl.edu, we would want to choose "unl" and not "ftp."
	 */
	token = str;
	if ((token = strtok(str, ".")) == NULL)
		token = str;
	else if ((ISTRNEQ(token, "ftp", 3)) || (ISTRNEQ(token, "www", 3))) {
		if ((token = strtok(NULL, ".")) == NULL)
			token = "";
	}
	for (cp = token; ; cp++) {
		if (*cp == '\0') {
			/* Token was all digits, like an IP address perhaps. */
			token = "";
		}
		if (!isdigit((int) *cp))
			break;
	}
	(void) Strncpy(dst, token, siz);
}	/* DefaultBookmarkName */




int
AddNewItemToBookmarkTable(void)
{
	int nb;
	BookmarkPtr newTable, bmp;

	if (gBookmarkTable == NULL)
		return (-1);

	nb = gNumBookmarks + 1;
	
	newTable = (BookmarkPtr) realloc(gBookmarkTable, (size_t) (nb) * sizeof(Bookmark));
	if (newTable == NULL)
		return (-1);

	gBookmarkTable = newTable;
	gNumBookmarks = nb;

	bmp = &newTable[nb - 1];
	SetBookmarkDefaults(bmp);

	return (nb - 1);
}	/* AddNewItemToBookmarkTable */




void
DisposeBookmarkTable(void)
{
	if (gBookmarkTable != NULL) {
		free(gBookmarkTable);
		gBookmarkTable = NULL;
	}
}	/* DisposeBookmarkTable */
