src/http.c

/* [<][>]
[^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following functions.
  1. http_html_prelude
  2. rfc1122_date
  3. small_date
  4. http_file_prelude
  5. http_emit_header
  6. http_emit_footer
  7. http_bad_url
  8. url_get_file
  9. render_idx
  10. html_news
  11. html_td
  12. html_td_news
  13. html_tdb
  14. html_tdl
  15. add_i
  16. html_tdi
  17. add_f
  18. html_tdf
  19. html_tdpercent
  20. html_tdd
  21. html_tdt
  22. html_newsgroup
  23. MAC
  24. MAC
  25. MAC
  26. MAC
  27. MAC
  28. MAC_BIG
  29. MAC
  30. MAC
  31. server_downtime
  32. server_uptime
  33. MAC
  34. MAC
  35. MAC
  36. MAC
  37. MAC
  38. MAC
  39. MAC
  40. macro_call
  41. macro_parse
  42. http_macro
  43. url_rewrite
  44. http_get
  45. http_cmd
  46. httpHandler

/* $Id: http.c,v 1.6 2002/03/26 11:18:35 proff Exp $
 * $Copyright$
 */

#include "nglobal.h"

#include "acc.h"
#include "group.h"
#include "nlist.h"
#include "ll.h"

#include "confused_runtime.h"

#include "http.h"

#define HTTP "HTTP/1.0"
#define TABLE "<table border=2>\n"
#define ENDTABLE "</table>\n"

struct macro_func
{
    char *name;
    void (*func)(struct strStack *out, int argc, char **argv);
    int min_args;
};

static void http_html_prelude (char *msg)
/* [<][>][^][v][top][bottom][index][help] */
{
        emitf("\
%s %s\r\n\
Server: NNTPCache %s\r\n\
Connection: close\r\n\
Content-Type: text/html\r\n\r\n", HTTP, msg, VERSION);
}

EXPORT char *rfc1122_date (time_t ti)
/* [<][>][^][v][top][bottom][index][help] */
{
    static char buf[80];
    struct tm *tm;
    tm = gmtime(&ti);
    if (!tm)
        {
            loge (("gmtime() failed"));
            return "time error"; /* XXX */
        }
    
    strftime (buf, sizeof buf, "%d %b %Y %H:%M:%S %Z", tm);
    return buf;
}

static char *small_date (time_t ti)
/* [<][>][^][v][top][bottom][index][help] */
{
    static char buf[80];
    struct tm *tm;
    tm = localtime(&ti);
    if (!tm)
        {
            loge (("localtime() failed"));
            return "time error"; /* XXX */
        }
    strftime (buf, sizeof buf, (time(NULL)-ti > 3600*24*180)? "%d %b %y %H:%M:%S" : "%d %b %H:%M:%S", tm);
    return buf;
}

static void http_file_prelude (char *url, int len, time_t modified)
/* [<][>][^][v][top][bottom][index][help] */
{
    char *p;
    char *content = "text/plain";
    char *pragma;
    p = strrchr (url, '.');
    if (p)
        {
            p++;
            if (strCaseEq (p, "html") || strCaseEq (p, "htm"))
                content = "text/html";
            else
            if (strCaseEq (p, "gif"))
                content = "image/gif";
            else
            if (strCaseEq (p, "jpg") || strCaseEq (p, "jpeg"))
                content = "image/jpeg";
        }
    if (modified == 0)
        {
            modified = time(NULL);
            pragma = "Pragma: NoCache\r\n";
        }
    else
        {
            pragma = "";
        }
    emitf("\
%s 200 ok\r\n\
Server: NNTPCache %s\r\n\
Content-Type: %s\r\n\
Content-Length: %d\r\n\
Connection: close\r\n\
%s", HTTP, VERSION, content, len, pragma);
    emitf("Last-Modified: %s\r\n", rfc1122_date(modified));
    emitf("Date: %s\r\n\r\n", rfc1122_date(time(NULL)));
}

static void http_emit_header (char *status, char *title)
/* [<][>][^][v][top][bottom][index][help] */
{
    http_html_prelude (status);
    emitf ("\
<HTML>\n\
<HEAD>\n\
<TITLE>%s</TITLE>\n\
</HEAD>\n\
<BODY>\n", title);
}

static void http_emit_footer ()
/* [<][>][^][v][top][bottom][index][help] */
{
    emitf ("\
</BODY>\n\
</HTML>\n");
}

static void http_bad_url (char *url)
/* [<][>][^][v][top][bottom][index][help] */
{
    http_emit_header (HTTP_STATUS_NOTFOUND, "File not found");
    emitf ("<H1>%s</H1>\nThe requested URL %s was not found on this server\n", "File not found", url);
    http_emit_footer ();
}

