/* $Cambridge: hermes/src/prayer/shared/user_agent.c,v 1.5 2009/09/28 15:05:13 dpc22 Exp $ */
/************************************************
 *    Prayer - a Webmail Interface              *
 ************************************************/

/* Copyright (c) University of Cambridge 2000 - 2008 */
/* See the file NOTICE for conditions of use and distribution. */

#include "shared.h"

/* Class for recording attributes and behaviour of different user agents,
 * in order to determine suitable behaviour and set of optimisations
 */

/* user_agent_create() **************************************************
 *
 * Creates a fresh user agent structure.
 ***********************************************************************/

struct user_agent *user_agent_create(struct pool *pool)
{
    struct user_agent *result;

    result =
        (struct user_agent *) pool_alloc(pool, sizeof(struct user_agent));

    /* Defaults */
    result->pool = pool;
    result->manual = NIL;
    result->agent = NIL;
    result->use_override = NIL;
    result->use_debug = NIL;
    result->use_telemetry = NIL;
    result->use_telemetry_all = NIL;
    result->use_telemetry_frontend = NIL;
    result->use_icons = T;
    result->use_cookie = T;
    result->use_substitution = NIL;
    result->use_direct = T;
    result->use_pipelining = T;
    result->use_http_1_1 = T;
    result->use_pipelining = T;
    result->use_persist = T;
    result->use_short = T;
    result->use_gzip = T;

    return (result);
}

/* user_agent_free() ****************************************************
 *
 * Free user agent structure
 ***********************************************************************/

void user_agent_free(struct user_agent *user_agent)
{
    if (user_agent->pool == NIL)
        free(user_agent);
}

/* ====================================================================== */

/* user_agent_clear() ***************************************************
 *
 * Clear out user agent structure.
 ***********************************************************************/

static void user_agent_clear(struct user_agent *ua)
{
    /* Defaults */
    ua->manual = NIL;
    ua->use_override = NIL;
    ua->use_debug = NIL;
    ua->use_telemetry = NIL;
    ua->use_telemetry_all = NIL;
    ua->use_telemetry_frontend = NIL;
    ua->use_icons = NIL;
    ua->use_cookie = NIL;
    ua->use_substitution = NIL;
    ua->use_direct = NIL;
    ua->use_pipelining = NIL;
    ua->use_pipelining = NIL;
    ua->use_http_1_1 = NIL;
    ua->use_persist = NIL;
    ua->use_short = NIL;
    ua->use_gzip = NIL;
}

/* ====================================================================== */

/* user_agent_clone() ***************************************************
 *
 * Copy a user agent structure
 *    dest: Destination
 *     src: Source
 ***********************************************************************/

struct user_agent *user_agent_clone(struct pool *pool,
                                    struct user_agent *src)
{
    struct user_agent *result;

    result =
        (struct user_agent *) pool_alloc(pool, sizeof(struct user_agent));

    memcpy((void *) result, (void *) src, sizeof(struct user_agent));

    return (result);
}

/* user_agent_copy() ****************************************************
 *
 * Copy a user agent structure
 *    dest: Destination
 *     src: Source
 ***********************************************************************/

void user_agent_copy(struct user_agent *dest, struct user_agent *src)
{
    memcpy((void *) dest, (void *) src, sizeof(struct user_agent));
}

/* ====================================================================== */

/* user_agent_set_option() **********************************************
 *
 * Set or clear user-agent flag from text string.
 *      ua:
 *  option: Option name
 *  enable: T => enable. NIL => disable.
 ***********************************************************************/

