/*
 * XOVER.C	- general nntp reader commands
 *
 *	Overview-related NNTP commands
 *
 * (c)Copyright 1998, Matthew Dillon, All Rights Reserved.  Refer to
 *    the COPYRIGHT file in the base directory of this distribution
 *    for specific rights granted.
 */

#include "defs.h"

Prototype void NNTPNewNews(Connection *conn, char **pptr);
Prototype void NNTPXHdr(Connection *conn, char **pptr);
Prototype void NNTPXOver(Connection *conn, char **pptr);
Prototype void NNTPXPat(Connection *conn, char **pptr);
Prototype void NNTPXPath(Connection *conn, char **pptr);

void NNListNewNews(Connection *conn);
void NNStartListOverview(Connection *conn, const char *hdr, const char *ran, const char *pat, int mode);
void NNStartListOverviewRange(Connection *conn);
void NNStartListOverviewMsgId(Connection *conn, const char *msgid);
void NNListOverviewRange(Connection *conn);
int OutputOverview(Connection *conn, const char *res, int resLen, int artSize);

Prototype char *OverViewFmt;

char *OverViewFmt = OVERVIEW_FMT;

void
NNTPNewNews(Connection *conn, char **pptr)
{
    char *newsgroups = NULL;
    const char *yymmdd = NULL;
    const char *hhmmss = NULL;
    const char *gmt = NULL;
    const char *distributions = NULL;
 
    if (!conn->co_Auth.dr_ReaderDef->rd_AllowNewnews) {
	MBLogPrintf(conn, &conn->co_TMBuf, "500 \"newnews\" not implemented\r\n");
	NNCommand(conn);
	return;
    }

    if (pptr) {
	newsgroups = parseword(pptr, " \t");
	yymmdd = parseword(pptr, " \t");
	hhmmss = parseword(pptr, " \t");
	gmt = parseword(pptr, " \t");
	distributions = parseword(pptr, " \t");
    }
    if (SetTimeRestrict(&conn->co_TimeRestrict, yymmdd, hhmmss, gmt) < 0) {
	MBLogPrintf(conn, &conn->co_TMBuf, "501 newsgroups yymmdd hhmmss [\"GMT\"] [<distributions>]\r\n");
	NNCommand(conn);
    } else {
	if (conn->co_Auth.dr_Flags & DF_GROUPLOG)
	    logit(LOG_INFO, "info %s%s%s%s%s newnews %s %s %s %s %s",
		*conn->co_Auth.dr_AuthUser ? conn->co_Auth.dr_AuthUser : "",
		*conn->co_Auth.dr_AuthUser ? "/" : "",
		*conn->co_Auth.dr_IdentUser ? conn->co_Auth.dr_IdentUser : "",  
		*conn->co_Auth.dr_IdentUser ? "@" : "",
		conn->co_Auth.dr_Host,
		newsgroups,
		yymmdd,
		hhmmss,
		gmt ? gmt : "-",
		distributions ? distributions : "-");

	MBLogPrintf(conn, &conn->co_TMBuf, "230 New news follows.\r\n");
	zfreeStr(&conn->co_MemPool, &conn->co_ListPat);
	zfreeStr(&conn->co_MemPool, &conn->co_ListHdrs);
	conn->co_ListPat = (distributions) ? zallocStr(&conn->co_MemPool, distributions) : NULL;
	conn->co_ListHdrs = zallocStr(&conn->co_MemPool, "Message-ID");
	conn->co_ArtMode = COM_NEWNEWS;
	conn->co_ListCacheGroups = NULL;
	ListActiveGroups(conn, newsgroups);
	NNListNewNews(conn);
    }
}

void
NNListNewNews(Connection *conn)
{
    conn->co_Func = NNListNewNews;
    conn->co_State = "listnewnews";

    if (conn->co_ListCacheGroups == NULL) {
	MBPrintf(&conn->co_TMBuf, ".\r\n");
	if (conn->co_GroupName)
	    zfreeStr(&conn->co_MemPool, &conn->co_GroupName);
	NNCommand(conn);
    } else {
	GroupList *gl = conn->co_ListCacheGroups;
	if (conn->co_GroupName)
	    zfreeStr(&conn->co_MemPool, &conn->co_GroupName);
	conn->co_GroupName = gl->group;
	conn->co_ListBegNo = 0;
	conn->co_ListEndNo = INT_MAX;
	conn->co_ListCacheGroups = gl->next;
	zfree(&conn->co_MemPool, gl, sizeof(GroupList));
	NNStartListOverviewRange(conn);
    }
}

