/** mod_tidy.c -- a TidyLib based DSO module for the Apache HTTP Server Version 2

  COPYRIGHT NOTICE:
  
    Copyright [2002] [Sebastian Tusk]
    Copyright [2005] [Sierk Bornemann]

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

  For detailled information about this licence, please see the file called LICENCE.
   
  Contributing Author(s):

     Sebastian Tusk (Initial author)
     Sierk Bornemann <bornemann@users.sourceforge.net>

  The contributing author(s) would like to thank all those who
  helped with testing, bug fixes and suggestions for improvements. 
  This wouldn't have been possible without your help.


  Created 2005-07-14 by Sierk Bornemann
  Updated 2005-07-14 by Sierk Bornemann - 1st Implementation

*/
 
// #define MOD_TIDY_DEBUG
 
#include "apr_buckets.h"
#include "apr_strings.h"
#include "ap_config.h"
#include "util_filter.h"
#include "httpd.h"
#include "http_config.h"
#include "http_request.h"
#include "http_core.h"
#include "http_protocol.h"
#include "http_log.h"
#include "http_main.h"
#include "util_script.h"
#include "http_core.h"

#include "tidy.h"
#include "buffio.h"

module AP_MODULE_DECLARE_DATA tidy_module;

typedef struct {
	apr_table_t* tidyoptions;
} mod_tidy_config; 

// tidy memory management

//apr_thread_mutex_t *mod_tidy_g_MemMutex = NULL;
apr_pool_t *mod_tidy_g_MemPool = NULL;

void mod_tidy_FatalError( ctmbstr msg )
{
	fprintf( stderr, "Fatal error: %s\n", msg );
	exit(2);
}

void* mod_tidy_MemAlloc( size_t size )
{
	void *p;

	p = apr_pcalloc(mod_tidy_g_MemPool, size + sizeof( size_t ) );
	
	if ( p ) {
		*(size_t*)p = size;
    	p = (size_t*)p + 1;
	} else {
        mod_tidy_FatalError("Out of memory!");
	}
    return p;
}

void mod_tidy_MemFree( void* mem )
{
}

void* mod_tidy_MemRealloc( void* mem, size_t newsize )
{
	void *p;
	int oldsize;

	if (mem == (void *)NULL)
		return mod_tidy_MemAlloc(newsize);
		
	if (newsize == 0) {
		mod_tidy_MemFree( mem );
		return NULL;
	}
	
	oldsize = *((size_t*)mem - 1);

	if (oldsize >= newsize) {
		return mem;
	}
	
	p = mod_tidy_MemAlloc( newsize );
	if (!p)
		mod_tidy_FatalError("Out of memory!");
	else{
		memcpy( p, mem, oldsize );
		
		mod_tidy_MemFree( mem );
	}
    return p;
}

typedef struct mod_tidy_struct {
    apr_bucket_brigade *bb;
	apr_bucket_alloc_t *ba;
	ap_filter_t *f;
	
	TidyDoc	tidyDoc;
	TidyBuffer errbuf;
	TidyBuffer htmlbuf;
} mod_tidy_struct;

// apply options from table to tidy doc
static int mod_tidy_applyoptions(void *rec, const char *key, 
	const char *value)
{
	mod_tidy_struct *ctx = rec;
	
#ifdef MOD_TIDY_DEBUG
	printf( "mod_tidy_applyoptions\n" );
	printf( "key == %s\n", key );
	printf( "value == %s\n", value );
#endif // #ifdef MOD_TIDY_DEBUG
	
	if (!tidyOptParseValue(ctx->tidyDoc, key, value )) {
		ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, ctx->f->r->server,
			"error occured 'TidyOption %s %s'", key, value );
	}
	
	return TRUE;
}