static char *url_get_file (char *url, int *len, char **outfn, time_t *modified)
/* [<][>][^][v][top][bottom][index][help] */
{
    struct stat st;
    int fd = -1;
    char *p;
    char *fn;
    if (strnEq (url, "../", 3) || strstr (url, "/.."))
        {
            logwn (("hack attempt -- URL contains \"/..\" or \"../\": '%.128s'", url));
            return NULL;
        }
    for (fn = url; *fn && *fn == '/'; fn++) {}
    if (fn[0] == '\0')
        fn = "index.html";
    *outfn = fn;
    if (stat (fn, &st) != 0 || st.st_size < 1)
        return NULL;
    if (st.st_mode & S_IFDIR)
        {
            static char path[MAX_PATH]; /* note static */
            snprintf(path, sizeof path, "%s/index.html", fn);
            *outfn = fn = path;
            if (stat (fn, &st) != 0 || st.st_size < 1)
                return NULL;
        }
    *outfn = fn;
    fd = open(fn, O_RDONLY);
    if (fd<0)
        return NULL;
    p = Smalloc (st.st_size);
    if (read (fd, p, st.st_size) != st.st_size)
        {
            loge (("read ('%s') failed", fn));
            free (p);
            close (fd);
            return NULL;
        }
    *len = st.st_size;
    *modified = st.st_mtime;
    return p;
}

static char *render_idx(struct confused_idx *idx, char **t)
/* [<][>][^][v][top][bottom][index][help] */
{
    static char buf[MAX_LINE] = "unknown";
    struct strList *sl;
    char *p;
    int l;
    char *ret = buf;
    switch (idx->type)
        {
        case cf_string:
            *t = "string";
            ret = *(char**)idx->data;
            break;
        case cf_stringl:
            *t = "list";
            for (p=buf, sl = *(struct strList**)idx->data; sl; sl=sl->next)
                {
                    l = strlen(sl->data);
                    if (!l)
                        continue;
                    if (p != buf)
                        {
                            memcpy(p, ", ", 3);
                            p += 2;
                        }
                    memcpy(p, sl->data, l+1);
                    p += l;
                }
            break;
        case cf_bool:
            *t = "bool";
            ret = (*(bool*)idx->data)? "true": "false";
            break;
        case cf_int:    
            *t = "int";
            sprintf(buf, "%d", *(int*)idx->data);
            break;
        case cf_time:   
            *t = "time";
            ret = nnitod(*(long*)idx->data);
            break;
        default:
            *t = "unknown";
            break;
        }
    return ret;
}
    
static void html_news(struct strStack *out, char *s)
/* [<][>][^][v][top][bottom][index][help] */
{
    strStackAdd(out, "<a href=\"news:");
    strStackAdd(out, s);
    strStackAdd(out, "\">");
    strStackAdd(out, s);
    strStackAdd(out, "</a>");
}

static void html_td(struct strStack *out, char *s, char *align)
/* [<][>][^][v][top][bottom][index][help] */
{
    strStackAdd(out, "<td align=");
    strStackAdd(out, align);
    strStackAdd(out, ">");
    if (s && s[0])
        strStackAdd(out, s);
    strStackAdd(out, "</td>");
}

static void html_td_news(struct strStack *out, char *s, char *align)
/* [<][>][^][v][top][bottom][index][help] */
{
    strStackAdd(out, "<td align=");
    strStackAdd(out, align);
    strStackAdd(out, ">");
    if (s)
        html_news(out, s);
    strStackAdd(out, "</td>");
}

static void html_tdb(struct strStack *out, big_t big, char *align)
/* [<][>][^][v][top][bottom][index][help] */
{
    if (big == 0)
        html_td(out, NULL, align);
    else
        html_td(out, bigToStr(big), align);
}

static void html_tdl(struct strStack *out, big_t big, char *align)
/* [<][>][^][v][top][bottom][index][help] */
{
    if (big == 0)
        html_td(out, NULL, align);
    else
        html_td(out, (conv(big)[0] == '0')? "": conv(big), align);
}

static void add_i(struct strStack *out, int i)
/* [<][>][^][v][top][bottom][index][help] */
{
    char buf [128];
    sprintf(buf, "%d", i);
    strStackAdd(out, buf);
}

static void html_tdi(struct strStack *out, int i, char *align)
/* [<][>][^][v][top][bottom][index][help] */
{
    char buf [32];
    if (i == 0) 
        {
        html_td(out, NULL, align);
        return;
        }
    sprintf(buf, "%d", i);
    html_td(out, buf, align);
}

static void add_f(struct strStack *out, float f)
/* [<][>][^][v][top][bottom][index][help] */
{
    char buf [128];
    sprintf(buf, "%.02f", f);
    strStackAdd(out, buf);
}

static void html_tdf(struct strStack *out, float f, char *align)
/* [<][>][^][v][top][bottom][index][help] */
{
    char buf [128];
    if (f == 0.0)
        {
        html_td(out, NULL, align);
        return;
        }
    sprintf(buf, "%.02f", f);
    html_td(out, buf, align);
}

static void html_tdpercent(struct strStack *out, float f, char *align)
/* [<][>][^][v][top][bottom][index][help] */
{
    char buf [128];
    if (f == 0.0)
        {
        html_td(out, NULL, align);
        return;
        }
    sprintf(buf, "%.2f%%", f*100.0);
    html_td(out, buf, align);
}

static void html_tdd(struct strStack *out, time_t ti, char *align)
/* [<][>][^][v][top][bottom][index][help] */
{
    if (ti == 0)
        {
        html_td(out, NULL, align);
        return;
        }
    html_td(out, small_date(ti), align);
}

static void html_tdt(struct strStack *out, long i, char *align)
/* [<][>][^][v][top][bottom][index][help] */
{
    if (i == 0)
        html_td(out, NULL, align);
    else
        html_td(out, nnitod(i), align);
}