static void
user_agent_set_option(struct user_agent *ua, char *option, BOOL enable)
{
    switch (option[0]) {
    case 'c':
        if (!strcmp(option, "cookie"))
            ua->use_cookie = enable;
        break;
    case 'd':
        if (!strcmp(option, "debug"))
            ua->use_debug = enable;
        else if (!strcmp(option, "direct"))
            ua->use_direct = enable;
        break;
    case 'g':
        if (!strcmp(option, "gzip"))
            ua->use_gzip = enable;
    case 'h':
        if (!strcmp(option, "http_1.1"))
            ua->use_http_1_1 = enable;
        else if (!strcmp(option, "http_1_1"))
            ua->use_http_1_1 = enable;
        break;
    case 'i':
        if (!strcmp(option, "icons"))
            ua->use_icons = enable;
        break;
    case 'm':
        if (!strcmp(option, "manual"))
            ua->manual = enable;
        break;
    case 'o':
        if (!strcmp(option, "override"))
            ua->use_override = enable;
        break;
    case 'p':
        if (!strcmp(option, "pipelining"))
            ua->use_pipelining = enable;
        else if (!strcmp(option, "persist"))
            ua->use_persist = enable;
        break;
    case 's':
        if (!strcmp(option, "substitution"))
            ua->use_substitution = enable;
        else if (!strcmp(option, "short"))
            ua->use_short = enable;
        break;
    case 't':
        if (!strcmp(option, "telemetry"))
            ua->use_telemetry = enable;
        else if (!strcmp(option, "telemetry_all"))
            ua->use_telemetry_all = enable;
        else if (!strcmp(option, "telemetry-all"))
            ua->use_telemetry_all = enable;
        else if (!strcmp(option, "telemetry_frontend"))
            ua->use_telemetry_frontend = enable;
        else if (!strcmp(option, "telemetry-frontend"))
            ua->use_telemetry_frontend = enable;
        break;
    }
}

static void user_agent_failsafe(struct user_agent *ua)
{
    ua->manual = T;
    ua->use_substitution = NIL;
    ua->use_direct = NIL;
    ua->use_http_1_1 = NIL;
    ua->use_pipelining = NIL;
    ua->use_persist = NIL;
    ua->use_short = NIL;
    ua->use_gzip = NIL;
}

/* ====================================================================== */

/* Static utility routine for generating part of extended login screen */

static void
html_form_table_checkbox(struct buffer *b, char *desc, char *name,
                         BOOL enabled)
{
    bprintf(b,
            "<td><input type=\"checkbox\" name=\"%s\" value=\"1\"%s></td>",
            name, (enabled) ? "checked" : "");
    bprintf(b, "<td> %s</td>" CRLF, desc);
}

/* user_agent_generate_form() *******************************************
 *
 * Generate diagnostic form for login screen.
 *   b: Target Buffer for HTML.
 *  ua: User agent.
 ***********************************************************************/

void user_agent_generate_form(struct buffer *b, struct user_agent *ua)
{
    bputs(b, "<table>" CRLF);

    bputs(b, "<tr><td>User-Agent:</td><td>");
    if (ua->agent)
        html_common_quote_string(b, ua->agent);
    else
        bputs(b, "Unknown" CRLF);
    bputs(b, "</td></tr></table>" CRLF);

    bputs(b, "<table><tr>" CRLF);
    html_form_table_checkbox(b, "Enable icons", "use_icons",
                             ua->use_icons);
    html_form_table_checkbox(b, "Enable short URLS", "use_short",
                             ua->use_short);
    bputs(b, "</tr>" CRLF);

    bputs(b, "<tr>" CRLF);
    html_form_table_checkbox(b, "Enable HTTP Cookie",
                             "use_cookie", ua->use_cookie);

    html_form_table_checkbox(b, "Enable Page substitition",
                             "use_substitution", ua->use_substitution);
    bputs(b, "</tr>" CRLF);

    bputs(b, "<tr>" CRLF);
    html_form_table_checkbox(b, "Enable persistent HTTP connections",
                             "use_persist", ua->use_persist);
    bputs(b, "</tr>" CRLF);

    bputs(b, "<tr>" CRLF);
    html_form_table_checkbox(b, "Enable HTTP/1.1",
                             "use_http_1_1", ua->use_http_1_1);
    html_form_table_checkbox(b, "Enable HTTP/1.1 Pipelining",
                             "use_pipelining", ua->use_pipelining);
    bputs(b, "</tr>" CRLF);

    bputs(b, "<tr>" CRLF);
    html_form_table_checkbox(b, "Enable gzip compression",
                             "use_gzip", ua->use_gzip);
    bputs(b, "</tr>" CRLF);

    bputs(b, "<tr>" CRLF);
    html_form_table_checkbox(b, "Use direct connection",
                             "use_direct", ua->use_direct);

    html_form_table_checkbox(b, "Override User Preferences",
                             "use_override", ua->use_override);
    bputs(b, "</tr>" CRLF);

    bputs(b, "</table>" CRLF);
}

