
/*
 * $Id$
 *
 * DEBUG: section 90    HTTP Cache Control Header
 * AUTHOR: Alex Rousskov
 *         Robert Collins (Surrogate-Control is derived from
 *         		   Cache-Control).
 *
 * SQUID Web Proxy Cache          http://www.squid-cache.org/
 * ----------------------------------------------------------
 *
 *  Squid is the result of efforts by numerous individuals from
 *  the Internet community; see the CONTRIBUTORS file for full
 *  details.   Many organizations have provided support for Squid's
 *  development; see the SPONSORS file for full details.  Squid is
 *  Copyrighted (C) 2001 by the Regents of the University of
 *  California; see the COPYRIGHT file for full details.  Squid
 *  incorporates software developed and/or copyrighted by other
 *  sources; see the CREDITS file for full details.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
 *
 */

#include "squid.h"
#include "Store.h"
#include "HttpHeader.h"
#include "HttpHdrSc.h"

/* this table is used for parsing surrogate control header */
static const HttpHeaderFieldAttrs ScAttrs[SC_ENUM_END] = {
    {"no-store", (http_hdr_type)SC_NO_STORE},

    {"no-store-remote", (http_hdr_type)SC_NO_STORE_REMOTE},
    {"max-age", (http_hdr_type)SC_MAX_AGE},
    {"content", (http_hdr_type)SC_CONTENT},
    {"Other,", (http_hdr_type)SC_OTHER}	/* ',' will protect from matches */
};

HttpHeaderFieldInfo *ScFieldsInfo = NULL;

http_hdr_sc_type &operator++ (http_hdr_sc_type &aHeader)
{
    int tmp = (int)aHeader;
    aHeader = (http_hdr_sc_type)(++tmp);
    return aHeader;
}

int operator - (http_hdr_sc_type const &anSc, http_hdr_sc_type const &anSc2)
{
    return (int)anSc - (int)anSc2;
}


/* local prototypes */
static int httpHdrScParseInit(HttpHdrSc * sc, const String * str);

/* module initialization */

void
httpHdrScInitModule(void)
{
    ScFieldsInfo = httpHeaderBuildFieldsInfo(ScAttrs, SC_ENUM_END);
}

void
httpHdrScCleanModule(void)
{
    httpHeaderDestroyFieldsInfo(ScFieldsInfo, SC_ENUM_END);
    ScFieldsInfo = NULL;
}

/* implementation */

HttpHdrSc *
httpHdrScCreate(void)
{
    return new HttpHdrSc();
}

/* creates an sc object from a 0-terminating string */
HttpHdrSc *
httpHdrScParseCreate(const String * str)
{
    HttpHdrSc *sc = httpHdrScCreate();

    if (!httpHdrScParseInit(sc, str)) {
        httpHdrScDestroy(sc);
        sc = NULL;
    }

    return sc;
}