/* argv[0] == first newsgroup */

static void html_newsgroup(struct strStack *out, int argc, char **argv, struct newsgroup *ng)
/* [<][>][^][v][top][bottom][index][help] */
{
    int n;
    strStackAdd(out, "<tr>");
    for (n=0; n<argc; n++)
        {
            char *s = argv[n];
            char *p = strchr(s, '-');
            if (p)
                *p = '\0';
            if (strCaseEq(s, "Group")) html_td_news(out, ng->group, "left"); else
            if (strCaseEq(s, "Messages")) html_tdi(out, ng->msgs, "right"); else
            if (strCaseEq(s, "Lo")) html_tdi(out, ng->lo, "right"); else
            if (strCaseEq(s, "LoServer")) html_tdi(out, ng->lo_server, "right"); else
            if (strCaseEq(s, "Hi")) html_tdi(out, ng->hi, "right"); else
            if (strCaseEq(s, "HiServer")) html_tdi(out, ng->hi_server, "right"); else
            if (strCaseEq(s, "LoXover")) html_tdi(out, ng->lo_xover, "right"); else
            if (strCaseEq(s, "HiXover")) html_tdi(out, ng->hi_xover, "right"); else
            if (strCaseEq(s, "Mod")) {char b[2]="y"; b[0]=ng->moderation; html_td(out, b, "center");} else
            if (strCaseEq(s, "Creator")) html_td(out, ng->creator, "center"); else
            if (strCaseEq(s, "Creation")) html_tdd(out, ng->creation_time, "right"); else
            if (strCaseEq(s, "Rebuild")) html_tdd(out, ng->last_rebuild, "right"); else
            if (strCaseEq(s, "GroupTime")) html_tdd(out, ng->group_time, "right"); else
            if (strCaseEq(s, "GroupChange")) html_tdd(out, ng->group_change_time, "right"); else
            if (strCaseEq(s, "ListGroup")) html_tdd(out, ng->listgroup_time, "right"); else
            if (strCaseEq(s, "Description")) html_td(out, ng->desc, "left"); else
            if (strCaseEq(s, "Server")) {struct server_cfg *scfg = getServerGroup(ng->group); html_td(out, scfg? scfg->host: NULL, "right");} else
            {logen (("unkown parameter '%s' in @@%s@@", s, argv[0])); strStackAdd(out, "<td></td>");}
            if (p)
                *p = '-';
        }
    strStackAdd(out, "</tr>\n");
}

#define MAC(x) \
static void macro_ ## x (struct strStack *out, int argc, char **argv)
#define add(x) (strStackAdd(out, (x)))
#define MAC_BIG(x) MAC(x) {if (Stats->x) add(bigToStr(Stats->x));}
#define MAC_STR(x) MAC(x) {add(x);}
#define MAC_LEN(x) MAC(x) {add((conv(Stats->x)[0] == '0')? "": conv(Stats->x));}
#define MAC_TIM(x) MAC(x) {add(small_date((Stats->x)));}
#define CPU2BIG(x) ((x)/(CLK_TCK))
MAC(version)    {add(VERSION);}
/* [<][>][^][v][top][bottom][index][help] */
MAC(hostname)   {add(Host);}
/* [<][>][^][v][top][bottom][index][help] */
MAC(date)       {add(small_date(time(NULL)));}
/* [<][>][^][v][top][bottom][index][help] */
MAC(clienthost) {add(ClientHost);}
/* [<][>][^][v][top][bottom][index][help] */
MAC(efficiency) {add_f(out, (1.0-((float)(Stats->serverFromBytes+Stats->serverToBytes+1)/(float)(Stats->clientToBytes+Stats->clientFromBytes+1)))*100.0);}
/* [<][>][^][v][top][bottom][index][help] */

MAC_BIG(IPCfromChild)
/* [<][>][^][v][top][bottom][index][help] */
MAC_LEN(IPCfromChildBytes)
MAC_BIG(IPCtoChild)
MAC_LEN(IPCtoChildBytes)
MAC_BIG(articlesExpired)
MAC_BIG(clientConnects)
MAC_BIG(clientConnectsFailed)
MAC_LEN(clientFromBytes)
MAC_LEN(clientToBytes)
MAC_BIG(clientsActive)
MAC_BIG(crossposts)
MAC_LEN(crosspostsBytes)
MAC_BIG(groupsCached)
MAC_BIG(groupsExpired)
MAC_BIG(xoversExpired)
MAC_BIG(historyFetches)
MAC_LEN(historySize)
MAC_BIG(historyStores)
MAC_TIM(masterStarted)
MAC_BIG(posts)
MAC_BIG(postsCross)
MAC_LEN(postsBytes)
MAC_BIG(postsFailed)
MAC_BIG(serverConnects)
MAC_BIG(serverConnectsFailed)
MAC_LEN(serverFromBytes)
MAC_LEN(serverToBytes)
MAC_BIG(invocations)
MAC_TIM(statsStarted)