/* user_agent_process_form() ********************************************
 *
 * Process user-agent status form if active
 *  ua: User agent.
 *   h: Associative array containing parsed form from login screen.
 ***********************************************************************/

void user_agent_process_form(struct user_agent *ua, struct assoc *h)
{
    ua->manual = T;
    ua->use_icons = assoc_lookup(h, "use_icons") ? T : NIL;
    ua->use_cookie = assoc_lookup(h, "use_cookie") ? T : NIL;
    ua->use_pipelining = assoc_lookup(h, "use_pipelining") ? T : NIL;
    ua->use_http_1_1 = assoc_lookup(h, "use_http_1_1") ? T : NIL;
    ua->use_persist = assoc_lookup(h, "use_persist") ? T : NIL;
    ua->use_short = assoc_lookup(h, "use_short") ? T : NIL;
    ua->use_substitution = assoc_lookup(h, "use_substitution") ? T : NIL;
    ua->use_direct = assoc_lookup(h, "use_direct") ? T : NIL;
    ua->use_override = assoc_lookup(h, "use_override") ? T : NIL;
    ua->use_gzip = assoc_lookup(h, "use_gzip") ? T : NIL;
}


/* ====================================================================== */

/* user_agent_setup_browser() *******************************************
 *
 * Set up user agent configuration, based on "User-Agent: " header
 *      ua: User agent.
 * browser: String identifying browser, e.g: "mozilla/4".
 ***********************************************************************/

void user_agent_setup_browser(struct user_agent *ua, char *browser)
{
    if (!(browser && browser[0]))
        return;

    ua->agent = pool_strdup(ua->pool, browser);
    ua->manual = NIL;

    /* Mozilla */
    if (!strncasecmp(browser, "Mozilla/5", strlen("Mozilla/5"))) {
        /* Grr. Daft User-agent strings:
         *
         * Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_8; en-us)
         * AppleWebKit/531.9 (KHTML, like Gecko) Version/4.0.3 Safari/531.9
         */
 
        if (strstr(browser, " AppleWebKit/") ||
            strstr(browser, " Safari/")) {
            ua->use_icons = T;
            ua->use_cookie = T;
            ua->use_http_1_1 = T;
            ua->use_pipelining = NIL;
            /* Problems with persistent connections after file upload */
            ua->use_persist = NIL;
            ua->use_gzip = T;
            return;
        }

        ua->use_icons = T;
        ua->use_cookie = T;
        ua->use_http_1_1 = T;
        ua->use_pipelining = T;
        ua->use_persist = T;
        ua->use_gzip = T;
        return;
    }

    /* Need better parsing library for compatible nonsense */
    if (!strncasecmp(browser,
                     "Mozilla/4.0 (compatible; MSIE ",
                     strlen("Mozilla/4.0 (compatible; MSIE "))) {
        char *s;

        if ((s = strchr(browser, ')')) && !strcmp(s + 1, " Opera ")) {
            /* Opera */
            ua->use_icons = T;
            ua->use_cookie = T;
            ua->use_http_1_1 = NIL;     /* Possible problems with HTTP/1.1? */
            ua->use_pipelining = NIL;
            ua->use_persist = NIL;      /* Opera has problems with KeepAlive stuff */
            ua->use_gzip = T;
        } else {
            /* MSIE */
            ua->use_icons = T;
            ua->use_cookie = T;
            ua->use_http_1_1 = T;       /* Possible problems with HTTP/1.1? */
            ua->use_pipelining = T;
            ua->use_persist = T;
            ua->use_gzip = NIL;
        }
        return;
    }

    /* Cookie support broken in StarOffice */
    if (!strncasecmp(browser, "Mozilla/3.0 (compatible; StarOffice ",
                     strlen("Mozilla/3.0 (compatible; StarOffice "))) {
        ua->use_icons = T;
        ua->use_cookie = NIL;
        ua->use_http_1_1 = NIL;
        ua->use_pipelining = NIL;
        ua->use_persist = T;
        ua->use_gzip = T;
        return;
    }

    /* SSL support in Mac + Windows Netscape 4 is broken: can't handle
     * use_persist Checks: 4.08 okay. 4.5 and 4.7 definitely broken.
     * Solution: mask out use_persist for versions > 4.08.  Unix Netscape 4
     * appears to be completely different: supports HTTP/1.1 for a start! */

    if (!strncasecmp(browser, "Mozilla/4.", strlen("Mozilla/4.")) &&
        (browser[strlen("Mozilla/4.")] != '0')) {
        char *s = strchr(browser, '(');

        if (s && !strncasecmp(s + 1, "Macintosh", strlen("Macintosh"))) {
            ua->use_icons = T;
            ua->use_cookie = T;
            ua->use_http_1_1 = NIL;
            ua->use_pipelining = NIL;
            ua->use_persist = NIL;
            ua->use_gzip = T;
            return;
        } else if (s && !strncasecmp(s + 1, "Win", strlen("Win"))) {
            ua->use_icons = T;
            ua->use_cookie = T;
            ua->use_http_1_1 = T;
            ua->use_pipelining = T;
            ua->use_persist = NIL;
            ua->use_gzip = T;
            return;
        }
    }

    /* Generic Netscape settings */
    if (!strncasecmp(browser, "Mozilla/", strlen("Mozilla/"))) {
        ua->use_icons = T;
        ua->use_cookie = T;
        ua->use_http_1_1 = T;
        ua->use_pipelining = T;
        ua->use_gzip = T;
        return;
    }

    /* Fake Netscape */
    if (!strncasecmp(browser, "Netscape/", strlen("Netscape/"))) {
        ua->use_icons = T;
        ua->use_cookie = T;
        ua->use_http_1_1 = T;
        ua->use_pipelining = T;
        ua->use_gzip = T;
        return;
    }

    /* Lynx */
    if (!strncasecmp(browser, "Lynx/", strlen("Lynx/"))) {
        ua->use_icons = NIL;
        ua->use_cookie = NIL;
        ua->use_pipelining = NIL;
        ua->use_gzip = T;
        return;
    }

    /* W3M */
    if (!strncasecmp(browser, "w3m/", strlen("w3m/"))) {
        ua->use_icons = NIL;
        ua->use_cookie = NIL;
        ua->use_pipelining = NIL;
        ua->use_gzip = T;
        return;
    }
}