void 
NNTPXHdr(Connection *conn, char **pptr)
{
    char *hdr;

    if ((hdr = parseword(pptr, " \t")) == NULL) {
	MBLogPrintf(conn, &conn->co_TMBuf, "501 header [range|MessageID]\r\n");
	NNCommand(conn);
	return;
    }
    NNStartListOverview(conn, hdr, parseword(pptr, " \t"), NULL, COM_XHDR);
    return;
}

void 
NNTPXOver(Connection *conn, char **pptr)
{
    NNStartListOverview(conn, OverViewFmt, parseword(pptr, " \t"), NULL, COM_XOVER);
    return;
}

void 
NNTPXPat(Connection *conn, char **pptr)
{
    char *hdr;
    char *ran;
    char *pat;

    if ((hdr = parseword(pptr, " \t")) == NULL ||
	(ran = parseword(pptr, " \t")) == NULL ||
	*(pat = *pptr) == '\0'
    ) {
	MBLogPrintf(conn, &conn->co_TMBuf, "501 header range|MessageID pat\r\n");
	NNCommand(conn);
	return;
    }
    NNStartListOverview(conn, hdr, ran, pat, COM_XPAT);
}

void 
NNTPXPath(Connection *conn, char **pptr)
{
    MBLogPrintf(conn, &conn->co_TMBuf, "500 What? (xpath not implemented yet)\r\n");
    NNCommand(conn);
}

/*
 * NNStartListOverview() - list selected overview for specified article range
 *			   or message-id
 */

void
NNStartListOverview(Connection *conn, const char *hdr, const char *ran, const char *pat, int mode)
{
    zfreeStr(&conn->co_MemPool, &conn->co_ListPat);
    zfreeStr(&conn->co_MemPool, &conn->co_ListHdrs);
    conn->co_ListPat = (pat) ? zallocStr(&conn->co_MemPool, pat) : NULL;
    conn->co_ListHdrs = zallocStr(&conn->co_MemPool, hdr);

    if (ran == NULL || ran[0] != '<') {
	if (conn->co_GroupName == NULL) {
	    MBLogPrintf(conn, &conn->co_TMBuf, "412 Not in a newsgroup\r\n");
	    NNCommand(conn);
	    return;
	}
    }

    conn->co_ArtMode = mode;

    switch(mode) {
    case COM_XPAT:
	MBLogPrintf(conn, &conn->co_TMBuf, "221 %s matches follow.\r\n", hdr);
	break;
    case COM_XHDR:
	MBLogPrintf(conn, &conn->co_TMBuf, "221 %s data follows\r\n", hdr);
	break;
    case COM_XOVER:
	MBLogPrintf(conn, &conn->co_TMBuf, "224 data follows\r\n");
	break;
    default:
	MBLogPrintf(conn, &conn->co_TMBuf, "500 software error\r\n");
	NNCommand(conn);
	return;
    }
    if (ran == NULL) {
	conn->co_ListBegNo = conn->co_ArtNo;
	conn->co_ListEndNo = conn->co_ArtNo;
	NNStartListOverviewRange(conn);
    } else if (ran[0] != '<') {
	char *p;
	conn->co_ListBegNo = strtol(ran, &p, 10);
	if (*p == '-') {
	    if (p[1] == 0)
		conn->co_ListEndNo = INT_MAX;
	    else
		conn->co_ListEndNo = strtol(p + 1, NULL, 10);
	} else {
	    conn->co_ListEndNo = conn->co_ListBegNo;
	}
	NNStartListOverviewRange(conn);
    } else if ((ran = MsgId(ran, NULL)) && strcmp(ran, "<>") != 0) {
	NNStartListOverviewMsgId(conn, ran);
    } else {
	MBPrintf(&conn->co_TMBuf, ".\r\n");
	NNCommand(conn);
    }
}

void
NNStartListOverviewRange(Connection *conn)
{
    /*
     * trim list range
     */
    const char *rec;
    int recLen;
    int lbeg;
    int lend;

    /*
     * Handle user bogosity
     */

    if (conn->co_ListBegNo < 0)
	conn->co_ListBegNo = 0;
    if (conn->co_ListEndNo < 0)
	conn->co_ListEndNo = 0;

    if ((rec = KPDBReadRecord(KDBActive, conn->co_GroupName, KP_LOCK, &recLen)) == NULL) {
	MBPrintf(&conn->co_TMBuf, ".\r\n");
	NNCommand(conn);
	return;
    }
    lbeg = strtol(KPDBGetField(rec, recLen, "NB", NULL, "0"), NULL, 10);
    lend = strtol(KPDBGetField(rec, recLen, "NE", NULL, "0"), NULL, 10);
    KPDBUnlock(KDBActive, rec);

    /*
     * Handle some stupid case begin and end numbers
     */
    if ((conn->co_ListBegNo < lbeg && conn->co_ListEndNo < lbeg) ||
		conn->co_ListBegNo > lend ||
		conn->co_ListEndNo < lbeg ||
		conn->co_ListBegNo > conn->co_ListEndNo) {
	MBPrintf(&conn->co_TMBuf, ".\r\n");
	NNCommand(conn);
	return;
    }
    if (conn->co_ListBegNo < lbeg)
	conn->co_ListBegNo = lbeg;
    if (conn->co_ListEndNo > lend)
	conn->co_ListEndNo = lend;
    NNListOverviewRange(conn);
}

