/* testhtml.c */

/* Copyright 1997-1999 by Eberhard Mattes <em-gw@windhager.de>
   Donated to the public domain.  No warranty.

   1997-07-19 Initial version
   1997-09-06 "href" configuration
   1997-09-13 url_check_path() removed
   1997-10-20 Add openlog(), closelog(), and setlogmask()
   1997-10-21 libemfw.h defines octet
   1997-10-22 "script" configuration
   1998-02-19 Handle script macros in attributes
   1999-03-17 CONNECT (https/SSL)
   1999-08-26 allow/block embed
   1999-09-16 allow/block style */

#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include "magic.h"
#include "libemfw.h"
#include "emio.h"
#include "squid-gw.h"
#include "ctype.h"

/* These variables must be global.  They are just referenced by
   http.c, but not actually used. */

int needauth = 0;
magic_t magic;
EMI_FILE *c_in, *s_in;
EMO_FILE *c_out, *s_out;
struct ctype_t* ctypes;

static enum { ANNO_NONE, ANNO_KEYWORDS, ANNO_ANSI_COLOR } anno_mode;
static EMI_FILE *in;
static EMO_FILE *out;

static void usage (void) ATTR_NORETURN;

static void usage (void)
{
  fputs ("Usage: testhtml [<option>...]\n", stderr);
  fputs ("Options:\n", stderr);
  fputs ("  -allow <what>...           Allow (don't block) <what>\n", stderr);
  fputs ("  -annotation <astyle>       Select annotation style <astyle>\n", stderr);
  fputs ("  -attribute-length <n>      Set limit for length of attribute values\n", stderr);
  fputs ("  -block <what>...           Block (don't allow) <what>\n", stderr);
  fputs ("  -content-length <length>   Limit length of input\n", stderr);
  fputs ("  -drop-meta-content-type    Drop <META HTTP-EQUIV=Content-Type>\n", stderr);
  fputs ("  -log <event>...            Log <event>\n", stderr);
  fputs ("  -cookies <cookies>         How to parse cookies \n", stderr);
  fputs ("  -html-attributes <type>:<omode>[/log]...\n", stderr);
  fputs ("                             Policy for attributes\n", stderr);
  fputs ("  -html-tags <type>:<omode>[/log]...\n", stderr);
  fputs ("                             Policy for tags\n", stderr);
  fputs ("  -html-meta <type>:<omode>[/log]...\n", stderr);
  fputs ("                             Policy for meta tags\n", stderr);
  fputs ("  -html-references <type>:<omode>[/log]...\n", stderr);
  fputs ("                             Policy for references\n", stderr);
  fputs ("  -script <script-policy>    What to do with scripts\n", stderr);
  fputs ("<event>:\n", stderr);
  fputs ("  incorrect-tags             Incorrect tags\n", stderr);
  fputs ("  script-macros              Script macros in attributes\n", stderr);
  fputs ("  tag-attribute-pairs        Unpexted tag/attribute pairs\n", stderr);
  fputs ("<what>:\n", stderr);
  fputs ("  cookies                    Cookies\n", stderr);
  fputs ("  embed                      <EMBED>\n", stderr);
  fputs ("  java                       Java applets\n", stderr);
  fputs ("  javascript                 JavaScript / LiveScript\n", stderr);
  fputs ("  object                     <OBJECT> (ActiveX)\n", stderr);
  fputs ("  style                      Style sheets\n", stderr);
  fputs ("<cookies>:\n", stderr);
  fputs ("  nocheck                    Don't check cookies\n", stderr);
  fputs ("  netscape                   Relaxed parsing\n", stderr);
  fputs ("  rfc2109                    Strict parsing\n", stderr);
  fputs ("<astyle>:\n", stderr);
  fputs ("  ansi-color                 Use color via ANSI sequences\n", stderr);
  fputs ("  keywords                   Use keywords\n", stderr);
  fputs ("<type>:\n", stderr);
  fputs ("  dangerous                  Dangerous tag/attribute\n", stderr);
  fputs ("  invalid                    Invalid tag/attribute\n", stderr);
  fputs ("  on                         Unknown attributes starting with \"on\"\n", stderr);
  fputs ("  unknown                    Unknown tag/attribute/reference\n", stderr);
  fputs ("<rej>:\n", stderr);
  fputs ("  comment                    Replace with comment\n", stderr);
  fputs ("  copy                       Copy to output\n", stderr);
  fputs ("  drop                       Drop without any trace\n", stderr);
  fputs ("  escape                     Escape (make it printing)\n", stderr);
  fputs ("  prefix                     Prefix name with \"REMOVED-\"\n", stderr);
  fputs ("<script-policy>:\n", stderr);
  fputs ("  html                       Parse and rewrite as HTML\n", stderr);
  fputs ("  verbatim                   Copy verbatim\n", stderr);
  exit (1);
}