/* ====================================================================== */

/* user_agent_process_browser() *****************************************
 *
 * Set up user agent configuration based on user supplied override
 *      ua: User agent.
 *       s: String identifying browser, e.g: "netscape".
 ***********************************************************************/

static void user_agent_process_browser(struct user_agent *ua, char *s)
{
    if (!strcmp(s, "mozilla"))
        user_agent_setup_browser(ua, "Mozilla/5.x (simulation)");
    else if (!strcmp(s, "netscape"))
        user_agent_setup_browser(ua, "Netscape/4.x (simulation)");
    else if (!strcmp(s, "ie"))
        user_agent_setup_browser(ua, "IE/Unknown (simulation)");
    else if (!strcmp(s, "lynx"))
        user_agent_setup_browser(ua, "Lynx/Unknown (simulation)");
    else if (!strcmp(s, "w3m"))
        user_agent_setup_browser(ua, "w3m/Unknown (simulation)");
    else
        user_agent_setup_browser(ua, "Unknown/Unknown (simulation)");
}

/* user_agent_process_argv() ********************************************
 *
 * Process user agent options from argv array string. See URL_OPTIONS
 * for full details.
 *     enable=option,option,option
 *     disable=option,option,option
 *        opts=option,option,option
 *  user-agent=<browser id>
 *  debug
 *  override
 *  manual
 *  telemetry
 *  telemetry_all
 *  telemetry-all
 *  telemetry_frontend
 *  telemetry-frontend
 *
 * Args:
 *      ua: User agent
 *  string: String identifying browser, e.g: "netscape".
 *    pool: Target pool
 ***********************************************************************/

