mirror of https://github.com/lianthony/NT4.0
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1782 lines
64 KiB
1782 lines
64 KiB
/*
|
|
Enhanced NCSA Mosaic from Spyglass
|
|
"Guitar"
|
|
|
|
Copyright 1994 Spyglass, Inc.
|
|
All Rights Reserved
|
|
|
|
Author(s):
|
|
Jim Seidman [email protected]
|
|
|
|
Portions of this file were derived from
|
|
the CERN libwww, version 2.15.
|
|
*/
|
|
|
|
#ifndef _GIBRALTAR
|
|
#include "all.h"
|
|
|
|
#define NEWS_PORT 119 /* See rfc977 */
|
|
#define MAX_CHUNK 40 /* Largest number of articles in one window */
|
|
#define CHUNK_SIZE 20 /* Number of articles for quick display */
|
|
|
|
#define NEWS_END_MARK -1
|
|
|
|
struct _HTStructured
|
|
{
|
|
CONST HTStructuredClass *isa;
|
|
/* ... */
|
|
};
|
|
|
|
#define LINE_LENGTH 512 /* Maximum length of line of ARTICLE etc */
|
|
#define GROUP_NAME_LENGTH 256 /* Maximum length of group name */
|
|
|
|
void News_DisposeNewsConnection(struct _CachedConn *pCon)
|
|
{
|
|
XX_Assert((pCon->type == CONN_NNTP), ("News_DisposeNewsConnection: connection type is %d!", pCon->type));
|
|
XX_Assert((pCon->addr != 0), ("News_DisposeNewsConnection: connection has no address!"));
|
|
pCon->addr = 0;
|
|
Net_Close(pCon->socket);
|
|
pCon->socket = -1;
|
|
pCon->type = CONN_NONE;
|
|
}
|
|
|
|
/****************************************************************************/
|
|
/* This routine sends a NNTP command and retrieves the status return,
|
|
as per PFC 977 */
|
|
struct Params_News_Command {
|
|
HTInputSocket * isoc;
|
|
char * cmd; /* Command to send - will be freed! */
|
|
int * pResult; /* Place to store response */
|
|
char ** ppResText; /* Where to return response text (can be NULL) */
|
|
|
|
/* Used internally */
|
|
int net_status; /* Network operation result */
|
|
char text[LINE_LENGTH + 1];
|
|
int index; /* index into text[] */
|
|
};
|
|
|
|
#define STATE_COMMAND_SENT (STATE_OTHER)
|
|
#define STATE_COMMAND_GOTDATA (STATE_OTHER+1)
|
|
static int News_Command_Async(struct Mwin *tw, int nState, void **ppInfo)
|
|
{
|
|
struct Params_News_Command *pParams;
|
|
signed char ch;
|
|
|
|
pParams = *ppInfo;
|
|
|
|
switch (nState)
|
|
{
|
|
case STATE_INIT:
|
|
pParams->index = 0;
|
|
|
|
/* Send command */
|
|
if (pParams->cmd)
|
|
{
|
|
struct Params_Send *pps;
|
|
|
|
XX_DMsg(DBG_LOAD, ("News: sending command %s", pParams->cmd));
|
|
|
|
pps = GTR_CALLOC(sizeof(*pps), 1);
|
|
if (pps)
|
|
{
|
|
pps->socket = pParams->isoc->input_file_number;
|
|
pps->pBuf = pParams->cmd;
|
|
pps->nBufLen = strlen(pParams->cmd);
|
|
pps->pStatus = &pParams->net_status;
|
|
Async_DoCall(Net_Send_Async, pps);
|
|
return STATE_COMMAND_SENT;
|
|
}
|
|
else
|
|
{
|
|
ERR_ReportError(tw, SID_ERR_OUT_OF_MEMORY, NULL, NULL);
|
|
return STATE_ABORT;
|
|
}
|
|
}
|
|
/* Otherwise we're just reading response.
|
|
Fall through */
|
|
|
|
case STATE_COMMAND_SENT:
|
|
if (pParams->cmd)
|
|
{
|
|
GTR_FREE(pParams->cmd);
|
|
pParams->cmd = NULL;
|
|
if (pParams->net_status < 0)
|
|
{
|
|
*pParams->pResult = -1;
|
|
return STATE_DONE;
|
|
}
|
|
}
|
|
pParams->net_status = 0;
|
|
/* fall through */
|
|
|
|
case STATE_COMMAND_GOTDATA:
|
|
if (pParams->net_status < 0)
|
|
{
|
|
*pParams->pResult = -1;
|
|
return STATE_DONE;
|
|
}
|
|
ch = 0;
|
|
for ( ; pParams->isoc->input_pointer < pParams->isoc->input_limit; pParams->isoc->input_pointer++)
|
|
{
|
|
ch = *pParams->isoc->input_pointer;
|
|
if (ch == CR)
|
|
continue;
|
|
else if (ch == LF)
|
|
break;
|
|
else if (pParams->index < LINE_LENGTH)
|
|
{
|
|
pParams->text[pParams->index++] = ch;
|
|
}
|
|
}
|
|
/* Step past the character we just read in the isoc */
|
|
pParams->isoc->input_pointer++;
|
|
|
|
/* If we didn't quit the loop because of finding an LF, get more */
|
|
if (ch != LF)
|
|
{
|
|
struct Params_Isoc_Fill *pif;
|
|
|
|
pif = GTR_CALLOC(sizeof(*pif), 1);
|
|
if (pif)
|
|
{
|
|
pif->isoc = pParams->isoc;
|
|
pif->pStatus = &pParams->net_status;
|
|
Async_DoCall(Isoc_Fill_Async, pif);
|
|
return STATE_COMMAND_GOTDATA;
|
|
}
|
|
else
|
|
{
|
|
ERR_ReportError(tw, SID_ERR_OUT_OF_MEMORY, NULL, NULL);
|
|
return STATE_ABORT;
|
|
}
|
|
}
|
|
|
|
/* Terminate this line of stuff */
|
|
pParams->text[pParams->index] = '\0';
|
|
if (pParams->ppResText)
|
|
{
|
|
*pParams->ppResText = GTR_CALLOC(pParams->index + 1, 1);
|
|
if (*pParams->ppResText)
|
|
{
|
|
strcpy(*pParams->ppResText, pParams->text);
|
|
}
|
|
else
|
|
{
|
|
ERR_ReportError(tw, SID_ERR_OUT_OF_MEMORY, NULL, NULL);
|
|
return STATE_ABORT;
|
|
}
|
|
}
|
|
XX_DMsg(DBG_LOAD, ("News: Other side sent %s\n", pParams->text));
|
|
*pParams->pResult = atoi(pParams->text);
|
|
if (*pParams->pResult == 0)
|
|
{
|
|
/* Something must be wrong */
|
|
*pParams->pResult = -1;
|
|
}
|
|
return STATE_DONE;
|
|
|
|
case STATE_ABORT:
|
|
if (pParams->cmd)
|
|
{
|
|
GTR_FREE(pParams->cmd);
|
|
}
|
|
*pParams->pResult = -1;
|
|
return STATE_DONE;
|
|
}
|
|
XX_Assert((0), ("Function called with illegal state: %d", nState));
|
|
return STATE_DONE;
|
|
}
|
|
|
|
/****************************************************************************/
|
|
/* Utility functions */
|
|
|
|
/* Case insensitive string comparisons
|
|
** -----------------------------------
|
|
**
|
|
** On entry,
|
|
** template must be already un upper case.
|
|
** unknown may be in upper or lower or mixed case to match.
|
|
*/
|
|
PRIVATE BOOL match(CONST char *unknown, CONST char *template)
|
|
{
|
|
CONST char *u = unknown;
|
|
CONST char *t = template;
|
|
for (; *u && *t && (TOUPPER(*u) == *t); u++, t++) /* Find mismatch or end */ ;
|
|
return (BOOL) (*t == 0); /* OK if end of template */
|
|
}
|
|
|
|
/* Find Author's name in mail address
|
|
** ----------------------------------
|
|
**
|
|
** On exit,
|
|
** THE EMAIL ADDRESS IS CORRUPTED
|
|
**
|
|
** For example, returns "Tim Berners-Lee" if given any of
|
|
** " Tim Berners-Lee <[email protected]> "
|
|
** or " [email protected] ( Tim Berners-Lee ) "
|
|
*/
|
|
PRIVATE char *author_name(char *email)
|
|
{
|
|
char *s, *e;
|
|
|
|
if ((s = strchr(email, '(')) && (e = strchr(email, ')')))
|
|
if (e > s)
|
|
{
|
|
*e = 0; /* Chop off everything after the ')' */
|
|
return HTStrip(s + 1); /* Remove leading and trailing spaces */
|
|
}
|
|
|
|
if ((s = strchr(email, '<')) && (e = strchr(email, '>')))
|
|
if (e > s)
|
|
{
|
|
strcpy(s, e + 1); /* Remove <...> */
|
|
return HTStrip(email); /* Remove leading and trailing spaces */
|
|
}
|
|
|
|
return HTStrip(email); /* Default to the whole thing */
|
|
|
|
}
|
|
|
|
/* Start anchor element */
|
|
PRIVATE void start_anchor(HTStructured *target, CONST char *href)
|
|
{
|
|
BOOL present[HTML_A_ATTRIBUTES];
|
|
CONST char *value[HTML_A_ATTRIBUTES];
|
|
int i;
|
|
|
|
for (i = 0; i < HTML_A_ATTRIBUTES; i++)
|
|
present[i] = FALSE;
|
|
present[HTML_A_HREF] = TRUE;
|
|
value[HTML_A_HREF] = href;
|
|
(*target->isa->start_element) (target, HTML_A, present, value);
|
|
}
|
|
|
|
/* Paste in an Anchor
|
|
** ------------------
|
|
**
|
|
**
|
|
** On entry,
|
|
** HT has a selection of zero length at the end.
|
|
** text points to the text to be put into the file, 0 terminated.
|
|
** addr points to the hypertext refernce address,
|
|
** terminated by white space, comma, NULL or '>'
|
|
*/
|
|
PRIVATE void write_anchor(HTStructured *target, CONST char *text, CONST char *addr)
|
|
{
|
|
char href[LINE_LENGTH + 1];
|
|
|
|
{
|
|
CONST char *p;
|
|
strcpy(href, "news:");
|
|
for (p = addr; *p && (*p != '>') && !WHITE(*p) && (*p != ','); p++) ;
|
|
strncat(href, addr, p - addr); /* Make complete hypertext reference */
|
|
}
|
|
|
|
start_anchor(target, href);
|
|
(target->isa->put_string)(target, text);
|
|
(target->isa->end_element)(target, HTML_A);
|
|
}
|
|
|
|
/* Write list of anchors
|
|
** ---------------------
|
|
**
|
|
** We take a pointer to a list of objects, and write out each,
|
|
** generating an anchor for each.
|
|
**
|
|
** On entry,
|
|
** HT has a selection of zero length at the end.
|
|
** text points to a comma or space separated list of addresses.
|
|
** On exit,
|
|
** *text is NOT any more chopped up into substrings.
|
|
*/
|
|
PRIVATE void write_anchors(HTStructured *target, char *text)
|
|
{
|
|
char *start = text;
|
|
char *end;
|
|
char c;
|
|
for (;;)
|
|
{
|
|
for (; *start && (WHITE(*start)); start++) ; /* Find start */
|
|
if (!*start)
|
|
return; /* (Done) */
|
|
for (end = start; *end && (*end != ' ') && (*end != ','); end++) ; /* Find end */
|
|
if (*end)
|
|
end++; /* Include comma or space but not NULL */
|
|
c = *end;
|
|
*end = 0;
|
|
write_anchor(target, start, start);
|
|
(*target->isa->start_element)(target, HTML_BR, 0, 0);
|
|
*end = c;
|
|
start = end; /* Point to next one */
|
|
}
|
|
}
|
|
|
|
/****************************************************************************/
|
|
/* Code to handle reading the list of available newsgroups */
|
|
|
|
struct Params_ReadList {
|
|
HTInputSocket * isoc;
|
|
int * pStatus;
|
|
HTStructured * target;
|
|
|
|
/* Used internally */
|
|
char line[LINE_LENGTH + 1];
|
|
int index; /* Index into line[] */
|
|
};
|
|
|
|
#define STATE_LIST_GOTDATA (STATE_OTHER)
|
|
static int News_ReadList_Async(struct Mwin *tw, int nState, void **ppInfo)
|
|
{
|
|
struct Params_ReadList *pParams;
|
|
signed char ch;
|
|
char *p;
|
|
char *pNext;
|
|
|
|
pParams = *ppInfo;
|
|
|
|
switch (nState)
|
|
{
|
|
case STATE_INIT:
|
|
(*pParams->target->isa->start_element)(pParams->target, HTML_TITLE, 0, 0);
|
|
(*pParams->target->isa->put_string)(pParams->target, GTR_GetString(SID_INF_AVAIALBLE_NEWSGROUPS));
|
|
(*pParams->target->isa->end_element)(pParams->target, HTML_TITLE);
|
|
(*pParams->target->isa->start_element)(pParams->target, HTML_DL, 0, 0);
|
|
|
|
pParams->index = 0;
|
|
if (pParams->isoc->input_buffer >= pParams->isoc->input_limit)
|
|
{
|
|
struct Params_Isoc_Fill *pif;
|
|
|
|
pif = GTR_CALLOC(sizeof(*pif), 1);
|
|
if (pif)
|
|
{
|
|
pif->isoc = pParams->isoc;
|
|
pif->pStatus = pParams->pStatus;
|
|
Async_DoCall(Isoc_Fill_Async, pif);
|
|
return STATE_LIST_GOTDATA;
|
|
}
|
|
else
|
|
{
|
|
ERR_ReportError(tw, SID_ERR_OUT_OF_MEMORY, NULL, NULL);
|
|
return STATE_ABORT;
|
|
}
|
|
}
|
|
/* Otherwise just hack a status value and fall through */
|
|
*pParams->pStatus = 1;
|
|
|
|
case STATE_LIST_GOTDATA:
|
|
if (*pParams->pStatus > 0)
|
|
{
|
|
for (pNext = pParams->isoc->input_pointer; pNext < pParams->isoc->input_limit; pNext++)
|
|
{
|
|
ch = *pNext;
|
|
if (ch == NEWS_END_MARK)
|
|
{
|
|
pParams->isoc->input_pointer = pNext + 1;
|
|
break;
|
|
}
|
|
else if (ch == CR)
|
|
{
|
|
continue;
|
|
}
|
|
else if (ch != LF)
|
|
{
|
|
pParams->line[pParams->index] = ch; /* Put character in line */
|
|
if (pParams->index < LINE_LENGTH - 1)
|
|
pParams->index++;
|
|
}
|
|
else
|
|
{
|
|
pParams->line[pParams->index] = '\0'; /* Terminate line */
|
|
pParams->index = 0; /* For next time through loop */
|
|
if (pParams->line[0] == '.' && pParams->line[1] < ' ')
|
|
{
|
|
/* End of data */
|
|
pParams->isoc->input_pointer = pNext + 1;
|
|
ch = NEWS_END_MARK;
|
|
break;
|
|
}
|
|
/* Figure out where the newsgroup name ends */
|
|
for (p = pParams->line; *p; p++)
|
|
{
|
|
if (isspace(*p))
|
|
break;
|
|
}
|
|
if (*p)
|
|
{
|
|
/* Null-terminate the name, then find where the description is. */
|
|
*p = '\0';
|
|
p++;
|
|
while (*p && isspace(*p))
|
|
p++;
|
|
if (!*p)
|
|
p = NULL;
|
|
}
|
|
else
|
|
{
|
|
p = NULL;
|
|
}
|
|
(*pParams->target->isa->start_element)(pParams->target, HTML_DT, 0, 0);
|
|
start_anchor(pParams->target, pParams->line);
|
|
(*pParams->target->isa->put_string)(pParams->target, pParams->line);
|
|
(*pParams->target->isa->end_element)(pParams->target, HTML_A);
|
|
if (p && *p != '?')
|
|
{
|
|
(*pParams->target->isa->start_element)(pParams->target, HTML_DD, 0, 0);
|
|
(*pParams->target->isa->put_string)(pParams->target, p);
|
|
}
|
|
}
|
|
}
|
|
if (ch != NEWS_END_MARK)
|
|
{
|
|
struct Params_Isoc_Fill *pif;
|
|
|
|
/* Show what we have so far */
|
|
(*pParams->target->isa->block_done)(pParams->target);
|
|
|
|
/* Get next block of data */
|
|
pif = GTR_CALLOC(sizeof(*pif), 1);
|
|
if (pif)
|
|
{
|
|
pif->isoc = pParams->isoc;
|
|
pif->pStatus = pParams->pStatus;
|
|
Async_DoCall(Isoc_Fill_Async, pif);
|
|
return STATE_LIST_GOTDATA;
|
|
}
|
|
else
|
|
{
|
|
ERR_ReportError(tw, SID_ERR_OUT_OF_MEMORY, NULL, NULL);
|
|
return STATE_ABORT;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Clean up */
|
|
(*pParams->target->isa->end_element)(pParams->target, HTML_DL);
|
|
*pParams->pStatus = 1;
|
|
return STATE_DONE;
|
|
|
|
case STATE_ABORT:
|
|
*pParams->pStatus = -1;
|
|
return STATE_DONE;
|
|
}
|
|
XX_Assert((0), ("Function called with illegal state: %d", nState));
|
|
return STATE_DONE;
|
|
}
|
|
|
|
|
|
/****************************************************************************/
|
|
/* Code to handle reading a list of articles from a newsgroup */
|
|
|
|
struct Params_ReadGroup {
|
|
HTInputSocket * isoc;
|
|
int * pStatus;
|
|
HTStructured * target;
|
|
int nFirst; /* First article desired for list */
|
|
int nLast; /* Last article desired */
|
|
int nFirstInGroup;
|
|
int nLastInGroup;
|
|
int nArticleCount;
|
|
char szGroup[256];
|
|
|
|
/* Used internally */
|
|
char line[LINE_LENGTH + 1];
|
|
int index; /* Index into line[] */
|
|
struct hash_table hashArticles; /* Used to match up article IDs and subjects */
|
|
BOOL bHashEmpty;
|
|
};
|
|
|
|
#define STATE_GROUP_GOTIDS (STATE_OTHER)
|
|
#define STATE_GROUP_GOTIDDATA (STATE_OTHER+1)
|
|
#define STATE_GROUP_GOTSUBJECTS (STATE_OTHER+2)
|
|
#define STATE_GROUP_GOTSUBJECTDATA (STATE_OTHER+3)
|
|
#define STATE_GROUP_GOTAUTHORS (STATE_OTHER+4)
|
|
#define STATE_GROUP_GOTAUTHORDATA (STATE_OTHER+5)
|
|
static int News_ReadGroup_Async(struct Mwin *tw, int nState, void **ppInfo)
|
|
{
|
|
struct Params_ReadGroup *pParams;
|
|
signed char ch;
|
|
char *pNext;
|
|
char *p;
|
|
int n;
|
|
int ndx;
|
|
char *pID;
|
|
char *pSubj;
|
|
char buf[512+1];
|
|
|
|
pParams = *ppInfo;
|
|
|
|
switch (nState)
|
|
{
|
|
case STATE_INIT:
|
|
sprintf(buf, GTR_GetString(SID_INF_NEWSGROUP_ARTICLES_S_D_D),
|
|
pParams->szGroup, pParams->nFirst, pParams->nLast);
|
|
(*pParams->target->isa->start_element)(pParams->target, HTML_TITLE, 0, 0);
|
|
(*pParams->target->isa->put_string)(pParams->target, buf);
|
|
(*pParams->target->isa->end_element)(pParams->target, HTML_TITLE);
|
|
|
|
sprintf(buf, GTR_GetString(SID_INF_ARTICLES_CURRENTLY_SHOWN_D_S_D_D),
|
|
pParams->nArticleCount, pParams->szGroup, pParams->nFirst, pParams->nLast);
|
|
(*pParams->target->isa->put_string)(pParams->target, buf);
|
|
(*pParams->target->isa->start_element)(pParams->target, HTML_BR, 0, 0);
|
|
|
|
/* Link to earlier articles */
|
|
if (pParams->nFirst > pParams->nFirstInGroup)
|
|
{
|
|
int start;
|
|
|
|
if (pParams->nFirst - MAX_CHUNK <= pParams->nFirstInGroup)
|
|
start = pParams->nFirstInGroup;
|
|
else
|
|
start = pParams->nFirst - CHUNK_SIZE;
|
|
sprintf(buf, "%s/%d-%d", pParams->szGroup, start, pParams->nFirst - 1);
|
|
(*pParams->target->isa->put_string)(pParams->target, " (");
|
|
start_anchor(pParams->target, buf);
|
|
(*pParams->target->isa->put_string)(pParams->target, GTR_GetString(SID_INF_EARLIER_ARTICLES));
|
|
(*pParams->target->isa->end_element)(pParams->target, HTML_A);
|
|
(*pParams->target->isa->put_string)(pParams->target, "...) ");
|
|
}
|
|
|
|
/* Link to later articles */
|
|
if (pParams->nLast < pParams->nLastInGroup)
|
|
{
|
|
int end;
|
|
|
|
if (pParams->nLast + MAX_CHUNK >= pParams->nLastInGroup)
|
|
end = pParams->nLastInGroup;
|
|
else
|
|
end = pParams->nLast + CHUNK_SIZE;
|
|
sprintf(buf, "%s/%d-%d", pParams->szGroup, pParams->nLast + 1, end);
|
|
(*pParams->target->isa->put_string)(pParams->target, " (");
|
|
start_anchor(pParams->target, buf);
|
|
(*pParams->target->isa->put_string)(pParams->target, GTR_GetString(SID_INF_LATER_ARTICLES));
|
|
(*pParams->target->isa->end_element)(pParams->target, HTML_A);
|
|
(*pParams->target->isa->put_string)(pParams->target, "...)");
|
|
}
|
|
|
|
if (pParams->nFirst > pParams->nFirstInGroup || pParams->nLast < pParams->nLastInGroup)
|
|
{
|
|
/* Add a break after earlier/later articles link */
|
|
(*pParams->target->isa->start_element)(pParams->target, HTML_BR, 0, 0);
|
|
}
|
|
|
|
(*pParams->target->isa->start_element)(pParams->target, HTML_UL, 0, 0);
|
|
|
|
Hash_Init(&pParams->hashArticles);
|
|
|
|
/* Get all of the message IDs for these articles. We'll later match them up
|
|
with the subjects. */
|
|
{
|
|
struct Params_News_Command *pnc;
|
|
|
|
pnc = GTR_CALLOC(sizeof(*pnc), 1);
|
|
if (pnc)
|
|
{
|
|
pnc->isoc = pParams->isoc;
|
|
pnc->cmd = GTR_CALLOC(100, 1);
|
|
if (pnc->cmd)
|
|
{
|
|
sprintf(pnc->cmd, "XHDR Message-ID %d-%d\015\012", pParams->nFirst, pParams->nLast);
|
|
pnc->pResult = pParams->pStatus;
|
|
pnc->ppResText = NULL;
|
|
Async_DoCall(News_Command_Async, pnc);
|
|
}
|
|
else
|
|
{
|
|
GTR_FREE(pnc);
|
|
ERR_ReportError(tw, SID_ERR_OUT_OF_MEMORY, NULL, NULL);
|
|
return STATE_ABORT;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ERR_ReportError(tw, SID_ERR_OUT_OF_MEMORY, NULL, NULL);
|
|
return STATE_ABORT;
|
|
}
|
|
}
|
|
return STATE_GROUP_GOTIDS;
|
|
|
|
case STATE_GROUP_GOTIDS:
|
|
if (*pParams->pStatus < 0)
|
|
{
|
|
Hash_FreeContents(&pParams->hashArticles);
|
|
return STATE_DONE;
|
|
}
|
|
if (*pParams->pStatus / 100 != 2)
|
|
{
|
|
*pParams->pStatus = -1;
|
|
Hash_FreeContents(&pParams->hashArticles);
|
|
ERR_ReportError(tw, SID_ERR_NO_XHDR_SUPPORT, NULL, NULL);
|
|
return STATE_DONE;
|
|
}
|
|
pParams->index = 0;
|
|
ch = 0;
|
|
/* Fall through */
|
|
case STATE_GROUP_GOTIDDATA:
|
|
if (*pParams->pStatus <= 0)
|
|
{
|
|
*pParams->pStatus = -1;
|
|
Hash_FreeContents(&pParams->hashArticles);
|
|
return STATE_DONE;
|
|
}
|
|
else
|
|
{
|
|
for (pNext = pParams->isoc->input_buffer; pNext < pParams->isoc->input_limit; pNext++)
|
|
{
|
|
ch = *pNext;
|
|
if (ch == NEWS_END_MARK)
|
|
{
|
|
pParams->isoc->input_pointer = pNext + 1;
|
|
break;
|
|
}
|
|
else if (ch == CR)
|
|
{
|
|
continue;
|
|
}
|
|
else if (ch != LF)
|
|
{
|
|
pParams->line[pParams->index] = ch; /* Put character in line */
|
|
if (pParams->index < LINE_LENGTH - 1)
|
|
pParams->index++;
|
|
}
|
|
else
|
|
{
|
|
pParams->line[pParams->index] = '\0'; /* Terminate line */
|
|
pParams->index = 0; /* For next time through loop */
|
|
|
|
if (pParams->line[0] == '.' && pParams->line[1] < ' ')
|
|
{
|
|
pParams->isoc->input_pointer = pNext + 1;
|
|
ch = NEWS_END_MARK;
|
|
break;
|
|
}
|
|
p = strchr(pParams->line, '>');
|
|
if (p)
|
|
{
|
|
*p = '\0';
|
|
}
|
|
else
|
|
{
|
|
XX_DMsg(DBG_LOAD, ("Strange XHDR response: %s\n", pParams->line));
|
|
continue;
|
|
}
|
|
n = atoi(pParams->line);
|
|
if (!n)
|
|
{
|
|
XX_DMsg(DBG_LOAD, ("Strange XHDR response: %s\n", pParams->line));
|
|
continue;
|
|
}
|
|
p = strchr(pParams->line, '<');
|
|
if (!p)
|
|
{
|
|
XX_DMsg(DBG_LOAD, ("Strange XHDR response: %s\n", pParams->line));
|
|
continue;
|
|
}
|
|
p++; /* Now p points to message-id */
|
|
/* Add this message-id to the hash along with its article number */
|
|
Hash_Add(&pParams->hashArticles, p, NULL, (void *) n);
|
|
}
|
|
}
|
|
|
|
if (ch != NEWS_END_MARK)
|
|
{
|
|
struct Params_Isoc_Fill *pif;
|
|
|
|
/* Get next block of data */
|
|
pif = GTR_CALLOC(sizeof(*pif), 1);
|
|
if (pif)
|
|
{
|
|
pif->isoc = pParams->isoc;
|
|
pif->pStatus = pParams->pStatus;
|
|
Async_DoCall(Isoc_Fill_Async, pif);
|
|
return STATE_GROUP_GOTIDDATA;
|
|
}
|
|
else
|
|
{
|
|
ERR_ReportError(tw, SID_ERR_OUT_OF_MEMORY, NULL, NULL);
|
|
return STATE_ABORT;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Now get all of the subjects for these articles */
|
|
{
|
|
struct Params_News_Command *pnc;
|
|
|
|
pnc = GTR_CALLOC(sizeof(*pnc), 1);
|
|
if (pnc)
|
|
{
|
|
pnc->isoc = pParams->isoc;
|
|
pnc->cmd = GTR_CALLOC(100, 1);
|
|
if (pnc->cmd)
|
|
{
|
|
sprintf(pnc->cmd, "XHDR Subject %d-%d\015\012", pParams->nFirst, pParams->nLast);
|
|
pnc->pResult = pParams->pStatus;
|
|
pnc->ppResText = NULL;
|
|
Async_DoCall(News_Command_Async, pnc);
|
|
}
|
|
else
|
|
{
|
|
GTR_FREE(pnc);
|
|
ERR_ReportError(tw, SID_ERR_OUT_OF_MEMORY, NULL, NULL);
|
|
return STATE_ABORT;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ERR_ReportError(tw, SID_ERR_OUT_OF_MEMORY, NULL, NULL);
|
|
return STATE_ABORT;
|
|
}
|
|
}
|
|
return STATE_GROUP_GOTSUBJECTS;
|
|
|
|
case STATE_GROUP_GOTSUBJECTS:
|
|
if (*pParams->pStatus < 0)
|
|
{
|
|
Hash_FreeContents(&pParams->hashArticles);
|
|
return STATE_DONE;
|
|
}
|
|
if (*pParams->pStatus / 100 != 2)
|
|
{
|
|
*pParams->pStatus = -1;
|
|
Hash_FreeContents(&pParams->hashArticles);
|
|
ERR_ReportError(tw, SID_ERR_NO_XHDR_SUPPORT, NULL, NULL);
|
|
return STATE_DONE;
|
|
}
|
|
pParams->index = 0;
|
|
ch = 0;
|
|
/* Fall through */
|
|
case STATE_GROUP_GOTSUBJECTDATA:
|
|
if (*pParams->pStatus <= 0)
|
|
{
|
|
*pParams->pStatus = -1;
|
|
Hash_FreeContents(&pParams->hashArticles);
|
|
return STATE_DONE;
|
|
}
|
|
else
|
|
{
|
|
for (pNext = pParams->isoc->input_buffer; pNext < pParams->isoc->input_limit; pNext++)
|
|
{
|
|
ch = *pNext;
|
|
if (ch == NEWS_END_MARK)
|
|
{
|
|
pParams->isoc->input_pointer = pNext + 1;
|
|
break;
|
|
}
|
|
else if (ch == CR)
|
|
{
|
|
continue;
|
|
}
|
|
else if (ch != LF)
|
|
{
|
|
pParams->line[pParams->index] = ch; /* Put character in line */
|
|
if (pParams->index < LINE_LENGTH - 1)
|
|
pParams->index++;
|
|
}
|
|
else
|
|
{
|
|
pParams->line[pParams->index] = '\0'; /* Terminate line */
|
|
pParams->index = 0; /* For next time through loop */
|
|
|
|
if (pParams->line[0] == '.' && pParams->line[1] < ' ')
|
|
{
|
|
pParams->isoc->input_pointer = pNext + 1;
|
|
ch = NEWS_END_MARK;
|
|
break;
|
|
}
|
|
n = atoi(pParams->line);
|
|
if (!n)
|
|
{
|
|
XX_DMsg(DBG_LOAD, ("Strange XHDR response: %s\n", pParams->line));
|
|
continue;
|
|
}
|
|
p = strchr(pParams->line, ' ');
|
|
if (!p)
|
|
{
|
|
XX_DMsg(DBG_LOAD, ("Strange XHDR response: %s\n", pParams->line));
|
|
continue;
|
|
}
|
|
p++; /* Now p points to the subject */
|
|
/* Match up this subject with its message-id */
|
|
ndx = Hash_FindByData(&pParams->hashArticles, NULL, NULL, (void *) n);
|
|
if (ndx < 0)
|
|
{
|
|
XX_DMsg(DBG_LOAD, ("Subject has no message-id: %s\n", pParams->line));
|
|
continue;
|
|
}
|
|
Hash_SetString2(&pParams->hashArticles, ndx, p);
|
|
}
|
|
}
|
|
|
|
if (ch != NEWS_END_MARK)
|
|
{
|
|
struct Params_Isoc_Fill *pif;
|
|
|
|
/* Get next block of data */
|
|
pif = GTR_CALLOC(sizeof(*pif), 1);
|
|
if (pif)
|
|
{
|
|
pif->isoc = pParams->isoc;
|
|
pif->pStatus = pParams->pStatus;
|
|
Async_DoCall(Isoc_Fill_Async, pif);
|
|
return STATE_GROUP_GOTSUBJECTDATA;
|
|
}
|
|
else
|
|
{
|
|
ERR_ReportError(tw, SID_ERR_OUT_OF_MEMORY, NULL, NULL);
|
|
return STATE_ABORT;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Get the authors for these articles */
|
|
{
|
|
struct Params_News_Command *pnc;
|
|
|
|
pnc = GTR_CALLOC(sizeof(*pnc), 1);
|
|
if (pnc)
|
|
{
|
|
pnc->isoc = pParams->isoc;
|
|
pnc->cmd = GTR_CALLOC(100, 1);
|
|
if (pnc->cmd)
|
|
{
|
|
sprintf(pnc->cmd, "XHDR From %d-%d\015\012", pParams->nFirst, pParams->nLast);
|
|
pnc->pResult = pParams->pStatus;
|
|
pnc->ppResText = NULL;
|
|
Async_DoCall(News_Command_Async, pnc);
|
|
}
|
|
else
|
|
{
|
|
GTR_FREE(pnc);
|
|
ERR_ReportError(tw, SID_ERR_OUT_OF_MEMORY, NULL, NULL);
|
|
return STATE_ABORT;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ERR_ReportError(tw, SID_ERR_OUT_OF_MEMORY, NULL, NULL);
|
|
return STATE_ABORT;
|
|
}
|
|
}
|
|
return STATE_GROUP_GOTAUTHORS;
|
|
|
|
case STATE_GROUP_GOTAUTHORS:
|
|
if (*pParams->pStatus < 0)
|
|
{
|
|
Hash_FreeContents(&pParams->hashArticles);
|
|
return STATE_DONE;
|
|
}
|
|
if (*pParams->pStatus / 100 != 2)
|
|
{
|
|
*pParams->pStatus = -1;
|
|
Hash_FreeContents(&pParams->hashArticles);
|
|
ERR_ReportError(tw, SID_ERR_NO_XHDR_SUPPORT, NULL, NULL);
|
|
return STATE_DONE;
|
|
}
|
|
pParams->index = 0;
|
|
ch = 0;
|
|
/* Fall through */
|
|
case STATE_GROUP_GOTAUTHORDATA:
|
|
if (*pParams->pStatus <= 0)
|
|
{
|
|
*pParams->pStatus = -1;
|
|
Hash_FreeContents(&pParams->hashArticles);
|
|
return STATE_DONE;
|
|
}
|
|
else
|
|
{
|
|
for (pNext = pParams->isoc->input_buffer; pNext < pParams->isoc->input_limit; pNext++)
|
|
{
|
|
ch = *pNext;
|
|
if (ch == NEWS_END_MARK)
|
|
{
|
|
pParams->isoc->input_pointer = pNext + 1;
|
|
break;
|
|
}
|
|
else if (ch == CR)
|
|
{
|
|
continue;
|
|
}
|
|
else if (ch != LF)
|
|
{
|
|
pParams->line[pParams->index] = ch; /* Put character in line */
|
|
if (pParams->index < LINE_LENGTH - 1)
|
|
pParams->index++;
|
|
}
|
|
else
|
|
{
|
|
pParams->line[pParams->index] = '\0'; /* Terminate line */
|
|
pParams->index = 0; /* For next time through loop */
|
|
|
|
if (pParams->line[0] == '.' && pParams->line[1] < ' ')
|
|
{
|
|
pParams->isoc->input_pointer = pNext + 1;
|
|
ch = NEWS_END_MARK;
|
|
break;
|
|
}
|
|
n = atoi(pParams->line);
|
|
if (!n)
|
|
{
|
|
XX_DMsg(DBG_LOAD, ("Strange XHDR response: %s\n", pParams->line));
|
|
continue;
|
|
}
|
|
p = strchr(pParams->line, ' ');
|
|
if (!p)
|
|
{
|
|
XX_DMsg(DBG_LOAD, ("Strange XHDR response: %s\n", pParams->line));
|
|
continue;
|
|
}
|
|
p++; /* Now p points to author */
|
|
p = author_name(p);
|
|
if (Hash_FindByData(&pParams->hashArticles, &pID, &pSubj, (void *) n) < 0)
|
|
{
|
|
XX_DMsg(DBG_LOAD, ("No hash entry for article %d by %s\n", n, p));
|
|
continue;
|
|
}
|
|
|
|
(*pParams->target->isa->start_element)(pParams->target, HTML_LI, 0, 0);
|
|
|
|
/* Start the anchor with the message-id as the reference */
|
|
start_anchor(pParams->target, pID);
|
|
|
|
/* Put together the string to display */
|
|
sprintf(buf, "#%d \"%s\" - %s", n, pSubj ? pSubj : GTR_GetString(SID_INF_NO_SUBJECT), p);
|
|
(*pParams->target->isa->put_string)(pParams->target, buf);
|
|
(*pParams->target->isa->end_element)(pParams->target, HTML_A);
|
|
}
|
|
}
|
|
|
|
if (ch != NEWS_END_MARK)
|
|
{
|
|
struct Params_Isoc_Fill *pif;
|
|
|
|
/* Get next block of data */
|
|
pif = GTR_CALLOC(sizeof(*pif), 1);
|
|
if (pif)
|
|
{
|
|
pif->isoc = pParams->isoc;
|
|
pif->pStatus = pParams->pStatus;
|
|
Async_DoCall(Isoc_Fill_Async, pif);
|
|
return STATE_GROUP_GOTAUTHORDATA;
|
|
}
|
|
else
|
|
{
|
|
ERR_ReportError(tw, SID_ERR_OUT_OF_MEMORY, NULL, NULL);
|
|
return STATE_ABORT;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Clean up */
|
|
(*pParams->target->isa->end_element)(pParams->target, HTML_UL);
|
|
Hash_FreeContents(&pParams->hashArticles);
|
|
*pParams->pStatus = 1;
|
|
return STATE_DONE;
|
|
|
|
case STATE_ABORT:
|
|
Hash_FreeContents(&pParams->hashArticles);
|
|
*pParams->pStatus = -1;
|
|
return STATE_DONE;
|
|
}
|
|
XX_Assert((0), ("Function called with illegal state: %d", nState));
|
|
return STATE_DONE;
|
|
}
|
|
|
|
/****************************************************************************/
|
|
/* Code to handle reading an article */
|
|
|
|
struct Params_ReadArticle {
|
|
HTInputSocket * isoc;
|
|
int * pStatus;
|
|
HTStructured * target;
|
|
|
|
/* Used internally */
|
|
char line[LINE_LENGTH + 1];
|
|
int index; /* Index into line[] */
|
|
BOOL bInHead;
|
|
char * newsgroups;
|
|
char * references;
|
|
char * subject;
|
|
};
|
|
|
|
#define STATE_ARTICLE_GOTDATA (STATE_OTHER)
|
|
static int News_ReadArticle_Async(struct Mwin *tw, int nState, void **ppInfo)
|
|
{
|
|
struct Params_ReadArticle *pParams;
|
|
char *pNext;
|
|
signed char ch;
|
|
char *p;
|
|
|
|
pParams = *ppInfo;
|
|
|
|
switch (nState)
|
|
{
|
|
case STATE_INIT:
|
|
pParams->index = 0;
|
|
pParams->bInHead = TRUE;
|
|
pParams->newsgroups = NULL;
|
|
pParams->references = NULL;
|
|
pParams->subject = NULL;
|
|
(*pParams->target->isa->start_element)(pParams->target, HTML_ADDRESS, 0, 0);
|
|
if (pParams->isoc->input_buffer >= pParams->isoc->input_limit)
|
|
{
|
|
struct Params_Isoc_Fill *pif;
|
|
|
|
pif = GTR_CALLOC(sizeof(*pif), 1);
|
|
if (pif)
|
|
{
|
|
pif->isoc = pParams->isoc;
|
|
pif->pStatus = pParams->pStatus;
|
|
Async_DoCall(Isoc_Fill_Async, pif);
|
|
return STATE_ARTICLE_GOTDATA;
|
|
}
|
|
else
|
|
{
|
|
ERR_ReportError(tw, SID_ERR_OUT_OF_MEMORY, NULL, NULL);
|
|
return STATE_ABORT;
|
|
}
|
|
}
|
|
/* Otherwise just hack a status value and fall through */
|
|
*pParams->pStatus = 1;
|
|
|
|
case STATE_ARTICLE_GOTDATA:
|
|
if (*pParams->pStatus > 0)
|
|
{
|
|
for (pNext = pParams->isoc->input_buffer; pNext < pParams->isoc->input_limit; pNext++)
|
|
{
|
|
ch = *pNext;
|
|
if (ch == NEWS_END_MARK)
|
|
{
|
|
pParams->isoc->input_pointer = pNext + 1;
|
|
break;
|
|
}
|
|
else if (ch == CR)
|
|
{
|
|
continue;
|
|
}
|
|
else if (ch != LF)
|
|
{
|
|
pParams->line[pParams->index] = ch; /* Put character in line */
|
|
if (pParams->index < LINE_LENGTH - 1)
|
|
pParams->index++;
|
|
}
|
|
else
|
|
{
|
|
pParams->line[pParams->index++] = LF;
|
|
pParams->line[pParams->index] = '\0'; /* Terminate line */
|
|
pParams->index = 0; /* For next time through loop */
|
|
if (pParams->bInHead)
|
|
{
|
|
/* We're still in the article's header */
|
|
if (pParams->line[0] < ' ')
|
|
{
|
|
/* End of header */
|
|
pParams->bInHead = FALSE;
|
|
(*pParams->target->isa->end_element)(pParams->target, HTML_ADDRESS);
|
|
if (pParams->newsgroups || pParams->references)
|
|
{
|
|
(*pParams->target->isa->start_element)(pParams->target, HTML_DL, 0, 0);
|
|
if (pParams->newsgroups)
|
|
{
|
|
(*pParams->target->isa->start_element)(pParams->target, HTML_DT, 0, 0);
|
|
(*pParams->target->isa->put_string)(pParams->target, GTR_GetString(SID_INF_NEWSGROUPS));
|
|
(*pParams->target->isa->start_element)(pParams->target, HTML_DD, 0, 0);
|
|
write_anchors(pParams->target, pParams->newsgroups);
|
|
GTR_FREE(pParams->newsgroups);
|
|
pParams->newsgroups = NULL;
|
|
}
|
|
if (pParams->references)
|
|
{
|
|
(*pParams->target->isa->start_element)(pParams->target, HTML_DT, 0, 0);
|
|
(*pParams->target->isa->put_string)(pParams->target, GTR_GetString(SID_INF_REFERENCES));
|
|
(*pParams->target->isa->start_element)(pParams->target, HTML_DD, 0, 0);
|
|
write_anchors(pParams->target, pParams->references);
|
|
GTR_FREE(pParams->references);
|
|
pParams->references = NULL;
|
|
}
|
|
(*pParams->target->isa->end_element)(pParams->target, HTML_DL);
|
|
}
|
|
(*pParams->target->isa->start_element)(pParams->target, HTML_HR, 0, 0);
|
|
if (pParams->subject)
|
|
{
|
|
(*pParams->target->isa->start_element)(pParams->target, HTML_H2, 0, 0);
|
|
(*pParams->target->isa->put_string)(pParams->target, pParams->subject);
|
|
(*pParams->target->isa->end_element)(pParams->target, HTML_H2);
|
|
GTR_FREE(pParams->subject);
|
|
pParams->subject = NULL;
|
|
}
|
|
(*pParams->target->isa->start_element)(pParams->target, HTML_PRE, 0, 0);
|
|
}
|
|
else if (match(pParams->line, "SUBJECT:"))
|
|
{
|
|
(*pParams->target->isa->start_element)(pParams->target, HTML_TITLE, 0, 0);
|
|
(*pParams->target->isa->put_string)(pParams->target, pParams->line + 9);
|
|
(*pParams->target->isa->end_element)(pParams->target, HTML_TITLE);
|
|
|
|
if (pParams->subject) /* TODO is this needed ? */
|
|
{
|
|
GTR_FREE(pParams->subject);
|
|
}
|
|
pParams->subject = GTR_strdup(pParams->line + 9);
|
|
}
|
|
else if (match(pParams->line, "DATE:") ||
|
|
match(pParams->line, "ORGANIZATION:") ||
|
|
match(pParams->line, "FROM:"))
|
|
{
|
|
(*pParams->target->isa->put_string)(pParams->target, strchr(pParams->line, ':') + 2);
|
|
(*pParams->target->isa->start_element)(pParams->target, HTML_BR, 0, 0);
|
|
}
|
|
else if (match(pParams->line, "NEWSGROUPS:"))
|
|
{
|
|
if (pParams->newsgroups) /* TODO is this needed ? */
|
|
{
|
|
GTR_FREE(pParams->newsgroups);
|
|
}
|
|
pParams->newsgroups = GTR_strdup(HTStrip(strchr(pParams->line, ':') + 1));
|
|
}
|
|
else if (match(pParams->line, "REFERENCES:"))
|
|
{
|
|
/* < and > characters can confuse the parser, since they're
|
|
HTML markup. They're illegal in a message-id anyway,
|
|
since they're used as delimiters. */
|
|
while ((p = strchr(pParams->line, '<')) != NULL)
|
|
{
|
|
*p = ' ';
|
|
}
|
|
while ((p = strchr(pParams->line, '>')) != NULL)
|
|
{
|
|
*p = ' ';
|
|
}
|
|
if (pParams->references) /* TODO is this needed ? */
|
|
{
|
|
GTR_FREE(pParams->references);
|
|
}
|
|
pParams->references = GTR_strdup(HTStrip(strchr(pParams->line, ':') + 1));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
char *l;
|
|
/* We're in the article body */
|
|
l = pParams->line;
|
|
/* Under RFC 977 section 2.4.1, a line starting with a period which
|
|
has other stuff on it should have the period ignored. */
|
|
if (*l == '.')
|
|
{
|
|
l++;
|
|
if (*l < ' ')
|
|
{
|
|
/* End of message */
|
|
pParams->isoc->input_pointer = pNext + 1;
|
|
ch = NEWS_END_MARK;
|
|
break;
|
|
}
|
|
}
|
|
/* Scan for references to other articles. Note that this will
|
|
incorrectly pick up mail addresses occuring inside brackets. */
|
|
while ((p = strchr(l, '<')))
|
|
{
|
|
char *q = strchr(p, '>');
|
|
char *at = strchr(p, '@');
|
|
if (q && at && at < q)
|
|
{
|
|
char c = q[1];
|
|
q[1] = 0; /* chop up */
|
|
*p = 0;
|
|
(*pParams->target->isa->put_string)(pParams->target, l);
|
|
*p = '<'; /* again */
|
|
*q = 0;
|
|
start_anchor(pParams->target, p + 1);
|
|
*q = '>'; /* again */
|
|
(*pParams->target->isa->put_string)(pParams->target, p);
|
|
(*pParams->target->isa->end_element)(pParams->target, HTML_A);
|
|
q[1] = c; /* again */
|
|
l = q + 1;
|
|
}
|
|
else
|
|
break; /* line has unmatched <> */
|
|
}
|
|
(*pParams->target->isa->put_string)(pParams->target, l); /* Last bit of the line */
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ch != NEWS_END_MARK)
|
|
{
|
|
struct Params_Isoc_Fill *pif;
|
|
|
|
/* Update display */
|
|
(*pParams->target->isa->block_done)(pParams->target);
|
|
|
|
/* Get next block of data */
|
|
pif = GTR_CALLOC(sizeof(*pif), 1);
|
|
if (pif)
|
|
{
|
|
pif->isoc = pParams->isoc;
|
|
pif->pStatus = pParams->pStatus;
|
|
Async_DoCall(Isoc_Fill_Async, pif);
|
|
return STATE_ARTICLE_GOTDATA;
|
|
}
|
|
else
|
|
{
|
|
ERR_ReportError(tw, SID_ERR_OUT_OF_MEMORY, NULL, NULL);
|
|
return STATE_ABORT;
|
|
}
|
|
}
|
|
|
|
if (pParams->bInHead)
|
|
(*pParams->target->isa->end_element)(pParams->target, HTML_ADDRESS);
|
|
else
|
|
(*pParams->target->isa->end_element)(pParams->target, HTML_PRE);
|
|
}
|
|
return STATE_DONE;
|
|
|
|
case STATE_ABORT:
|
|
if (pParams->references)
|
|
GTR_FREE(pParams->references);
|
|
if (pParams->newsgroups)
|
|
GTR_FREE(pParams->newsgroups);
|
|
if (pParams->subject)
|
|
GTR_FREE(pParams->subject);
|
|
*pParams->pStatus = -1;
|
|
return STATE_DONE;
|
|
}
|
|
XX_Assert((0), ("Function called with illegal state: %d", nState));
|
|
return STATE_DONE;
|
|
}
|
|
/****************************************************************************/
|
|
/* Main protocol/loading code */
|
|
|
|
struct Data_LoadNews {
|
|
HTRequest * request;
|
|
int * pStatus;
|
|
|
|
int response; /* RFC 977 numerical response */
|
|
struct MultiAddress address;
|
|
unsigned short port;
|
|
unsigned long where; /* Where we connected to */
|
|
int net_status;
|
|
int s;
|
|
enum {ARTICLE, GROUP, LIST} reqtype;
|
|
int nFirst; /* First article desired for list */
|
|
int nLast; /* Last article desired */
|
|
char * pResText; /* Response text from server */
|
|
HTInputSocket * isoc;
|
|
BOOL bWaiting;
|
|
HTStructured * target;
|
|
};
|
|
|
|
#define STATE_NEWS_GOTHOST (STATE_OTHER)
|
|
#define STATE_NEWS_CONNECTED (STATE_OTHER + 1)
|
|
#define STATE_NEWS_GOTGREETING (STATE_OTHER + 2)
|
|
#define STATE_NEWS_READY (STATE_OTHER + 3)
|
|
#define STATE_NEWS_SENTCOMMAND (STATE_OTHER + 4)
|
|
#define STATE_NEWS_READDONE (STATE_OTHER + 5)
|
|
|
|
static void News_CleanUp(struct Mwin *tw, struct Data_LoadNews *pData)
|
|
{
|
|
XX_Assert((!pData->target), ("News_CleanUp: target not freed!"));
|
|
if (pData->bWaiting)
|
|
WAIT_Pop(tw);
|
|
if (pData->pResText)
|
|
GTR_FREE(pData->pResText);
|
|
if (pData->isoc)
|
|
HTInputSocket_free(pData->isoc);
|
|
}
|
|
|
|
static int News_DoInit(struct Mwin *tw, void **ppInfo)
|
|
{
|
|
struct Params_LoadAsync *pParams;
|
|
struct Data_LoadNews *pData;
|
|
struct Params_MultiParseInet *ppi;
|
|
|
|
pParams = *ppInfo;
|
|
|
|
/* Copy the parameters we were passed into our own, larger structure. */
|
|
pData = GTR_CALLOC(sizeof(struct Data_LoadNews), 1);
|
|
if (pData)
|
|
{
|
|
memset(pData, 0, sizeof(*pData));
|
|
pData->request = pParams->request;
|
|
pData->pStatus = pParams->pStatus;
|
|
GTR_FREE(pParams);
|
|
*ppInfo = pData;
|
|
}
|
|
else
|
|
{
|
|
ERR_ReportError(tw, SID_ERR_OUT_OF_MEMORY, NULL, NULL);
|
|
return STATE_ABORT;
|
|
}
|
|
|
|
/* See if we have a cached NNTP connection whose socket is still open.
|
|
We don't really check here to see if it's the correct host, since
|
|
we assume that the host changes rarely, if ever. */
|
|
if (tw->cached_conn.type == CONN_NNTP)
|
|
{
|
|
if (!Net_FlushSocket(tw->cached_conn.socket))
|
|
{
|
|
/* Great! Let's go with it... */
|
|
pData->isoc = HTInputSocket_new(tw->cached_conn.socket);
|
|
return STATE_NEWS_READY;
|
|
}
|
|
else
|
|
{
|
|
/* We had a news connection, but it shut down. */
|
|
TW_DisposeConnection(&tw->cached_conn);
|
|
}
|
|
}
|
|
|
|
if (!gPrefs.szNNTP_Server[0])
|
|
{
|
|
/* We have no news server configured */
|
|
ERR_ReportError(tw, SID_ERR_NO_NEWS_SERVER_CONFIGURED, NULL, NULL);
|
|
*pData->pStatus = -1;
|
|
return STATE_DONE;
|
|
}
|
|
|
|
/* Figure out address for news host. */
|
|
pData->port = WS_HTONS(NEWS_PORT);
|
|
ppi = GTR_CALLOC(sizeof(*ppi), 1);
|
|
if (ppi)
|
|
{
|
|
ppi->pAddress = &pData->address;
|
|
ppi->pPort = &pData->port;
|
|
ppi->str = gPrefs.szNNTP_Server;
|
|
ppi->pStatus = &pData->net_status;
|
|
Async_DoCall(Net_MultiParse_Async, ppi);
|
|
return STATE_NEWS_GOTHOST;
|
|
}
|
|
else
|
|
{
|
|
ERR_ReportError(tw, SID_ERR_OUT_OF_MEMORY, NULL, NULL);
|
|
return STATE_ABORT;
|
|
}
|
|
}
|
|
|
|
static int News_DoGotHost(struct Mwin *tw, void **ppInfo)
|
|
{
|
|
struct Data_LoadNews *pData;
|
|
|
|
pData = *ppInfo;
|
|
|
|
if (pData->net_status < 0)
|
|
{
|
|
XX_DMsg(DBG_LOAD, ("Net_Parse_Async returned %d\n", pData->net_status));
|
|
*pData->pStatus = -1;
|
|
News_CleanUp(tw, pData);
|
|
return STATE_DONE;
|
|
}
|
|
|
|
/* Try to establish a new connection */
|
|
WAIT_Push(tw, waitSameInteract, GTR_GetString(SID_INF_CONNECTING_TO_NEWS_SERVER));
|
|
pData->bWaiting = TRUE;
|
|
{
|
|
/* Do connect call */
|
|
struct Params_MultiConnect *ppc;
|
|
|
|
ppc = GTR_CALLOC(sizeof(*ppc), 1);
|
|
if (ppc)
|
|
{
|
|
ppc->pSocket = &pData->s;
|
|
ppc->pAddress = &pData->address;
|
|
ppc->nPort = pData->port;
|
|
ppc->pWhere = &pData->where;
|
|
ppc->pStatus = &pData->net_status;
|
|
|
|
#ifdef FEATURE_SOCKS_LOW_LEVEL
|
|
ppc->bUseSocksProxy = pData->request->destination->bUseSocksProxy;
|
|
#endif
|
|
|
|
Async_DoCall(Net_MultiConnect_Async, ppc);
|
|
}
|
|
else
|
|
{
|
|
ERR_ReportError(tw, SID_ERR_OUT_OF_MEMORY, NULL, NULL);
|
|
return STATE_ABORT;
|
|
}
|
|
}
|
|
return STATE_NEWS_CONNECTED;
|
|
}
|
|
|
|
static int News_DoConnected(struct Mwin *tw, void **ppInfo)
|
|
{
|
|
struct Data_LoadNews *pData;
|
|
|
|
pData = *ppInfo;
|
|
|
|
if (pData->bWaiting)
|
|
{
|
|
WAIT_Pop(tw);
|
|
pData->bWaiting = FALSE;
|
|
}
|
|
|
|
if (pData->net_status < 0)
|
|
{
|
|
*pData->pStatus = -1;
|
|
News_CleanUp(tw, pData);
|
|
return STATE_DONE;
|
|
}
|
|
|
|
pData->isoc = HTInputSocket_new(pData->s);
|
|
|
|
/* Get initial response from server */
|
|
{
|
|
struct Params_News_Command *pnc;
|
|
|
|
pnc = GTR_CALLOC(sizeof(*pnc), 1);
|
|
if (pnc)
|
|
{
|
|
pnc->isoc = pData->isoc;
|
|
pnc->cmd = NULL;
|
|
pnc->pResult = &pData->response;
|
|
pnc->ppResText = NULL;
|
|
Async_DoCall(News_Command_Async, pnc);
|
|
return STATE_NEWS_GOTGREETING;
|
|
}
|
|
else
|
|
{
|
|
ERR_ReportError(tw, SID_ERR_OUT_OF_MEMORY, NULL, NULL);
|
|
return STATE_ABORT;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int News_DoGotGreeting(struct Mwin *tw, void **ppInfo)
|
|
{
|
|
struct Data_LoadNews *pData;
|
|
|
|
pData = *ppInfo;
|
|
|
|
/* Let's see what the server had to say about us connecting... */
|
|
switch (pData->response)
|
|
{
|
|
case 200: /* OK, allowed to post */
|
|
case 201: /* OK, posting not allowed */
|
|
/* Dispose our old cached connection and cache this one instead */
|
|
TW_DisposeConnection(&tw->cached_conn);
|
|
tw->cached_conn.type = CONN_NNTP;
|
|
tw->cached_conn.addr = pData->where;
|
|
tw->cached_conn.socket = pData->s;
|
|
/* Now that we've cached it, we don't want to close the socket
|
|
independently of the cached connection. */
|
|
pData->s = 0;
|
|
return STATE_NEWS_READY;
|
|
|
|
case 502:
|
|
ERR_ReportError(tw, SID_ERR_NO_ACCESS_TO_NEWS_SERVER_S, gPrefs.szNNTP_Server, NULL);
|
|
default:
|
|
/* There was some sort of an error. Regardless of what it was,
|
|
shut down our connection and give up. */
|
|
Net_Close(pData->s);
|
|
*pData->pStatus = -1;
|
|
News_CleanUp(tw, pData);
|
|
return STATE_DONE;
|
|
}
|
|
XX_Assert((0), ("News: shouldn't get here!"));
|
|
}
|
|
|
|
static int News_DoReady(struct Mwin *tw, void **ppInfo)
|
|
{
|
|
struct Data_LoadNews *pData;
|
|
char *arg;
|
|
char *command;
|
|
|
|
pData = *ppInfo;
|
|
|
|
/* What was it the user wanted in the first place? */
|
|
arg = pData->request->destination->szActualURL;
|
|
arg += 5; /* skip over "news:" part */
|
|
|
|
command = GTR_CALLOC(512, 1); /* Will be freed by News_Command_Async. */
|
|
if (!command)
|
|
{
|
|
ERR_ReportError(tw, SID_ERR_OUT_OF_MEMORY, NULL, NULL);
|
|
return STATE_ABORT;
|
|
}
|
|
|
|
if (strchr(arg, '@'))
|
|
{
|
|
/* Person is looking for an article */
|
|
pData->reqtype = ARTICLE;
|
|
strcpy(command, "ARTICLE ");
|
|
if (!strchr(arg, '<'))
|
|
strcat(command, "<");
|
|
strcat(command, arg);
|
|
if (!strchr(arg, '>'))
|
|
strcat(command, ">");
|
|
WAIT_Push(tw, waitSameInteract, GTR_GetString(SID_INF_RETRIEVING_NEWS_ARTICLE));
|
|
pData->bWaiting = TRUE;
|
|
}
|
|
else
|
|
{
|
|
if (strchr(arg, '*'))
|
|
{
|
|
/* The user wants a list of newsgroups */
|
|
pData->reqtype = LIST;
|
|
strcpy(command, "LIST NEWSGROUPS");
|
|
WAIT_Push(tw, waitSameInteract, GTR_GetString(SID_INF_RETRIEVING_NEWS_GROUP_LIST));
|
|
pData->bWaiting = TRUE;
|
|
}
|
|
else
|
|
{
|
|
/* The user wants group contents */
|
|
char *p;
|
|
pData->reqtype = GROUP;
|
|
/* You can specify a numerical range in a group with
|
|
the form "news:group/start-end" */
|
|
p = strchr(arg, '/');
|
|
strcpy(command, "GROUP ");
|
|
if (p)
|
|
{
|
|
/* Parse out the group name, and first and last articles */
|
|
strncat(command, arg, p - arg);
|
|
/* (The command will still be null-terminated since we memset
|
|
it to 0 before we started */
|
|
pData->nFirst = atoi(p + 1);
|
|
p = strchr(p, '-');
|
|
if (p)
|
|
pData->nLast = atoi(p + 1);
|
|
}
|
|
else
|
|
{
|
|
strcat(command, arg);
|
|
}
|
|
WAIT_Push(tw, waitSameInteract, GTR_GetString(SID_INF_RETRIEVING_NEWS_ARTICLE_LIST));
|
|
pData->bWaiting = TRUE;
|
|
}
|
|
}
|
|
strcat(command, "\015\012");
|
|
|
|
/* We've composed the command. Send it off to the server */
|
|
{
|
|
struct Params_News_Command *pnc;
|
|
|
|
pnc = GTR_CALLOC(sizeof(*pnc), 1);
|
|
if (pnc)
|
|
{
|
|
pnc->isoc = pData->isoc;
|
|
pnc->cmd = command;
|
|
pnc->pResult = &pData->response;
|
|
if (pData->reqtype == GROUP)
|
|
pnc->ppResText = &pData->pResText;
|
|
else
|
|
pnc->ppResText = NULL;
|
|
Async_DoCall(News_Command_Async, pnc);
|
|
return STATE_NEWS_SENTCOMMAND;
|
|
}
|
|
else
|
|
{
|
|
ERR_ReportError(tw, SID_ERR_OUT_OF_MEMORY, NULL, NULL);
|
|
return STATE_ABORT;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int News_DoSentCommand(struct Mwin *tw, void **ppInfo)
|
|
{
|
|
struct Data_LoadNews *pData;
|
|
|
|
pData = *ppInfo;
|
|
|
|
if ((pData->response / 100) != 2)
|
|
{
|
|
/* An error occurred. */
|
|
if (pData->reqtype == GROUP && pData->response == 411)
|
|
{
|
|
ERR_ReportError(tw, SID_ERR_SERVER_DOES_NOT_CARRY_GROUP, NULL, NULL);
|
|
}
|
|
if (pData->response < 0 || pData->response == 400)
|
|
{
|
|
/* Either an error occured or service was disconnected.
|
|
Either way, close our connection. */
|
|
TW_DisposeConnection(&tw->cached_conn);
|
|
}
|
|
*pData->pStatus = -1;
|
|
News_CleanUp(tw, pData);
|
|
return STATE_DONE;
|
|
}
|
|
|
|
/* Successful response. Read data from server. */
|
|
switch (pData->reqtype)
|
|
{
|
|
case ARTICLE:
|
|
{
|
|
struct Params_ReadArticle *pra;
|
|
|
|
pData->target = HTML_new(tw, pData->request, NULL, WWW_HTML,
|
|
pData->request->output_format, pData->request->output_stream);
|
|
pra = GTR_CALLOC(sizeof(*pra), 1);
|
|
if (pra)
|
|
{
|
|
pra->isoc = pData->isoc;
|
|
pra->pStatus = &pData->net_status;
|
|
pra->target = pData->target;
|
|
Async_DoCall(News_ReadArticle_Async, pra);
|
|
return STATE_NEWS_READDONE;
|
|
}
|
|
else
|
|
{
|
|
ERR_ReportError(tw, SID_ERR_OUT_OF_MEMORY, NULL, NULL);
|
|
return STATE_ABORT;
|
|
}
|
|
}
|
|
case GROUP:
|
|
{
|
|
struct Params_ReadGroup *prg;
|
|
char szGroup[256];
|
|
int n, f, l;
|
|
|
|
/* Response to group command gives count and first/last articles numbers.
|
|
See section 3.2.2 of RFC 977. */
|
|
sscanf(pData->pResText, "%*d %d %d %d %s ", &n, &f, &l, szGroup);
|
|
if (pData->nFirst && pData->nLast)
|
|
{
|
|
if (pData->nFirst < f)
|
|
pData->nFirst = f;
|
|
if (pData->nLast > l)
|
|
pData->nLast = l;
|
|
}
|
|
else
|
|
{
|
|
pData->nLast = l;
|
|
if (l - f + 1 > MAX_CHUNK)
|
|
pData->nFirst = pData->nLast - CHUNK_SIZE + 1;
|
|
else
|
|
pData->nFirst = f;
|
|
}
|
|
GTR_FREE(pData->pResText);
|
|
pData->pResText = NULL;
|
|
|
|
if (l < f || l == 0)
|
|
{
|
|
ERR_ReportError(tw, SID_ERR_NO_ARTICLES_IN_GROUP_S, szGroup, NULL);
|
|
}
|
|
else if (pData->nLast < pData->nFirst || pData->nLast == 0)
|
|
{
|
|
ERR_ReportError(tw, SID_ERR_INVALID_ARTICLE_RANGE, NULL, NULL);
|
|
}
|
|
else
|
|
{
|
|
pData->target = HTML_new(tw, pData->request, NULL, WWW_HTML,
|
|
pData->request->output_format, pData->request->output_stream);
|
|
|
|
prg = GTR_CALLOC(sizeof(*prg), 1);
|
|
if (prg)
|
|
{
|
|
prg->isoc = pData->isoc;
|
|
prg->pStatus = &pData->net_status;
|
|
prg->target = pData->target;
|
|
prg->nFirst = pData->nFirst;
|
|
prg->nLast = pData->nLast;
|
|
prg->nFirstInGroup = f;
|
|
prg->nLastInGroup = l;
|
|
prg->nArticleCount = n;
|
|
strcpy(prg->szGroup, szGroup);
|
|
Async_DoCall(News_ReadGroup_Async, prg);
|
|
}
|
|
else
|
|
{
|
|
ERR_ReportError(tw, SID_ERR_OUT_OF_MEMORY, NULL, NULL);
|
|
return STATE_ABORT;
|
|
}
|
|
}
|
|
return STATE_NEWS_READDONE;
|
|
}
|
|
case LIST:
|
|
{
|
|
struct Params_ReadList *prl;
|
|
|
|
pData->target = HTML_new(tw, pData->request, NULL, WWW_HTML,
|
|
pData->request->output_format, pData->request->output_stream);
|
|
|
|
prl = GTR_CALLOC(sizeof(*prl), 1);
|
|
if (prl)
|
|
{
|
|
prl->isoc = pData->isoc;
|
|
prl->pStatus = &pData->net_status;
|
|
prl->target = pData->target;
|
|
Async_DoCall(News_ReadList_Async, prl);
|
|
return STATE_NEWS_READDONE;
|
|
}
|
|
else
|
|
{
|
|
ERR_ReportError(tw, SID_ERR_OUT_OF_MEMORY, NULL, NULL);
|
|
return STATE_ABORT;
|
|
}
|
|
}
|
|
default:
|
|
XX_Assert((0), ("Illegal news type: %d", pData->reqtype));
|
|
return STATE_DONE;
|
|
}
|
|
}
|
|
|
|
static int News_DoReadDone(struct Mwin *tw, void **ppInfo)
|
|
{
|
|
struct Data_LoadNews *pData;
|
|
|
|
pData = *ppInfo;
|
|
|
|
if (pData->target)
|
|
{
|
|
(*pData->target->isa->free)(pData->target);
|
|
pData->target = NULL;
|
|
}
|
|
|
|
if (pData->net_status < 0)
|
|
{
|
|
/* A network error occured. Discard our connection. */
|
|
TW_DisposeConnection(&tw->cached_conn);
|
|
*pData->pStatus = -1;
|
|
}
|
|
else
|
|
{
|
|
*pData->pStatus = HT_LOADED;
|
|
}
|
|
News_CleanUp(tw, pData);
|
|
return STATE_DONE;
|
|
}
|
|
|
|
static int News_DoAbort(struct Mwin *tw, void **ppInfo)
|
|
{
|
|
struct Data_LoadNews *pData;
|
|
|
|
pData = *ppInfo;
|
|
|
|
if (pData->target)
|
|
{
|
|
(*pData->target->isa->free)(pData->target);
|
|
pData->target = NULL;
|
|
}
|
|
News_CleanUp(tw, pData);
|
|
return STATE_DONE;
|
|
}
|
|
|
|
static int HTLoadNews_Async(struct Mwin *tw, int nState, void **ppInfo)
|
|
{
|
|
switch (nState)
|
|
{
|
|
case STATE_INIT:
|
|
return News_DoInit(tw, ppInfo);
|
|
case STATE_NEWS_GOTHOST:
|
|
return News_DoGotHost(tw, ppInfo);
|
|
case STATE_NEWS_CONNECTED:
|
|
return News_DoConnected(tw, ppInfo);
|
|
case STATE_NEWS_GOTGREETING:
|
|
return News_DoGotGreeting(tw, ppInfo);
|
|
case STATE_NEWS_READY:
|
|
return News_DoReady(tw, ppInfo);
|
|
case STATE_NEWS_SENTCOMMAND:
|
|
return News_DoSentCommand(tw, ppInfo);
|
|
case STATE_NEWS_READDONE:
|
|
return News_DoReadDone(tw, ppInfo);
|
|
case STATE_ABORT:
|
|
return News_DoAbort(tw, ppInfo);
|
|
}
|
|
XX_Assert((0), ("Function called with illegal state: %d", nState));
|
|
return STATE_DONE;
|
|
}
|
|
|
|
|
|
GLOBALDEF PUBLIC HTProtocol HTNews = {"news", NULL, HTLoadNews_Async};
|
|
|
|
#endif /* !_GIBRALTAR */
|