void
NNListOverviewRange(Connection *conn)
{
    OverInfo *ov;
    int xpat_count = 0;

    conn->co_Func = NNListOverviewRange;
    conn->co_State = "listover";

    if ((ov = GetOverInfo(conn->co_GroupName)) == NULL) {
	MBPrintf(&conn->co_TMBuf, ".\r\n");
	NNCommand(conn);
	return;
    }
    if (conn->co_TMBuf.mh_WError) {
	logit(LOG_ERR, "%s mh_WError (co_ArtMode = %d)",
		conn->co_Auth.dr_Host, conn->co_ArtMode);
	PutOverInfo(ov);
	NNCommand(conn);
	return;
    }
    while (conn->co_TMBuf.mh_Bytes < 800 && 
	    conn->co_ListBegNo <= conn->co_ListEndNo
    ) {
	int resLen;
	int artSize;
	const char *res;
	TimeRestrict *tr = NULL;

	if (conn->co_ArtMode == COM_XPAT && ++xpat_count > 50) {
	    FD_SET(conn->co_Desc->d_Fd, &WFds);
	    break;
	}
	if (conn->co_ArtMode == COM_NEWNEWS)
	    tr = &conn->co_TimeRestrict;
	if ((res = GetOverRecord(ov, conn->co_ListBegNo, &resLen, &artSize, tr, NULL)) != NULL) {
	    OutputOverview(conn, res, resLen, artSize);
	}
	++conn->co_ListBegNo;
    }
    PutOverInfo(ov);
    if (conn->co_ListBegNo > conn->co_ListEndNo) {
	if (conn->co_ArtMode != COM_NEWNEWS)
	    MBPrintf(&conn->co_TMBuf, ".\r\n");
	NNCommand(conn);
	if (conn->co_ArtMode == COM_NEWNEWS) {
	    NNListNewNews(conn);
	}
    }
}

void
NNStartListOverviewMsgId(Connection *conn, const char *msgid)
{
    /* XXX StartListOverviewMsgId */
    MBPrintf(&conn->co_TMBuf, ".\r\n");
    NNCommand(conn);
}

