/*
 * (C) 2013 Jani Hautamaki <jani.hautamaki@uta.fi>
 *
 * Demonstrates a bug in libxml2's HTML push parser's recovery mode.
 * Known to occur in mod_proxy_html (for apache2) at least.
 *
 * Symptoms in mod_proxy_html: undeterministic behaviour of HTMLURLMap,
 * because the chunk size varies between  each request. With some bad
 * luck the chunk ends right at the middle of an ending </script> tag
 * causing the rest of the page to be ignored by the filter because
 * no startElement() events are triggered.
 *
 * Compile with:
 *     gcc html-push-recovery-script-bug.c -I/usr/include/libxml2 -lxml2
 *
 */

#include <stdio.h>
#include <string.h>
#include <libxml/parser.h>
#include <libxml/tree.h>
#include <libxml/HTMLparser.h>

/*
 * Chunk size 52 is tailored so that the chunk ends
 * inside the </script> tag.
 */
#define CHUNK_SIZE 52

/*
 * This chunk size can hold the whole data,
 * and demonstrates the expected behaviour.
 */
//#define CHUNK_SIZE 128

/* The data; a simple web page with a script */
const char *data
    = "<html><head><title>x</title><script src=\"file.js\"></script>"
      "</head><body><p>1</p><p>2</p></body></html>";

static void pstartElement(
   void* ctxt,
   const xmlChar* uname,
   const xmlChar** uattrs
) {
   printf("startElement: <%s>\n", (const char *) uname);
}

static void dump_chunk(const char *s, int len) {
   int i;
   printf("htmlParseChunk: ");
   for (i = 0; i < len; i++) {
      char c = s[i];
      if (c == '\n') printf("\\n");
      else printf("%c", c);
   }
   printf("<end-of-chunk>\n");
}

int main(void) {
   htmlSAXHandler sax;
   htmlParserCtxtPtr parser;
   int bytes, rval;
   unsigned int offset, size;

   memset(&sax, 0, sizeof(htmlSAXHandler));
   sax.startElement = pstartElement;
   offset = 0;
   size = strlen(data);

   bytes = 4;
   parser = htmlCreatePushParserCtxt(&sax, &parser, &data[offset], bytes, 0, 0);
   offset += bytes;

   // The program works fine if the recovery mode is commented out
   xmlCtxtUseOptions(parser, XML_PARSE_RECOVER);

   while (offset < size) {
        bytes = size-offset;
        if (bytes > CHUNK_SIZE) bytes = CHUNK_SIZE;
        dump_chunk(&data[offset], bytes);
        rval = htmlParseChunk(parser, &data[offset], bytes, 0);
        offset += bytes;
        printf("lastError: (%d) %s\n", rval, parser->lastError.message);
   }

   rval = htmlParseChunk(parser, &data[offset], 0, 1);
   printf("lastError: (%d) %s\n", rval, parser->lastError.message);

   xmlCleanupParser();
   xmlMemoryDump();
}