MAC(conftable)
{
    struct confused_idx *idx;
    strStackAdd(out, TABLE);
    strStackAdd(out, "<tr><th align=left>Type</th><th align=left>Name</th><th align=left>Value</th></tr>\n"); 
    for (idx = nnconf_idx; idx->name; idx++)
        {
            char *type;
            char *val;
            strStackAdd(out, "<tr>");
            val = render_idx (idx, &type);
            html_td(out, type, "left");
            html_td(out, idx->name, "left");
            html_td(out, val, "left");
            strStackAdd(out, "</tr>\n");
        }
    strStackAdd(out, ENDTABLE);
}

MAC(html_th)
/* [<][>][^][v][top][bottom][index][help] */
{
    int n;
    strStackAdd(out, "<tr>");
    for (n=0; n<argc; n++)
        {
            char *p;
            strStackAdd(out, "<th align=center>");
            p = strchr(argv[n], '-');
            strStackAdd(out, p? p+1: argv[n]);
            strStackAdd(out, "</th>");
        }
    strStackAdd(out, "</tr>\n");
}

MAC(html_table)
/* [<][>][^][v][top][bottom][index][help] */
{
    strStackAdd(out, TABLE);
    macro_html_th(out, argc, argv);
}

static int server_downtime(struct server_cfg *p)
/* [<][>][^][v][top][bottom][index][help] */
{
    
    if (p->share->server_down > p->share->server_up)
        return p->share->server_down_time + (time(NULL) - p->share->server_down);
    else
        return p->share->server_down_time;
}

static int server_uptime(struct server_cfg *p)
/* [<][>][^][v][top][bottom][index][help] */
{
    return p->share->server_up_time + ((p->share->server_up > p->share->server_down)? time(NULL) - p->share->server_up: 0);
}

