/* bookmark.c
*
* Copyright (c) 1992-2005 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 int) 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. */
}
(void) chmod(pathName, 00600);
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);
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, (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++ = 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++ = 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);
(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);
}
(void) chmod(pathName, 00600);
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 */
void
DisposeBookmarkTable(void)
{
if (gBookmarkTable != NULL) {
free(gBookmarkTable);
gBookmarkTable = NULL;
}
} /* DisposeBookmarkTable */
syntax highlighted by Code2HTML, v. 0.9.1