static int mod_tidy_filter(ap_filter_t *f, apr_bucket_brigade *bb)
{
    mod_tidy_struct *ctx = f->ctx;
    apr_bucket *e;
    apr_pool_t *p = f->r->pool;
    char *word1 = NULL, *word2 = NULL;

#ifdef MOD_TIDY_DEBUG
	printf( ">>>mod_tidy_filter( 0x%X, 0x%X )\n", f, bb );
	printf( "ctx == 0x%X\n", ctx );
#endif // #ifdef MOD_TIDY_DEBUG

	//apr_thread_mutex_lock( mod_tidy_g_MemMutex );
	mod_tidy_g_MemPool = f->r->pool;
	
	if (ctx == NULL) {
		mod_tidy_config *cfg = ap_get_module_config(
    		f->r->per_dir_config, &tidy_module);
	
		f->ctx = ctx = apr_pcalloc(f->r->pool, sizeof(*ctx));
		ctx->ba = apr_bucket_alloc_create(f->r->pool);
		ctx->bb = apr_brigade_create(f->r->pool, ctx->ba);
		ctx->f = f;
		
		tidySetMallocCall( mod_tidy_MemAlloc );
		tidySetReallocCall( mod_tidy_MemRealloc );
		tidySetFreeCall( mod_tidy_MemFree );
		tidySetPanicCall( mod_tidy_FatalError );

		ctx->tidyDoc = tidyCreate();
		memset( &(ctx->errbuf), 0, sizeof( ctx->errbuf ) );
		memset( &(ctx->htmlbuf), 0, sizeof( ctx->htmlbuf ) );
		
		tidySetErrorBuffer( ctx->tidyDoc, &(ctx->errbuf) );
		
		// set tidy options; iterate all options
		apr_table_do( mod_tidy_applyoptions, ctx, 
			cfg->tidyoptions, NULL );
	}

    APR_BRIGADE_FOREACH(e, bb) {
        const char *str;
        apr_size_t len;
		
#ifdef MOD_TIDY_DEBUG
		printf( "APR_BRIGADE_FOREACH(e, bb)\n" );
		printf( "e == 0x%X\n", e );
#endif // #ifdef MOD_TIDY_DEBUG

        if (APR_BUCKET_IS_EOS(e)) { //* || APR_BUCKET_IS_FLUSH(e)
			int errors = 0;
			int line = 1;
			TidyBuffer tidyhtmlbuf;
		
			tidyParseBuffer( ctx->tidyDoc, &ctx->htmlbuf );
			ctx->htmlbuf.next = 0;
			
			memset( &(tidyhtmlbuf), 0, sizeof( tidyhtmlbuf ) );
			tidySaveBuffer( ctx->tidyDoc, &tidyhtmlbuf );
			
			if (tidyErrorCount(ctx->tidyDoc) || tidyWarningCount(ctx->tidyDoc) ) {
				errors = 1;
			}
		
			if (errors) {
				char strline[5];
				TidyBuffer escaped_errbuf;
				
				tidyBufAppend( &ctx->errbuf, "\n\n", 2 );
				tidyErrorSummary( ctx->tidyDoc );
				
				// replace & < > with named entities
				memset( &(escaped_errbuf), 0, sizeof( escaped_errbuf ) );
				
				while( !tidyBufEndOfInput( &ctx->errbuf ) ) {
					int ch = tidyBufGetByte( &ctx->errbuf );
					
					if (ch == '&') {
						tidyBufAppend( &escaped_errbuf, "&amp;", 5 );
					}else if (ch == '<') {
						tidyBufAppend( &escaped_errbuf, "&lt;", 4 );
					}else if (ch == '>') {
						tidyBufAppend( &escaped_errbuf, "&gt;", 4 );
					}else if (ch == '\n') {
						tidyBufAppend( &escaped_errbuf, "<br/>\n", 6 );
					}else{
						tidyBufPutByte( &escaped_errbuf, ch );
					}
				}
				
				tidyBufAppend( &escaped_errbuf, "<h3>source</h3>\n", 16 );
				
				tidyBufAppend( &escaped_errbuf, "001 ", 4 );
				line = 1;
				
				while( !tidyBufEndOfInput( &ctx->htmlbuf ) ) {
					int ch = tidyBufGetByte( &ctx->htmlbuf );
					
					if (ch == '&') {
						tidyBufAppend( &escaped_errbuf, "&amp;", 5 );
					}else if (ch == '<') {
						tidyBufAppend( &escaped_errbuf, "&lt;", 4 );
					}else if (ch == '>') {
						tidyBufAppend( &escaped_errbuf, "&gt;", 4 );
					}else if (ch == '\n') {
						tidyBufAppend( &escaped_errbuf, "<br/>\n", 6 );
						line ++;
						if (line < 1000) {
							sprintf( strline, "%0.3d ", line );
							tidyBufAppend( &escaped_errbuf, strline, 4 );
						}
					}else{
						tidyBufPutByte( &escaped_errbuf, ch );
					}
				}
				
				tidyBufAppend( &escaped_errbuf, "<h3>tidy output</h3>\n", 21 );
				
				tidyBufAppend( &escaped_errbuf, "001 ", 4 );
				line = 1;
				
				while( !tidyBufEndOfInput( &tidyhtmlbuf ) ) {
					int ch = tidyBufGetByte( &tidyhtmlbuf );
					
					if (ch == '&') {
						tidyBufAppend( &escaped_errbuf, "&amp;", 5 );
					}else if (ch == '<') {
						tidyBufAppend( &escaped_errbuf, "&lt;", 4 );
					}else if (ch == '>') {
						tidyBufAppend( &escaped_errbuf, "&gt;", 4 );
					}else if (ch == '\n') {
						tidyBufAppend( &escaped_errbuf, "<br/>\n", 6 );
						line ++;
						if (line < 1000) {
							sprintf( strline, "%0.3d ", line );
							tidyBufAppend( &escaped_errbuf, strline, 4 );
						}
					}else{
						tidyBufPutByte( &escaped_errbuf, ch );
					}
				}
							
				ap_fputstrs(f->next, ctx->bb, 
					"<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n"
					"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \n"
					"\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n"
					"<html xmlns=\"http://www.w3.org/1999/xhtml\">\n"
					"<head>\n"
					"<title>mod_tidy error log</title>\n"
					"</head>\n"
					"<body>\n"
					"<h3>mod_tidy error log</h3>\n",
					escaped_errbuf.bp,
					"\n</body>\n"
					"</html>\n",
					NULL );
			}else{
				ap_fputstrs(f->next, ctx->bb, ctx->htmlbuf.bp, NULL );
			}
		
            APR_BUCKET_REMOVE(e);
            APR_BRIGADE_INSERT_TAIL(ctx->bb, e);
            ap_pass_brigade(f->next, ctx->bb);
			
			mod_tidy_g_MemPool = NULL;
			//apr_thread_mutex_unlock( mod_tidy_g_MemMutex );
			
#ifdef MOD_TIDY_DEBUG
			printf( "<<<mod_tidy_filter( 0x%X, 0x%X ) eos\n", f, bb );
#endif // #ifdef MOD_TIDY_DEBUG
			
            return APR_SUCCESS;
        }
		/*if(APR_BUCKET_IS_FLUSH(e)) {
			APR_BUCKET_REMOVE(e);
            APR_BRIGADE_INSERT_TAIL(ctx->bb, e);
            ap_pass_brigade(f->next, ctx->bb);
			
			mod_tidy_g_MemAllocator = NULL;
			//apr_thread_mutex_unlock( mod_tidy_g_MemMutex );
			
#ifdef MOD_TIDY_DEBUG
			printf( "<<<mod_tidy_filter( 0x%X, 0x%X ) flush\n", f, bb );
#endif // #ifdef MOD_TIDY_DEBUG
			
            return APR_SUCCESS;
		}*/
		apr_bucket_read(e, &str, &len, APR_NONBLOCK_READ);
		APR_BUCKET_REMOVE(e);

		if (str) {
			tidyBufAppend( &ctx->htmlbuf, (void*)str, len );
		}
    }
	
	mod_tidy_g_MemPool = NULL;
	//apr_thread_mutex_unlock( mod_tidy_g_MemMutex );
	
#ifdef MOD_TIDY_DEBUG
	printf( "<<<mod_tidy_filter( 0x%X, 0x%X )\n", f, bb );
#endif // #ifdef MOD_TIDY_DEBUG
	
    return APR_SUCCESS;
}