BOOL
user_agent_process_argv(struct user_agent *ua,
                        char *string, struct pool *pool)
{
    /* Allow empty arguments */
    if (!strcmp(string, ""))
        return (T);

    if (!strncasecmp(string, "disable=", strlen("disable="))) {
        char *s = pool_strdup(pool, string + strlen("disable="));
        char *t;

        ua->manual = T;
        while (s) {
            if ((t = strchr(s, ',')))
                *t++ = '\0';
            user_agent_set_option(ua, s, NIL);
            s = t;
        }
        return (T);
    }

    if (!strncasecmp(string, "enable=", strlen("enable="))) {
        char *s = pool_strdup(pool, string + strlen("enable="));
        char *t;

        ua->manual = T;
        while (s) {
            if ((t = strchr(s, ',')))
                *t++ = '\0';
            user_agent_set_option(ua, s, T);
            s = t;
        }
        return (T);
    }

    if (!strncasecmp(string, "opts=", strlen("opts="))) {
        char *s = pool_strdup(pool, string + strlen("opts="));
        char *t;

        user_agent_clear(ua);
        ua->manual = T;
        while (s) {
            if ((t = strchr(s, ',')))
                *t++ = '\0';
            user_agent_set_option(ua, s, T);
            s = t;
        }
        return (T);
    }

    if (!strncasecmp(string, "user-agent=", strlen("user-agent=")))
        user_agent_process_browser(ua, string + strlen("user-agent="));
    else if (!strncasecmp(string, "browser=", strlen("browser=")))
        user_agent_process_browser(ua, string + strlen("user-agent="));
    else if (!strcmp(string, "failsafe"))
        user_agent_failsafe(ua);
    else if (!strcmp(string, "debug"))
        ua->use_debug = T;
    else if (!strcmp(string, "text"))
        ua->use_icons = NIL;
    else if (!strcmp(string, "lores"))
        ua->use_icons = NIL;
    else if (!strcmp(string, "override"))
        ua->use_override = T;
    else if (!strcmp(string, "manual"))
        ua->manual = T;
    else if (!strcmp(string, "telemetry"))
        ua->use_telemetry = T;
    else if (!strcmp(string, "telemetry_all"))
        ua->use_telemetry_all = T;
    else if (!strcmp(string, "telemetry-all"))
        ua->use_telemetry_all = T;
    else if (!strcmp(string, "telemetry_frontend"))
        ua->use_telemetry_frontend = T;
    else if (!strcmp(string, "telemetry-frontend"))
        ua->use_telemetry_frontend = T;
    else
        return (NIL);

    ua->manual = T;
    return (T);
}

/* ====================================================================== */

/* user_agent_parse() ***************************************************
 *
 * Build a user agent from list of options
 *      ua: User agent
 *  string: String containing list of comma separated options.
 ***********************************************************************/

void user_agent_parse(struct user_agent *ua, char *string)
{
    char *s = string;
    char *t;

    user_agent_clear(ua);

    while (s) {
        if ((t = strchr(s, ',')))
            *t++ = '\0';
        user_agent_set_option(ua, s, T);
        s = t;
    }
}

/* ====================================================================== */

/* user_agent_options() *************************************************
 *
 * Convery user agent options into printable representation.
 *      ua: User agent
 *
 * Returns: String containing list of options eg: "cookie"
 ***********************************************************************/

char *user_agent_options(struct user_agent *ua)
{
    struct buffer *b = buffer_create(ua->pool, 256);

    if (ua->use_debug)
        bputs(b, "debug,");

    if (ua->manual)
        bputs(b, "manual,");

    if (ua->use_override)
        bputs(b, "override,");

    if (ua->use_telemetry)
        bputs(b, "telemetry,");

    if (ua->use_telemetry_all)
        bputs(b, "telemetry_all,");

    if (ua->use_telemetry_frontend)
        bputs(b, "telemetry_frontend,");

    if (ua->use_icons)
        bputs(b, "icons,");

    if (ua->use_cookie)
        bputs(b, "cookie,");

    if (ua->use_pipelining)
        bputs(b, "pipelining,");

    if (ua->use_http_1_1)
        bputs(b, "http_1_1,");

    if (ua->use_persist)
        bputs(b, "persist,");

    if (ua->use_substitution)
        bputs(b, "substitution,");

    if (ua->use_short)
        bputs(b, "short,");

    if (ua->use_direct)
        bputs(b, "direct,");

    if (ua->use_gzip)
        bputs(b, "gzip,");

    if (buffer_size(b) > 0)
        return (buffer_fetch(b, 0, buffer_size(b) - 1, NIL));
    else
        return (pool_strdup(ua->pool, ""));
}