int
OutputOverview(Connection *conn, const char *res, int resLen, int artSize)
{
    const char *hdr = conn->co_ListHdrs;
    int didIndex = 0;

    while (*hdr) {
	int hlen;
	int hhlen;
	int rleft;
	int printHdr = 0;
	const char *rline;
	char hch;
	char ch;

	/*
	 * locate next header
	 */

	while (*hdr == '\r' || *hdr == '\n')
	    ++hdr;
	for (hlen = 0; hdr[hlen]; ++hlen) {
	    if (hdr[hlen] != '\n')
		continue;
	    if (hdr[hlen+1] == ' ' || hdr[hlen+1] == '\t')	/* multiline */
		continue;
	    ++hlen;
	    break;
	}
	if (hlen == 0)
	    break;

	/*
	 * locate header name non-inclusive of colon
	 */

	for (hhlen = 0; hhlen < hlen && hdr[hhlen] != ':'; ++hhlen)
	    ;

	/*
	 * scan overview info for header
	 */
	rline = res;
	rleft = resLen;

	hch = tolower(*hdr);

	while (rleft) {
	    int rlen;
	    int rrlen;

	    /*
	     * get entire header
	     */

	    for (rlen = 0; rline[rlen]; ++rlen) {
		if (rline[rlen] != '\n')
		    continue;
		if (rline[rlen+1] == ' ' || rline[rlen+1] == '\t')/* multiln */
		    continue;
		++rlen;
		break;
	    }

	    /*
	     * no more headers, don't scan article (if article data passed)
	     */
	    if (rlen == 2 && rline[0] == '\r' && rline[1] == '\n')
		break;

	    /*
	     * This occurs if the map file containing the headers is boshed
	     */

	    if (rlen == 0)
		break;

	    /*
	     * get just header portion, non inclusive of colon
	     */

	    for (rrlen = 0; rrlen < rlen && rline[rrlen] != ':'; ++rrlen)
		;

	    ch = tolower(*rline);
	    if (rrlen == hhlen &&
			ch == hch && strncasecmp(rline, hdr, hhlen) == 0) {
		switch(conn->co_ArtMode) {
		case COM_XPAT:
		    {
			char hdr[128];
			char *p = hdr;
			int glen = rrlen;

			if (glen < rlen && rline[glen] == ':')
			    ++glen;
			while (glen < rlen && 
			    (rline[glen] == ' ' || rline[glen] == '\t')
			) {
			    ++glen;
			}

			if (rlen >= sizeof(hdr))
			    p = zalloc(&conn->co_MemPool, rlen + 1);
			memcpy(p, rline, rlen);
			/* Handle CR/LF in overview data */
			if (rlen > 2 && p[rlen - 2] == '\r' && p[rlen - 1] == '\n') {
			    p[rlen - 2] = 0;
			}
			p[rlen] = 0;
			if (wildmat(p + glen, conn->co_ListPat))
			    printHdr = ' ';
			if (p != hdr)
			    zfree(&conn->co_MemPool, p, rlen + 1);
		    }
		    break;
		case COM_XHDR:
		    printHdr =  ' ';
		    break;
		case COM_NEWNEWS:
		    printHdr = ' ';
		    didIndex = 1;
		    break;
		case COM_XOVER:
		    printHdr = '\t';
		    break;
		}
		if (printHdr) {
		    int b;
		    int i;
		    int doSpace = 0;

		    if (didIndex == 0) {
			MBPrintf(&conn->co_TMBuf, "%d", conn->co_ListBegNo);
			didIndex = 1;
		    }
		    if (conn->co_ArtMode != COM_NEWNEWS)
			MBPrintf(&conn->co_TMBuf, "%c", printHdr);

		    /*
		     * hack for overview format options, only deal with
		     * 'full' at the moment.
		     */

		    if (hhlen < hlen && strncmp(hdr + hhlen + 1, "full", 4) == 0) {
			MBWrite(&conn->co_TMBuf, hdr, hhlen + 1);
			MBWrite(&conn->co_TMBuf, " ", 1);
		    }

		    /*
		     * Compress whitespace if necessary. Mainly applies
		     * to folded lines.
		     */
		    for (b = i = rrlen + 1; i < rlen; ++i) {
			if (rline[i] == '\r' || rline[i] == '\n' ||
			    rline[i] == ' ' || rline[i] == '\t'
			) {
			    /*
			     * Skip first whitespace
			     */
			    if (b == i) {
				++b;
			    } else {
				/*
				 * Don't compress spaces
				 * We will compress space for OVER when it
				 * gets implemented
				 */
				if (rline[i] == ' ')
				    continue;
				/*
				 * Only compress tabs if doing XOVER
				 */
				if (conn->co_ArtMode != COM_XOVER &&
							rline[i] == '\t')
				    continue;
				if (doSpace)
				    MBWrite(&conn->co_TMBuf, " ", 1);
				MBWrite(&conn->co_TMBuf, rline + b, i - b);
				b = i + 1;
				doSpace = 1;
			    }
			}
		    }
		    if (b != i) {
			if (doSpace)
			    MBWrite(&conn->co_TMBuf, " ", 1);
			MBWrite(&conn->co_TMBuf, rline + b, i - b);
		    }
		}
		break;
	    }
	    rleft -= rlen;
	    rline += rlen;
	} /* while */
	if (printHdr == 0 && conn->co_ArtMode == COM_XOVER) {
	    if (didIndex == 0) {
		MBPrintf(&conn->co_TMBuf, "%d", conn->co_ListBegNo);
		didIndex = 1;
	    }
	    if (hhlen == 5 && hch == 'b' && strncasecmp(hdr, "Bytes", 5) == 0) {
		MBPrintf(&conn->co_TMBuf, "\t%d", artSize + 1);
	    } else {
		MBPrintf(&conn->co_TMBuf, "\t");
	    }
	}

	hdr += hlen;
    }
    if (didIndex == 0 && 
	(/* conn->co_ArtMode == COM_XPAT || */ conn->co_ArtMode == COM_XHDR)
    ) {
	MBPrintf(&conn->co_TMBuf, "%d (none)", conn->co_ListBegNo);
	didIndex = 1;
    }
    if (didIndex) {
	MBPrintf(&conn->co_TMBuf, "\r\n");
	return(0);
    }
    return(-1);
}



syntax highlighted by Code2HTML, v. 0.9.1