MAC(servers)
/* [<][>][^][v][top][bottom][index][help] */
{
    struct server_cfg *p;
    macro_html_table(out, argc-1, argv+1);
    for (p = ServerList; p; p = p->next)
        {
            int n;
            strStackAdd(out, "<tr>");
            for (n=1; n<argc; n++)
                {
                    char *s = argv[n];
                    char *s2 = strchr(s, '-');
                    if (s2)
                        *s2 = '\0';
                    if (strCaseEq(s, "ActiveBytes")) html_tdl(out, p->share->list[l_active].bytes, "right"); else
                    if (strCaseEq(s, "ActiveEntries")) html_tdb(out, p->share->list[l_active].entries, "right"); else
                    if (strCaseEq(s, "ActiveLines")) html_tdb(out, p->share->list[l_active].lines, "right"); else
                    if (strCaseEq(s, "ActiveRebuildFail")) html_tdd(out, p->share->list[l_active].rebuild_fail, "right"); else
                    if (strCaseEq(s, "ActiveRebuildGood")) html_tdd(out, p->share->list[l_active].rebuild_good, "right"); else
                    if (strCaseEq(s, "ActiveRebuildRefused")) html_tdd(out, p->share->list[l_active].rebuild_refused, "right"); else
                    if (strCaseEq(s, "ActiveTimesBytes")) html_tdl(out, p->share->list[l_active_times].bytes, "right"); else 
                    if (strCaseEq(s, "ActiveTimesEntries")) html_tdb(out, p->share->list[l_active_times].entries, "right");  else
                    if (strCaseEq(s, "ActiveTimesLines")) html_tdb(out, p->share->list[l_active_times].lines, "right"); else
                    if (strCaseEq(s, "ActiveTimesRebuildFail")) html_tdd(out, p->share->list[l_active_times].rebuild_fail, "right"); else
                    if (strCaseEq(s, "ActiveTimesRebuildGood")) html_tdd(out, p->share->list[l_active_times].rebuild_good, "right"); else
                    if (strCaseEq(s, "ActiveTimesRebuildRefused")) html_tdd(out, p->share->list[l_active_times].rebuild_refused, "right"); else
                    if (strCaseEq(s, "Host")) html_td(out, p->host, "left"); else
                    if (strCaseEq(s, "NewsgroupsBytes")) html_tdl(out, p->share->list[l_newsgroups].bytes, "right"); else
                    if (strCaseEq(s, "NewsgroupsEntries")) html_tdb(out, p->share->list[l_newsgroups].entries, "right"); else
                    if (strCaseEq(s, "NewsgroupsRebuildGood")) html_tdd(out, p->share->list[l_newsgroups].rebuild_good, "right"); else
                    if (strCaseEq(s, "NewsgroupsLines")) html_tdb(out, p->share->list[l_newsgroups].lines, "right"); else
                    if (strCaseEq(s, "NewsgroupsRebuildFail")) html_tdd(out, p->share->list[l_newsgroups].rebuild_fail, "right"); else
                    if (strCaseEq(s, "NewsgroupsRebuildRefused")) html_tdd(out, p->share->list[l_newsgroups].rebuild_refused, "right"); else
                    if (strCaseEq(s, "OverviewFmtBytes")) html_tdl(out, p->share->list[l_overview_fmt].bytes, "right"); else
                    if (strCaseEq(s, "OverviewFmtEntries")) html_tdb(out, p->share->list[l_overview_fmt].entries, "right"); else
                    if (strCaseEq(s, "OverviewFmtLines")) html_tdb(out, p->share->list[l_overview_fmt].lines, "right"); else
                    if (strCaseEq(s, "OverviewFmtRebuildFail")) html_tdd(out, p->share->list[l_overview_fmt].rebuild_fail, "right"); else
                    if (strCaseEq(s, "OverviewFmtRebuildGood")) html_tdd(out, p->share->list[l_overview_fmt].rebuild_good, "right"); else
                    if (strCaseEq(s, "OverviewFmtRebuildRefused")) html_tdd(out, p->share->list[l_overview_fmt].rebuild_refused, "right"); else
                    if (strCaseEq(s, "ArticleFail")) html_tdb(out, p->share->article_fail, "right"); else
                    if (strCaseEq(s, "ArticleGood")) html_tdb(out, p->share->article_good, "right"); else
                    if (strCaseEq(s, "BodyFail")) html_tdb(out, p->share->body_fail, "right"); else
                    if (strCaseEq(s, "BodyGood")) html_tdb(out, p->share->body_good, "right"); else
                    if (strCaseEq(s, "BytesTo")) html_tdl(out, p->share->bytes_to, "right"); else
                    if (strCaseEq(s, "ConnectFail")) html_tdb(out, p->share->connect_fail, "right"); else
                    if (strCaseEq(s, "GroupFail")) html_tdb(out, p->share->group_fail, "right"); else
                    if (strCaseEq(s, "GroupGood")) html_tdb(out, p->share->group_good, "right"); else
                    if (strCaseEq(s, "HeadFail")) html_tdb(out, p->share->head_fail, "right"); else
                    if (strCaseEq(s, "HeadGood")) html_tdb(out, p->share->head_good, "right"); /* mmm. good head */ else
                    if (strCaseEq(s, "IhaveFail")) html_tdb(out, p->share->ihave_fail, "right"); else
                    if (strCaseEq(s, "IhaveGood")) html_tdb(out, p->share->ihave_good, "right"); else
                    if (strCaseEq(s, "ListgroupFail")) html_tdb(out, p->share->listgroup_fail, "right"); else
                    if (strCaseEq(s, "ListgroupGood")) html_tdb(out, p->share->listgroup_good, "right"); else
                    if (strCaseEq(s, "NewnewsFail")) html_tdb(out, p->share->newnews_fail, "right"); else
                    if (strCaseEq(s, "NewnewsGood")) html_tdb(out, p->share->newnews_good, "right"); else
                    if (strCaseEq(s, "NewsgroupsRebuild")) html_tdd(out, p->share->list[l_newsgroups].rebuild_good, "right"); else
                    if (strCaseEq(s, "OverviewFmtRebuild")) html_tdd(out, p->share->list[l_overview_fmt].rebuild_good, "right"); else
                    if (strCaseEq(s, "PostFail")) html_tdb(out, p->share->post_fail, "right"); else
                    if (strCaseEq(s, "PostGood")) html_tdb(out, p->share->post_good, "right"); else
                    if (strCaseEq(s, "RelayFail")) html_tdb(out, p->share->relay_fail, "right"); else
                    if (strCaseEq(s, "RelayGood")) html_tdb(out, p->share->relay_good, "right"); else
                    if (strCaseEq(s, "ServerDown")) html_tdd(out, p->share->server_down, "right"); else
                    if (strCaseEq(s, "ServerUp")) html_tdd(out, p->share->server_up, "right"); else
                    if (strCaseEq(s, "StatFail")) html_tdb(out, p->share->stat_fail, "right"); else
                    if (strCaseEq(s, "StatGood")) html_tdb(out, p->share->stat_good, "right"); else
                    if (strCaseEq(s, "Us")) html_td(out, p->us, "left"); else
                    if (strCaseEq(s, "XhdrFail")) html_tdb(out, p->share->xhdr_fail, "right"); else
                    if (strCaseEq(s, "XhdrGood")) html_tdb(out, p->share->xhdr_good, "right"); else
                    if (strCaseEq(s, "XoverFail")) html_tdb(out, p->share->xover_fail, "right"); else
                    if (strCaseEq(s, "XoverGood")) html_tdb(out, p->share->xover_good, "right"); else
                    if (strCaseEq(s, "ConnectGood")) html_tdb(out, p->share->connect_good, "right"); else
                    if (strCaseEq(s, "Availability")) {float f = (float)server_downtime(p)+(float)server_uptime(p); html_tdpercent(out, (f>0.0)? (float)server_uptime(p)/f: 0.0, "right");} else
                    if (strCaseEq(s, "BytesFrom")) html_tdl(out, p->share->bytes_from, "right"); else
                    if (strCaseEq(s, "DownTime")) html_tdt(out, server_downtime(p), "right"); else
                    if (strCaseEq(s, "UpTime")) html_tdt(out, server_uptime(p), "right"); else
                    {logen (("unkown parameter '%s' in @@%s@@", s, argv[0])); strStackAdd(out, "<td></td>");}
                    if (s2)
                        *s2 = '-';
                }
            strStackAdd(out, "</tr>\n");
        }
    strStackAdd(out, ENDTABLE);
}