/* parses a 0-terminating string and inits sc */
static int
httpHdrScParseInit(HttpHdrSc * sc, const String * str)
{
    const char *item;
    const char *p;		/* '=' parameter */
    const char *pos = NULL;
    const char *target = NULL; /* ;foo */
    const char *temp = NULL; /* temp buffer */
    int type;
    int ilen;
    int initiallen;
    HttpHdrScTarget *sct;
    assert(sc && str);

    /* iterate through comma separated list */

    while (strListGetItem(str, ',', &item, &ilen, &pos)) {
        initiallen = ilen;
        /* decrease ilen to still match the token for  '=' statements */

        if ((p = strchr(item, '=')) && (p - item < ilen))
            ilen = p++ - item;

        /* decrease ilen to still match the token for ';' qualified non '=' statments */
        else if ((p = strchr(item, ';')) && (p - item < ilen))
            ilen = p++ - item;

        /* find type */
        type = httpHeaderIdByName(item, ilen,
                                  ScFieldsInfo, SC_ENUM_END);

        if (type < 0) {
            debugs(90, 2, "hdr sc: unknown control-directive: near '" << item << "' in '" << str << "'");
            type = SC_OTHER;
        }

        /* Is this a targeted directive? */
        /* TODO sometime: implement a strnrchr that looks at a substring */
        temp = xstrndup (item, initiallen + 1);

        if (!((target = strrchr (temp, ';')) && !strchr (target, '"') && *(target + 1) != '\0'))
            target = NULL;
        else
            ++target;

        sct = httpHdrScFindTarget (sc, target);

        if (!sct) {
            sct = httpHdrScTargetCreate (target);
            dlinkAdd(sct, &sct->node, &sc->targets);
        }

        safe_free (temp);

        if (EBIT_TEST(sct->mask, type)) {
            if (type != SC_OTHER)
                debugs(90, 2, "hdr sc: ignoring duplicate control-directive: near '" << item << "' in '" << str << "'");

            ScFieldsInfo[type].stat.repCount++;

            continue;
        }

        /* update mask */
        EBIT_SET(sct->mask, type);

        /* post-processing special cases */
        switch (type) {

        case SC_MAX_AGE:

            if (!p || !httpHeaderParseInt(p, &sct->max_age)) {
                debugs(90, 2, "sc: invalid max-age specs near '" << item << "'");
                sct->max_age = -1;
                EBIT_CLR(sct->mask, type);
            }

            if ((p = strchr (p, '+')))
                if (!httpHeaderParseInt(++p, &sct->max_stale)) {
                    debugs(90, 2, "sc: invalid max-stale specs near '" << item << "'");
                    sct->max_stale = 0;
                    /* leave the max-age alone */
                }

            break;

        case SC_CONTENT:

            if (!p || !httpHeaderParseQuotedString(p, &sct->content)) {
                debugs(90, 2, "sc: invalid content= quoted string near '" << item << "'");
                sct->content.clean();
                EBIT_CLR(sct->mask, type);
            }

        default:
            break;
        }
    }

    return sc->targets.head != NULL;
}

void
httpHdrScDestroy(HttpHdrSc * sc)
{
    assert(sc);

    if (sc->targets.head) {
        dlink_node *sct = sc->targets.head;

        while (sct) {
            HttpHdrScTarget *t = (HttpHdrScTarget *)sct->data;
            sct = sct->next;
            dlinkDelete (&t->node, &sc->targets);
            httpHdrScTargetDestroy (t);
        }
    }

    delete sc;
}

HttpHdrSc *
httpHdrScDup(const HttpHdrSc * sc)
{
    HttpHdrSc *dup;
    dlink_node *node;
    assert(sc);
    node = sc->targets.head;
    dup = httpHdrScCreate();

    while (node) {
        HttpHdrScTarget *dupsct;
        dupsct = httpHdrScTargetDup ((HttpHdrScTarget *)node->data);
        dlinkAddTail (dupsct, &dupsct->node, &dup->targets);
        node = node->next;
    }

    return dup;
}

void
httpHdrScTargetPackInto(const HttpHdrScTarget * sc, Packer * p)
{
    http_hdr_sc_type flag;
    int pcount = 0;
    assert(sc && p);

    for (flag = SC_NO_STORE; flag < SC_ENUM_END; ++flag) {
        if (EBIT_TEST(sc->mask, flag) && flag != SC_OTHER) {

            /* print option name */
            packerPrintf(p, (pcount ? ", " SQUIDSTRINGPH : SQUIDSTRINGPH),
                         SQUIDSTRINGPRINT(ScFieldsInfo[flag].name));

            /* handle options with values */

            if (flag == SC_MAX_AGE)
                packerPrintf(p, "=%d", (int) sc->max_age);

            if (flag == SC_CONTENT)
                packerPrintf(p, "=\"" SQUIDSTRINGPH "\"", SQUIDSTRINGPRINT(sc->content));

            pcount++;
        }
    }

    if (sc->target.size())
        packerPrintf (p, ";" SQUIDSTRINGPH, SQUIDSTRINGPRINT(sc->target));
}