void quit (int rc)
{
  exit (rc);
}

static int recursion = 0;

void vsyslog (int prio, const char *fmt, va_list arg_ptr)
{
  if (recursion++ != 0)
    abort ();
  emo_flush (out);
  vfprintf (stderr, fmt, arg_ptr);
  fputc ('\n', stderr);
  fflush (stderr);
  --recursion;
}

void syslog (int prio, const char *fmt, ...)
{
  va_list arg_ptr;

  va_start (arg_ptr, fmt);
  vsyslog (prio, fmt, arg_ptr);
  va_end (arg_ptr);
}

/* In case all the syslog()-related functions are in one module... */

void openlog (const char *ident, int logopt, int facility)
{
}

void closelog (void)
{
}

int setlogmask (int maskpri)
{
  return 0;
}

void config_browsers (const char *s)
{
}

int ctype_policy (const char *ctype, char ***filters)
{
  return 0;
}

int config_destinations (const char *s, const struct url *u,
			 const char *method, const char **msg,
			 const char **redir)
{
  return 0;
}

int config_connect (const char *hostname, int port,
		    octet *ipaddr, const char **msg)
{
  return 0;
}

int config_href (const char *s, const struct url *u)
{
  return u->scheme != URL_OTHER_SCHEME ? 0 : -1; /* TODO */
}

static int bold = 0;
static enum html_type prev_type = HTML_NONE;
static long prev_amount = -1;

static void anno_start_ansi_color (enum html_type type)
{
  const char *bold_str;

  if (type == prev_type && emo_amount (out, 1) == prev_amount)
    bold = !bold;
  else
    {
      prev_type = type;
      bold = 0;
    }
  bold_str = bold ? ";1" : "";
  switch (type)
    {
    case HTML_TAG:
      emo_printf (out, "\033[0;32;40%sm", bold_str);
      break;
    case HTML_COMMENT:
      emo_printf (out, "\033[0;34;40%sm", bold_str);
      break;
    case HTML_MARKUPDECL:
      emo_printf (out, "\033[0;35;40%sm", bold_str);
      break;
    default:
      emo_puts (out, "\033[0;30;47m");
      break;
    }
  prev_amount = emo_amount (out, 1);
}

static void anno_start_keyword (enum html_type type)
{
  switch (type)
    {
    case HTML_TAG:
      emo_puts (out, "[ST]");
      break;
    case HTML_COMMENT:
      emo_puts (out, "[SC]");
      break;
    case HTML_MARKUPDECL:
      emo_puts (out, "[SM]");
      break;
    default:
      emo_puts (out, "[ERROR]");
      break;
    }
}

static void anno_end_keyword (enum html_type type)
{
  switch (type)
    {
    case HTML_TAG:
      emo_puts (out, "[ET]");
      break;
    case HTML_COMMENT:
      emo_puts (out, "[EC]");
      break;
    case HTML_MARKUPDECL:
      emo_puts (out, "[EM]");
      break;
    default:
      emo_puts (out, "[ERROR]");
      break;
    }
}

void html_callback (enum html_type type, int start)
{
  switch (anno_mode)
    {
    case ANNO_ANSI_COLOR:
      if (start)
	anno_start_ansi_color (type);
      else
	{
	  emo_puts (out, "\033[0m");
	  prev_amount = emo_amount (out, 1);
	}
      break;
    case ANNO_KEYWORDS:
      if (start)
	anno_start_keyword (type);
      else
	anno_end_keyword (type);
      break;
    default:
      break;
    }
}