MAC(cacheStats)
/* [<][>][^][v][top][bottom][index][help] */
{
    int m;
    macro_html_table(out, argc-1, argv+1);
    for (m = 0; m<c_none; m++)
        {
            int n;
            struct cache_stats *p = &Stats->cache_stats[m];
            strStackAdd(out, "<tr>");
            for (n=1; n<argc; n++)
                {
                    char *s = argv[n];
                    char *s2 = strchr(s, '-');
                    if (s2)
                        *s2 = '\0';
                    if (strCaseEq(s, "Type")) {struct command *c=commands;for(;c->cmd;c++) if (c->val == m) {html_td(out, c->cmd, "left");break;}} else
                    if (strCaseEq(s, "Requests")) html_tdb(out, p->requests, "right"); else
                    if (strCaseEq(s, "RequestsGood")) html_tdb(out, p->requests_good, "right"); else
                    if (strCaseEq(s, "RequestsFailed")) html_tdb(out, p->requests_failed, "right"); else
                    if (strCaseEq(s, "FilterBlocked")) html_tdb(out, p->filter_blocked, "right"); else
                    if (strCaseEq(s, "AuthBlocked")) html_tdb(out, p->auth_blocked, "right"); else
                    if (strCaseEq(s, "NocemBlocked")) html_tdb(out, p->nocem_blocked, "right"); else
                    if (strCaseEq(s, "ServerFromBytes")) html_tdl(out, p->serverFromBytes, "right"); else
                    if (strCaseEq(s, "ClientToBytes")) html_tdl(out, p->clientToBytes, "right"); else
                    if (strCaseEq(s, "ServerToBytes")) html_tdl(out, p->serverToBytes, "right"); else
                    if (strCaseEq(s, "ClientFromBytes")) html_tdl(out, p->clientFromBytes, "right"); else
                    if (strCaseEq(s, "ByMsgid")) html_tdb(out, p->by_msgid, "right"); else
                    if (strCaseEq(s, "ByArtnum")) html_tdb(out, p->by_artnum, "right"); else
                    {logen (("unkown parameter '%s' in @@%s@@", s, argv[0])); add("<td></td>");}
                    if (s2)
                        *s2 = '-';
                }
            add("</tr>\n");
        }
    add(ENDTABLE);
}

MAC(nocem)
/* [<][>][^][v][top][bottom][index][help] */
{
    int n;
    struct strList *sl;
    macro_html_table(out, argc-1, argv+1);
    for (n=0, sl = con->nocemGroups; sl && n<MAX_NOCEM; sl=sl->next, n++)
        {
            struct nocem_stats *p = &Stats->nocem_stats[n];
            strStackAdd(out, "<tr>");
            for (n=1; n<argc; n++)
                {
                    char *s = argv[n];
                    char *s2 = strchr(s, '-');
                    if (s2)
                        *s2 = '\0';
                    if (strCaseEq(s, "Group")) html_td(out, sl->data, "left"); else
                    if (strCaseEq(s, "ArtHi")) html_tdi(out, p->art_hi, "right"); else
                    if (strCaseEq(s, "ArtGood")) html_tdb(out, p->art_good, "right"); else
                    if (strCaseEq(s, "ArtFail")) html_tdb(out, p->art_fail, "right"); else
                    if (strCaseEq(s, "MsgidGood")) html_tdb(out, p->msgid_good, "right"); else
                    if (strCaseEq(s, "MsgidDup")) html_tdb(out, p->msgid_dup, "right"); else
                    if (strCaseEq(s, "MsgidFail")) html_tdb(out, p->msgid_fail, "right"); else
                    if (strCaseEq(s, "LastScan")) html_tdd(out, p->last_scan, "right"); else
                    if (strCaseEq(s, "ArtSkip")) html_tdb(out, p->art_skip, "right"); else
                    if (strCaseEq(s, "BytesFrom")) html_tdl(out, p->bytes_from, "right"); else
                    if (strCaseEq(s, "PGPgood")) html_tdb(out, p->pgp_good, "right"); else
                    if (strCaseEq(s, "PGPfail")) html_tdb(out, p->pgp_fail, "right"); else
                    {logen (("unkown parameter '%s' in @@%s@@", s, argv[0])); strStackAdd(out, "<td></td>");}
                    if (s2)
                        *s2 = '-';
                }
            strStackAdd(out, "</tr>\n");
        }
    strStackAdd(out, ENDTABLE);
}

MAC(tasklist)
/* [<][>][^][v][top][bottom][index][help] */
{
    int tn;
    struct task_info *task;
    macro_html_table(out, argc-1, argv+1);
    for (tn=0; tn<=Stats->task_high; tn++)
        {
            int n;
            task = &TaskList[tn]; /* XXX atomic locking */
            if (task->ti_state == nc_none)
                continue;
            strStackAdd(out, "<tr>");
            for (n=1; n<argc; n++)
                {
                    char *s = argv[n];
                    char *s2 = strchr(s, '-');
                    if (s2)
                        *s2 = '\0';
                    if (strCaseEq(s, "Type")) html_td(out, task->ti_name, "left"); else
                    if (strCaseEq(s, "Pid")) html_tdi(out, task->ti_pid, "right"); else
                    if (strCaseEq(s, "Started")) html_tdd(out, task->ti_started, "right"); else
                    if (strCaseEq(s, "Credentials")) html_td(out, task->ti_client_host, "right"); else
                    if (strCaseEq(s, "Server")) html_td(out, task->ti_CurrentScfg? task->ti_CurrentScfg->host: NULL, "right"); else
                    if (strCaseEq(s, "Group")) html_td(out, task->ti_CurrentGroup, "left"); else
                    if (strCaseEq(s, "Arts")) html_tdi(out, task->ti_ArtRead, "right"); else
                    if (strCaseEq(s, "Groups")) html_tdi(out, task->ti_GroupsEntered, "right"); else
                    if (strCaseEq(s, "Status")) html_td(out, task->ti_status_line, "left"); else
                    {logen (("unkown parameter '%s' in @@%s@@", s, argv[0])); strStackAdd(out, "<td></td>");}
                    if (s2)
                        *s2 = '-';
                }
            strStackAdd(out, "</tr>\n");
        }    
    strStackAdd(out, ENDTABLE);
}