void
httpHdrScPackInto(const HttpHdrSc * sc, Packer * p)
{
    dlink_node *node;
    assert(sc && p);
    node = sc->targets.head;

    while (node) {
        httpHdrScTargetPackInto((HttpHdrScTarget *)node->data, p);
        node = node->next;
    }
}

void
httpHdrScJoinWith(HttpHdrSc * sc, const HttpHdrSc * new_sc)
{
    assert(sc && new_sc);
#if 0
    /* RC TODO: check that both have the same target */

    if (sc->max_age < 0)
        sc->max_age = new_sc->max_age;

    /* RC TODO: copy unique missing stringlist entries */
    cc->mask |= new_cc->mask;

#endif
}

/* negative max_age will clean old max_Age setting */
void
httpHdrScSetMaxAge(HttpHdrSc * sc, char const *target, int max_age)
{
    HttpHdrScTarget *sct;
    assert(sc);
    sct = httpHdrScFindTarget (sc, target);

    if (!sct) {
        sct = httpHdrScTargetCreate (target);
        dlinkAddTail (sct, &sct->node, &sc->targets);
    }

    httpHdrScTargetSetMaxAge(sct, max_age);
}

void
httpHdrScUpdateStats(const HttpHdrSc * sc, StatHist * hist)
{
    dlink_node *sct;
    assert(sc);
    sct = sc->targets.head;

    while (sct) {
        httpHdrScTargetUpdateStats((HttpHdrScTarget *)sct->data, hist);
        sct = sct->next;
    }
}

void
httpHdrScTargetStatDumper(StoreEntry * sentry, int idx, double val, double size, int count)
{
    extern const HttpHeaderStat *dump_stat;     /* argh! */
    const int id = (int) val;
    const int valid_id = id >= 0 && id < SC_ENUM_END;
    const char *name = valid_id ? ScFieldsInfo[id].name.termedBuf() : "INVALID";

    if (count || valid_id)
        storeAppendPrintf(sentry, "%2d\t %-20s\t %5d\t %6.2f\n",
                          id, name, count, xdiv(count, dump_stat->scParsedCount));
}

void
httpHdrScStatDumper(StoreEntry * sentry, int idx, double val, double size, int count)
{
    extern const HttpHeaderStat *dump_stat;	/* argh! */
    const int id = (int) val;
    const int valid_id = id >= 0 && id < SC_ENUM_END;
    const char *name = valid_id ? ScFieldsInfo[id].name.termedBuf() : "INVALID";

    if (count || valid_id)
        storeAppendPrintf(sentry, "%2d\t %-20s\t %5d\t %6.2f\n",
                          id, name, count, xdiv(count, dump_stat->scParsedCount));
}

HttpHdrScTarget *
httpHdrScFindTarget (HttpHdrSc *sc, const char *target)
{
    dlink_node *node;
    assert (sc);
    node = sc->targets.head;

    while (node) {
        HttpHdrScTarget *sct = (HttpHdrScTarget *)node->data;

        if (target && sct->target.defined() && !strcmp (target, sct->target.termedBuf()))
            return sct;
        else if (!target && sct->target.undefined())
            return sct;

        node = node->next;
    }

    return NULL;
}

HttpHdrScTarget *
httpHdrScGetMergedTarget (HttpHdrSc *sc, const char *ourtarget)
{
    HttpHdrScTarget *sctus = httpHdrScFindTarget (sc, ourtarget);
    HttpHdrScTarget *sctgeneric = httpHdrScFindTarget (sc, NULL);

    if (sctgeneric || sctus) {
        HttpHdrScTarget *sctusable = httpHdrScTargetCreate (NULL);

        if (sctgeneric)
            httpHdrScTargetMergeWith (sctusable, sctgeneric);

        if (sctus)
            httpHdrScTargetMergeWith (sctusable, sctus);

        return sctusable;
    }

    return NULL;
}