static void prej (const char *s, struct rej_policy *dst, unsigned flags)
{
  if (parse_rej_policy (s, dst, flags) != 0)
    usage ();
}

int main (int argc, char *argv[])
{
  int i, block;
  const char *s;
  char *end;
  long n, content_length;

  html_default_config ();
  http_default_config ();
  cf_html.tag_dangerous.log = 0;
  cf_html.tag_invalid.log = 0;
  cf_html.tag_unknown.log = 0;
  cf_html.attr_alphanumeric.log = 0;
  cf_html.attr_dangerous.log = 0;
  cf_html.attr_novalue.log = 0;
  cf_html.attr_on.log = 0;
  cf_html.attr_unknown.log = 0;
  cf_html.ref_unknown.log = 0;
  cf_html.meta_unknown_name.log = 0;

  anno_mode = ANNO_NONE;
  content_length = LONG_MAX;

  i = 1;
  while (i < argc)
    if (strcmp (argv[i], "-block") == 0 || strcmp (argv[i], "-allow") == 0
	|| strcmp (argv[i], "-b") == 0 || strcmp (argv[i], "-a") == 0)
      {
	block = argv[i][1] != 'a';
	++i;
	if (i >= argc || argv[i][0] == '-')
	  usage ();
	while (i < argc && argv[i][0] != '-')
	  {
	    if (strcmp (argv[i], "cookies") == 0)
	      cf_http.block_cookies.v = block;
	    else if (strcmp (argv[i], "javascript") == 0)
	      {
		cf_html.block_javascript.v = block;
		cf_http.block_javascript.v = block;
	      }
	    else if (strcmp (argv[i], "java") == 0)
	      cf_html.block_java.v = block;
	    else if (strcmp (argv[i], "object") == 0)
	      cf_html.block_object.v = block;
	    else if (strcmp (argv[i], "embed") == 0)
	      cf_html.block_embed.v = block;
	    else if (strcmp (argv[i], "style") == 0)
	      {
		cf_html.block_style.v = block;
		cf_http.block_style.v = block;
	      }
	    else
	      usage ();
	    ++i;
	  }
      }
    else if (strcmp (argv[i], "-annotation") == 0)
      {
	if (++i >= argc)
	  usage ();
	cf_html.use_callback = 1;
	if (strcmp (argv[i], "ansi-color") == 0)
	  anno_mode = ANNO_ANSI_COLOR;
	else if (strcmp (argv[i], "keywords") == 0)
	  anno_mode = ANNO_KEYWORDS;
	else
	  usage ();
	++i;
      }
    else if (strcmp (argv[i], "-attribute-length") == 0)
      {
	if (++i >= argc)
	  usage ();
	n = strtol (argv[i], &end, 10);
	if (n < 64 || *end != 0 || n > INT_MAX)
	  usage ();
	cf_html.attr_value_limit = (int)n;
	++i;
      }
    else if (strcmp (argv[i], "-content-length") == 0)
      {
	if (++i >= argc)
	  usage ();
	content_length = strtol (argv[i], &end, 10);
	if (content_length < 0 || *end != 0 || end == argv[i])
	  usage ();
	++i;
      }
    else if (strcmp (argv[i], "-log") == 0)
      {
	++i;
	if (i >= argc || argv[i][0] == '-')
	  usage ();
	while (i < argc && argv[i][0] != '-')
	  {
	    if (strcmp (argv[i], "incorrect-tags") == 0)
	      cf_html.log_incorrect_tags = 1;
	    else if (strcmp (argv[i], "script-macros") == 0)
	      cf_html.log_script_macros = 1;
	    else if (strcmp (argv[i], "tag-attribute-pairs") == 0)
	      cf_html.log_key = 1;
	    else
	      usage ();
	    ++i;
	  }
      }
    else if (strcmp (argv[i], "-cookies") == 0)
      {
	++i;
	if (i >= argc || argv[i][0] == '-')
	  usage ();
	if (parse_cookies_mode (argv[i]) != 0)
	  usage ();
	++i;
      }
    else if (strcmp (argv[i], "-drop-meta-content-type") == 0)
      {
	cf_html.drop_meta_content_type.v = 1;
	++i;
      }
    else if (strcmp (argv[i], "-html-tags") == 0)
      {
	if (++i >= argc)
	  usage ();
	while (i < argc && argv[i][0] != '-')
	  {
	    s = argv[i++];
	    if (strncmp (s, "dangerous:", 10) == 0)
	      prej (s + 10, &cf_html.tag_dangerous,
		    RM_DROP | RM_PREFIX | RM_COMMENT | RM_ESCAPE);
	    else if (strncmp (s, "unknown:", 8) == 0)
	      prej (s + 8, &cf_html.tag_unknown,
		    RM_DROP | RM_PREFIX | RM_COMMENT | RM_ESCAPE | RM_COPY);
	    else if (strncmp (s, "invalid:", 8) == 0)
	      prej (s + 8, &cf_html.tag_invalid,
		    RM_DROP | RM_COMMENT | RM_ESCAPE);
	    else
	      usage ();
	  }
      }
    else if (strcmp (argv[i], "-html-attributes") == 0)
      {
	if (++i >= argc)
	  usage ();
	while (i < argc && argv[i][0] != '-')
	  {
	    s = argv[i++];
	    if (strncmp (s, "alphanumeric:", 13) == 0)
	      prej (s + 13, &cf_html.attr_alphanumeric,
		    RM_DROP | RM_PREFIX | RM_COPY);
	    else if (strncmp (s, "dangerous:", 10) == 0)
	      prej (s + 10, &cf_html.attr_dangerous,
		    RM_DROP | RM_PREFIX);
	    else if (strncmp (s, "novalue:", 8) == 0)
	      prej (s + 8, &cf_html.attr_novalue,
		    RM_DROP | RM_PREFIX | RM_COPY);
	    else if (strncmp (s, "on:", 3) == 0)
	      prej (s + 3, &cf_html.attr_on,
		    RM_DROP | RM_PREFIX | RM_COPY);
	    else if (strncmp (s, "unknown:", 8) == 0)
	      prej (s + 8, &cf_html.attr_unknown,
		    RM_DROP | RM_PREFIX | RM_COPY);
	    else
	      usage ();
	  }
      }
    else if (strcmp (argv[i], "-html-meta") == 0)
      {
	if (++i >= argc)
	  usage ();
	while (i < argc && argv[i][0] != '-')
	  {
	    s = argv[i++];
	    if (strncmp (s, "unknown:", 8) == 0)
	      prej (s + 8, &cf_html.meta_unknown_name,
		    RM_DROP | RM_PREFIX | RM_COMMENT | RM_ESCAPE | RM_COPY);
	    else
	      usage ();
	  }
      }
    else if (strcmp (argv[i], "-html-references") == 0)
      {
	if (++i >= argc)
	  usage ();
	while (i < argc && argv[i][0] != '-')
	  {
	    s = argv[i++];
	    if (strncmp (s, "unknown:", 8) == 0)
	      prej (s + 8, &cf_html.ref_unknown,
		    RM_DROP | RM_ESCAPE | RM_COPY);
	    else
	      usage ();
	  }
      }
    else if (strcmp (argv[i], "-script") == 0)
      {
	if (++i >= argc || argv[i][0] == '-')
	  usage ();
	s = argv[i++];
	if (strcmp (s, "html") == 0)
	  cf_html.script = CDATA_HTML;
	else if (strcmp (s, "verbatim") == 0)
	  cf_html.script = CDATA_VERBATIM;
	else
	  usage ();
      }
    else
      usage ();

  in = emi_fdopen (0, 4096);
  if (in == NULL)
    {
      perror ("emi_fdopen()");
      exit (2);
    }
  out = emo_fdopen (1, 4096);
  if (out == NULL)
    {
      perror ("emo_fdopen()");
      exit (2);
    }
  html_copy (out, in, content_length);
  if (anno_mode == ANNO_ANSI_COLOR)
    emo_puts (out, "\033[0m");
  if (emo_close (out) != 0)
    {
      perror ("emo_close()");
      exit (2);
    }
  return 0;
}