MAC(newsgroups)
/* [<][>][^][v][top][bottom][index][help] */
{
    struct newsgroup *n;
    char *pat;
    pat = argv[1];
    macro_html_table (out, argc-2, argv+2);
    for (n=Ni->newsgroup_head; n; n=n->next)
    {
            newsgroupLockRead(n);
            if (match (pat, n->group, 1, 0))
                    html_newsgroup(out, argc-2, argv+2, n);
            newsgroupUnlockRead(n);
        }
    strStackAdd(out, ENDTABLE);
}

MAC(lists)
/* [<][>][^][v][top][bottom][index][help] */
{
    int n;
    macro_html_table(out, argc-1, argv+1);
    add("<tr>\n");
    add("<th align=center>List</th>");
    add("<th align=center>Entries</th>");
    add("<th align=center>Length</th>");
    add("<th align=center>Requests</th>");
    for (n = 0; lists[n].name; n++)
        {
            struct list_stats *t = &Stats->list_stats[lists[n].type];
            add("<tr>");
            html_td(out, lists[n].name, "left");
            html_tdb(out, t->entries, "right");
            html_tdl(out, t->len, "right");
            html_tdb(out, t->cache_stats.requests, "right");
            add("</tr>\n");
        }
    add(ENDTABLE);
}

MAC(cputab)
/* [<][>][^][v][top][bottom][index][help] */
{
    int n;
    macro_html_table(out, argc-1, argv+1);
    add("<tr>\n");
    add("<th align=center>Type</th>");
    add("<th align=center>Num</th>");
    add("<th align=center>Real</th>");
    add("<th align=center>CPU</th>");
    add("<th align=center>User</th>");
    add("<th align=center>System</th>");
    for (n = nc_none+1; n<nc_last; n++)
        {
            struct task_stats *t = &Stats->task_stats[n];
            add("<tr>");
            html_td(out, task_desc[n], "left");
            html_tdb(out, t->invocations, "right");
            html_tdt(out, t->elapsed, "right");
            html_tdt(out, CPU2BIG(t->cpu_user+t->cpu_system), "right");
            html_tdt(out, CPU2BIG(t->cpu_user), "right");
            html_tdt(out, CPU2BIG(t->cpu_system), "right");
            add("</tr>\n");
        }
    add(ENDTABLE);
}


struct macro_func macro[] = 
{
#define S(x,y) {#x , macro_ ##x , y}
    S(IPCfromChild, 0),
    S(IPCfromChildBytes, 0),
    S(IPCtoChild, 0),
    S(IPCtoChildBytes, 0),
    S(articlesExpired, 0),
    S(clientConnects, 0),
    S(clientConnectsFailed, 0),
    S(clientFromBytes, 0),
    S(clientToBytes, 0),
    S(clienthost, 0),
    S(clientsActive, 0),
    S(conftable, 0),
    S(cputab, 0),
    S(crossposts, 0),
    S(crosspostsBytes, 0),
    S(date, 0),
    S(efficiency, 0),
    S(groupsCached, 0),
    S(groupsExpired, 0),
    S(xoversExpired, 0),
    S(historyFetches, 0),
    S(historySize, 0),
    S(historyStores, 0),
    S(hostname, 0),
    S(invocations, 0),
    S(lists, 0),
    S(masterStarted, 0),
    S(newsgroups, 2),
    S(nocem, 1),
    S(posts, 0),
    S(postsBytes, 0),
    S(postsCross, 0),
    S(postsFailed, 0),
    S(serverConnects, 0),
    S(serverConnectsFailed, 0),
    S(serverFromBytes, 0),
    S(serverToBytes, 0),
    S(servers, 1),
    S(statsStarted, 0),
    S(tasklist, 1),
    S(version, 0),
    S(cacheStats, 1),
    {NULL, NULL, 0}
#undef S
};


static void macro_call (struct strStack *out, int argc, char **argv)
/* [<][>][^][v][top][bottom][index][help] */
{
    struct macro_func *s;
    for (s = macro; s->name; s++)
        if (strCaseEq(argv[0], s->name))
            {
                if (s->min_args+1>argc)
                    {
                        logen (("nntpcache macro @@%.128s@@ requires a minimum of %d argument%s",
                                argv[0], s->min_args, (s->min_args == 1)? "": "s"));
                        return;
                    }
                s->func(out, argc, argv);
                return;
            }
    logen (("nntpcache macro @@%.128s@@ not recognised", argv[0]));
}    

/*
 * returns NULL if document contained no @@'s
 */