static void mod_tidy_register_hook(apr_pool_t *p)
{
    ap_register_output_filter("TIDY", mod_tidy_filter, NULL, AP_FTYPE_CONTENT_SET);
}

static void *mod_tidy_create_dirconfig(apr_pool_t *p, char *dir)
{
	// module config
	mod_tidy_config* cfg;
	
	cfg = (mod_tidy_config*)apr_pcalloc(p, sizeof(mod_tidy_config));

	cfg->tidyoptions = apr_table_make(p, 4);

	return cfg;
}

static const char *set_mod_tidy_option(cmd_parms *parms,
  void *mconfig, const char *w1, const char *w2)
{
	mod_tidy_config* cfg = (mod_tidy_config*)mconfig;
	
#ifdef MOD_TIDY_DEBUG
	printf( "set_mod_tidy_option\n" );
	printf( "key == %s\n", w1 );
	printf( "value == %s\n", w2 );
#endif // #ifdef MOD_TIDY_DEBUG

	// append option; w1==key; w2==value
	apr_table_set( cfg->tidyoptions, w1, w2 );

	return NULL;
}

static const command_rec mod_tidy_directives[] =
{
	AP_INIT_TAKE2(
		"TidyOption",
		set_mod_tidy_option,
		NULL,
		ACCESS_CONF|OR_OPTIONS,
		"TidyOption name value  see tidy libary documentation "\
		" http://tidy.sourceforge.net/"
	),
	{NULL}
};

module AP_MODULE_DECLARE_DATA tidy_module = 
{
    STANDARD20_MODULE_STUFF,
    mod_tidy_create_dirconfig,			/* create per-directory config structure */
    NULL,        		/* merge per-directory config structures */
    NULL,			/* create per-server config structure */
    NULL,			/* merge per-server config structures */
    mod_tidy_directives,			/* command apr_table_t */
    mod_tidy_register_hook		/* register hooks */
};
