/*
 * Formats a command line using the formatting language common to
 * Telnet proxies, local proxy commands, and pre-connection hooks.
 *
 * Returns the formatted command, dynamically allocated. Also,
 * optionally, returns flags indicating whether %user or %pass were
 * referred to but not provided in the Conf.
 */

#include "putty.h"

char *format_connection_setup_command(
    const char *fmt, SockAddr *addr, int port, Conf *conf, unsigned *flags_out)
{
    int so = 0, eo = 0;
    strbuf *buf = strbuf_new();
    unsigned flags = 0;

    /* we need to escape \\, \%, \r, \n, \t, \x??, \0???,
     * %%, %host, %port, %user, and %pass
     */

    while (fmt[eo] != 0) {

        /* scan forward until we hit end-of-line,
         * or an escape character (\ or %) */
        while (fmt[eo] != 0 && fmt[eo] != '%' && fmt[eo] != '\\')
            eo++;

        /* if we hit eol, break out of our escaping loop */
        if (fmt[eo] == 0) break;

        /* if there was any unescaped text before the escape
         * character, send that now */
        if (eo != so)
            put_data(buf, fmt + so, eo - so);

        so = eo++;

        /* if the escape character was the last character of
         * the line, we'll just stop and send it. */
        if (fmt[eo] == 0) break;

        if (fmt[so] == '\\') {

            /* we recognize \\, \%, \r, \n, \t, \x??.
             * anything else, we just send unescaped (including the \).
             */

            switch (fmt[eo]) {

              case '\\':
                put_byte(buf, '\\');
                eo++;
                break;

              case '%':
                put_byte(buf, '%');
                eo++;
                break;

              case 'r':
                put_byte(buf, '\r');
                eo++;
                break;

              case 'n':
                put_byte(buf, '\n');
                eo++;
                break;

              case 't':
                put_byte(buf, '\t');
                eo++;
                break;

              case 'x':
              case 'X': {
                /* escaped hexadecimal value (ie. \xff) */
                unsigned char v = 0;
                int i = 0;

                for (;;) {
                    eo++;
                    if (fmt[eo] >= '0' && fmt[eo] <= '9')
                        v += fmt[eo] - '0';
                    else if (fmt[eo] >= 'a' && fmt[eo] <= 'f')
                        v += fmt[eo] - 'a' + 10;
                    else if (fmt[eo] >= 'A' && fmt[eo] <= 'F')
                        v += fmt[eo] - 'A' + 10;
                    else {
                        /* non hex character, so we abort and just
                         * send the whole thing unescaped (including \x)
                         */
                        put_byte(buf, '\\');
                        eo = so + 1;
                        break;
                    }

                    /* we only extract two hex characters */
                    if (i == 1) {
                        put_byte(buf, v);
                        eo++;
                        break;
                    }

                    i++;
                    v <<= 4;
                }
                break;
              }

              default:
                put_data(buf, fmt + so, 2);
                eo++;
                break;
            }
        } else {

            /* % escape. we recognize %%, %host, %port, %user, %pass.
             * %proxyhost, %proxyport. Anything else we just send
             * unescaped (including the %).
             */

            if (fmt[eo] == '%') {
                put_byte(buf, '%');
                eo++;
            }
            else if (strnicmp(fmt + eo, "host", 4) == 0) {
                char dest[512];
                sk_getaddr(addr, dest, lenof(dest));
                put_data(buf, dest, strlen(dest));
                eo += 4;
            }
            else if (strnicmp(fmt + eo, "port", 4) == 0) {
                put_fmt(buf, "%d", port);
                eo += 4;
            }
            else if (strnicmp(fmt + eo, "user", 4) == 0) {
                const char *username = conf_get_str(conf, CONF_proxy_username);
                put_data(buf, username, strlen(username));
                eo += 4;
                if (!*username)
                    flags |= TELNET_CMD_MISSING_USERNAME;
            }
            else if (strnicmp(fmt + eo, "pass", 4) == 0) {
                const char *password = conf_get_str(conf, CONF_proxy_password);
                put_data(buf, password, strlen(password));
                eo += 4;
                if (!*password)
                    flags |= TELNET_CMD_MISSING_PASSWORD;
            }
            else if (strnicmp(fmt + eo, "proxyhost", 9) == 0) {
                const char *host = conf_get_str(conf, CONF_proxy_host);
                put_data(buf, host, strlen(host));
                eo += 9;
            }
            else if (strnicmp(fmt + eo, "proxyport", 9) == 0) {
                int port = conf_get_int(conf, CONF_proxy_port);
                put_fmt(buf, "%d", port);
                eo += 9;
            }
            else {
                /* we don't escape this, so send the % now, and
                 * don't advance eo, so that we'll consider the
                 * text immediately following the % as unescaped.
                 */
                put_byte(buf, '%');
            }
        }

        /* resume scanning for additional escapes after this one. */
        so = eo;
    }

    /* if there is any unescaped text at the end of the line, send it */
    if (eo != so) {
        put_data(buf, fmt + so, eo - so);
    }

    if (flags_out)
        *flags_out = flags;
    return strbuf_to_str(buf);
}