static struct strStack *macro_parse (char *in, int len, int *expansions)
/* [<][>][^][v][top][bottom][index][help] */
{
    struct strStack *out = NULL;
    int argc = 0;
    char *p;
    *expansions = 0;
    for (p = in;;)
        {
            struct strStack *argvs;
            char *p2;
            p2 = strstr(p, "@@");
            if (!p2)
                {
                    out = strnStackAdd(out, p, len - (p-in));
                    break;
                }
            (*expansions)++;
            if (p2 - p > 0)
                out = strnStackAdd(out, p, p2-p);
            p2 += 2;
            p = strstr(p2, "@@");
            if (!p)
                {
                    logen (("unbalanced @@ macro sequence"));
                    break;
                }
            if (p == p2) /* @@@@ -> @@ */
                {
                    out = strStackAdd(out, "@@");
                    p += 2;
                    continue;
                }
            *p = '\0';
            p += 2;
            for (argvs = NULL, argc=0; *p2;)
                {
                    SKIPWHITE(p2);
                    if (!*p2)
                        break;
                    argvs = strnStackAdd (argvs, (char*)&p2, sizeof p2); /* accepts binary data */
                    argc++;
                    SKIPNOWHITE(p2);
                    if (!*p2)
                        break;
                    *p2++ = '\0';
                } 
            if (!argvs)
                continue;
            p2 = NULL;
            argvs = strnStackAdd (argvs, (char*)&p2, sizeof p2);
            macro_call (out, argc, (char**)(argvs->data));
            strStackFree(argvs);
        }
    return out;
}
            
static bool http_macro (char *url)
/* [<][>][^][v][top][bottom][index][help] */
{
    int len;
    char *in;
    struct strStack *out;
    char *fn;
    char *p;
    int expands;
    time_t modified;
    in = url_get_file (url, &len, &fn, &modified);
    if (!in)
        {
        bad:
            http_bad_url (fn);
            return FALSE;
        }
    p = strrchr (fn, '.');
    if (!p++ || !(strCaseEq (p, "html") || strCaseEq (p, "htm")))
        {
            http_file_prelude (fn, len, modified);
            fwriteClient (in, len);
            free (in);
            return TRUE;
        }
    out = macro_parse (in, len, &expands);
    free (in);
    if (!out)
        goto bad;
    http_file_prelude (fn, out->used, (expands>0)? 0: modified);
    fwriteClient (out->data, out->used);
    strStackFree (out);
    return TRUE;
}

static char *url_rewrite (char *url)
/* [<][>][^][v][top][bottom][index][help] */
{
    if (strnCaseEq (url, "http://", sizeof ("http://") -1))
        {
            char *p;
            url+=sizeof("http://") -1;
            p = strchr (url, '/');
            if (p)
                url = p+1;
            else
                url = "";
        }
    return url;
}

static bool http_get (char *url)
/* [<][>][^][v][top][bottom][index][help] */
{
    return http_macro (url_rewrite(url));
}

static bool http_cmd ()
/* [<][>][^][v][top][bottom][index][help] */
{
    char buf[MAX_URL]; /* security: make sure these are all the same length */
    char cmd[MAX_URL];
    char url[MAX_URL];
    settaskinfo("%s: waiting for http input", ClientHost);
    if (!Get (buf, sizeof buf))
        {
            logd (("http client disconencted before GET"));
            settaskinfo("%s diconnected before GET", ClientHost);
            return FALSE;
        }
    if (sscanf (buf, "%s %s", cmd, url) != 2)
        {
            http_emit_header (HTTP_STATUS_BADREQUEST,"Bad Request");
            emitf ("<H1>Bad Request</H1> Your browser sent a query that this server could not understand.<P>");
            http_emit_footer ();
            return FALSE;
        }
    settaskinfo("%s: %.80s %.80s", ClientHost, cmd, url);
    if (strCaseEq (cmd, "GET"))

        return http_get (url);
    http_emit_header (HTTP_STATUS_BADREQUEST,"Bad Request");
    emitf ("<H1>Bad Request</H1> Your browser sent a query that this server could not understand.<P>");
    http_emit_footer ();
    return FALSE;
}

EXPORT bool httpHandler()
/* [<][>][^][v][top][bottom][index][help] */
{
    bool ret;
    while (HoldForksHttp) {}
    alarm(con->idleTimeout);
    settaskinfo("authenticating http client");
    if (chdir (con->httpFiles) !=0)
        {
            loge (("chdir ('%s') failed", con->httpFiles));
            http_emit_header (HTTP_STATUS_FORBIDDEN,"Access Forbidden");
            emitf ("<H1>Access denied</H1> Access Forbidden-- '%s' inaccessable<P>\r\n", con->httpFiles);
            http_emit_footer ();
            flush ();
            return FALSE;
        }
    if (!fillAuth(fileno(clientin), "<http>"))
        {
            http_emit_header (HTTP_STATUS_FORBIDDEN,"Access Forbidden");
            emitf ("<H1>Access Forbidden</H1> [%s], you do not have connect permissions in the %s file.<P>\r\n", ClientHost, con->accessFile);
            flush ();
            log (("refused connect from %s (%s)", ClientHost, ClientHostAddr));
            return FALSE;
        }
    log (("connect from %s (%s)", ClientHost, ClientHostAddr));
    settaskinfo("%s: http starting", ClientHost);
    ret = http_cmd ();
    flush ();
    return ret;
}

/* [<][>][^][v][top][bottom][index][help] */