/*----------------------------------------------------------------------------*/
/* Hobbit monitor network test tool.                                          */
/*                                                                            */
/* This is used to implement the testing of HTTP service.                     */
/*                                                                            */
/* Copyright (C) 2004-2006 Henrik Storner <henrik@hswn.dk>                    */
/*                                                                            */
/* This program is released under the GNU General Public License (GPL),       */
/* version 2. See the file "COPYING" for details.                             */
/*                                                                            */
/*----------------------------------------------------------------------------*/

static char rcsid[] = "$Id: httpresult.c,v 1.25 2006/07/20 16:06:41 henrik Rel $";

#include <sys/types.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <regex.h>
#include <ctype.h>
#include <sys/stat.h>

#include "libbbgen.h"

#include "bbtest-net.h"
#include "contest.h"
#include "httpresult.h"

static int statuscolor(testedhost_t *h, long status)
{
	int result;

	switch(status) {
	  case 000:			/* transportlayer reports error */
		result = (h->dialup ? COL_CLEAR : COL_RED);
		break;
	  case 100: /* Continue - should be ok */
	  case 200: case 201: case 202: case 203: case 204: case 205: case 206:
	  case 301: case 302: case 303: case 307:
	  case 401: case 403: 		/* Is "Forbidden" an OK status ? */
		result = COL_GREEN;
		break;
	  case 400: case 404: case 405: case 406:
		result = COL_RED;	/* Trouble getting page */
		break;
	  case 500:
	  case 501:
	  case 502:  /* Proxy error */
	  case 503:
	  case 504:
	  case 505:
		result = COL_RED;	/* Server error */
		break;
	  case STATUS_CONTENTMATCH_FAILED:
		result = COL_RED;		/* Pseudo status: content match fails */
		break;
	  case STATUS_CONTENTMATCH_BADREGEX:	/* Pseudo status: bad regex to match against */
	  case STATUS_CONTENTMATCH_NOFILE:	/* Pseudo status: content match requested, but no match-file */
		result = COL_YELLOW;
		break;
	  default:
		result = COL_YELLOW;	/* Unknown status */
		break;
	}

	return result;
}


void send_http_results(service_t *httptest, testedhost_t *host, testitem_t *firsttest,
		       char *nonetpage, int failgoesclear)
{
	testitem_t *t;
	int	color = -1;
	char    *svcname;
	strbuffer_t *msgtext;
	char    *nopagename;
	int     nopage = 0;
	int	anydown = 0, totalreports = 0;

	if (firsttest == NULL) return;

	svcname = strdup(httptest->testname);
	if (httptest->namelen) svcname[httptest->namelen] = '\0';

	/* Check if this service is a NOPAGENET service. */
	nopagename = (char *) malloc(strlen(svcname)+3);
	sprintf(nopagename, ",%s,", svcname);
	nopage = (strstr(nonetpage, svcname) != NULL);
	xfree(nopagename);

	dbgprintf("Calc http color host %s : ", host->hostname);

	msgtext = newstrbuffer(0);
	for (t=firsttest; (t && (t->host == host)); t = t->next) {
		http_data_t *req = (http_data_t *) t->privdata;

		/* Skip the data-reports for now */
		if (t->senddata) continue;

		totalreports++;
		req->httpcolor = statuscolor(host, req->httpstatus);
		if (req->httpcolor == COL_RED) anydown++;

		/* Dialup hosts and dialup tests report red as clear */
		if ((req->httpcolor != COL_GREEN) && (host->dialup || t->dialup)) req->httpcolor = COL_CLEAR;

		/* If ping failed, report CLEAR unless alwaystrue */
		if ( ((req->httpcolor == COL_RED) || (req->httpcolor == COL_YELLOW)) && /* Test failed */
		     (host->downcount > 0)                   && /* The ping check did fail */
		     (!host->noping && !host->noconn)        && /* We are doing a ping test */
		     (failgoesclear)                         &&
		     (!t->alwaystrue)                           )  /* No "~testname" flag */ {
			req->httpcolor = COL_CLEAR;
		}

		/* If test we depend on has failed, report CLEAR unless alwaystrue */
		if ( ((req->httpcolor == COL_RED) || (req->httpcolor == COL_YELLOW)) && /* Test failed */
		      failgoesclear && !t->alwaystrue )  /* No "~testname" flag */ {
			char *faileddeps = deptest_failed(host, t->service->testname);

			if (faileddeps) {
				req->httpcolor = COL_CLEAR;
				req->faileddeps = strdup(faileddeps);
			}
		}

		dbgprintf("%s(%s) ", t->testspec, colorname(req->httpcolor));
		if (req->httpcolor > color) color = req->httpcolor;

		/* Build the short msgtext which goes on line 1 of the status message. */
		addtobuffer(msgtext, (STRBUFLEN(msgtext) ? " ; " : ": ") );
		if (req->tcptest->errcode != CONTEST_ENOERROR) {
			switch (req->tcptest->errcode) {
			  case CONTEST_ETIMEOUT: 
				  req->errorcause = "Server timeout"; break;
			  case CONTEST_ENOCONN : 
				  req->errorcause =  strdup(strerror(req->tcptest->connres)); break;
			  case CONTEST_EDNS    : 
				  switch (req->parsestatus) {
					  case 1 : req->errorcause =  "Invalid URL"; break;
					  case 2 : req->errorcause =  "Hostname not in DNS"; break;
					  default: req->errorcause =  "DNS error"; break;
				  }
				  break;
			  case CONTEST_EIO     : 
				  req->errorcause =  "I/O error"; break;
			  case CONTEST_ESSL    : 
				  req->errorcause =  "SSL error"; break;
			  default: 
				  req->errorcause =  "Xfer failed";
			}

			addtobuffer(msgtext, req->errorcause);
		} 
		else if (req->tcptest->open == 0) {
			req->errorcause = "Connect failed";
			addtobuffer(msgtext, req->errorcause);
		}
		else if ((req->httpcolor == COL_RED) || (req->httpcolor == COL_YELLOW)) {
			char m1[30];

			if (req->headers) {
				char *p = req->headers;

				/* Skip past "HTTP/1.x 200 " and pick up the explanatory text, if any */
				if (strncasecmp(p, "http/", 5) == 0) {
					p += 5;
					p += strspn(p, "0123456789. ");
				}

				strncpy(m1, p, sizeof(m1)-1);
				m1[sizeof(m1)-1] = '\0';
			}
			else {
				sprintf(m1, "HTTP error %ld", req->httpstatus);
			}
			addtobuffer(msgtext, m1);
			req->errorcause = strdup(m1);
		}
		else {
			addtobuffer(msgtext, "OK");
		}
	}

	/* It could be that we have 0 http tests - if we only do the apache one */
	if (totalreports > 0) {
		char msgline[4096];

		if (anydown) {
			firsttest->downcount++; 
			if(firsttest->downcount == 1) firsttest->downstart = time(NULL);
		} 
		else firsttest->downcount = 0;

		/* Handle the "badtest" stuff for http tests */
		if ((color == COL_RED) && (firsttest->downcount < firsttest->badtest[2])) {
			if      (firsttest->downcount >= firsttest->badtest[1]) color = COL_YELLOW;
			else if (firsttest->downcount >= firsttest->badtest[0]) color = COL_CLEAR;
			else                                                    color = COL_GREEN;
		}

		if (nopage && (color == COL_RED)) color = COL_YELLOW;
		dbgprintf(" --> %s\n", colorname(color));

		/* Send off the http status report */
		init_status(color);
		sprintf(msgline, "status %s.%s %s %s", 
			commafy(host->hostname), svcname, colorname(color), timestamp);
		addtostatus(msgline);
		addtostrstatus(msgtext);
		addtostatus("\n");

		for (t=firsttest; (t && (t->host == host)); t = t->next) {
			char *urlmsg;
			http_data_t *req = (http_data_t *) t->privdata;

			/* Skip the "data" reports */
			if (t->senddata) continue;

			urlmsg = (char *)malloc(1024 + strlen(req->url));
			sprintf(urlmsg, "\n&%s %s - ", colorname(req->httpcolor), req->url);
			addtostatus(urlmsg);

			if (req->httpcolor == COL_GREEN) addtostatus("OK");
			else {
				if (req->errorcause) addtostatus(req->errorcause);
				else addtostatus("failed");
			}
			addtostatus("\n");

			if (req->headers) {
				addtostatus("\n");
				addtostatus(req->headers);
			}
			if (req->faileddeps) addtostatus(req->faileddeps);

			sprintf(urlmsg, "\nSeconds: %5d.%02d\n\n", 
				(unsigned int)req->tcptest->totaltime.tv_sec, 
				(unsigned int)req->tcptest->totaltime.tv_usec / 10000 );
			addtostatus(urlmsg);
			xfree(urlmsg);
		}
		addtostatus("\n\n");
		finish_status();
	}

	/* Send off any "data" messages now */
	for (t=firsttest; (t && (t->host == host)); t = t->next) {
		http_data_t *req;
		char *data = "";
		char *msg;

		if (!t->senddata) continue;

		req = (http_data_t *) t->privdata;
		if (req->output) data = req->output;

		msg = (char *)malloc(1024 + strlen(host->hostname) + strlen(req->bburl.columnname) + strlen(data));
		sprintf(msg, "data %s.%s\n%s", commafy(host->hostname), req->bburl.columnname, data);
		sendmessage(msg, NULL, NULL, NULL, 0, BBTALK_TIMEOUT);
		xfree(msg);
	}

	xfree(svcname);
	freestrbuffer(msgtext);
}


static testitem_t *nextcontenttest(service_t *httptest, testedhost_t *host, testitem_t *current)
{
	testitem_t *result;

	result = current->next;

	if ((result == NULL) || (result->host != host)) {
		result = NULL;
	}

	return result;
}

void send_content_results(service_t *httptest, testedhost_t *host,
			  char *nonetpage, char *contenttestname, int failgoesclear)
{
	testitem_t *t, *firsttest;
	int	color = -1;
	char    *nopagename;
	int     nopage = 0;
	char    *conttest;
	int 	contentnum = 0;
	conttest = (char *) malloc(128);

	if (host->firsthttp == NULL) return;

	/* Check if this service is a NOPAGENET service. */
	nopagename = (char *) malloc(strlen(contenttestname)+3);
	sprintf(nopagename, ",%s,", contenttestname);
	nopage = (strstr(nonetpage, contenttestname) != NULL);
	xfree(nopagename);

	dbgprintf("Calc content color host %s : ", host->hostname);

	firsttest = host->firsthttp;

	for (t=firsttest; (t && (t->host == host)); t = nextcontenttest(httptest, host, t)) {
		http_data_t *req = (http_data_t *) t->privdata;
		char cause[100];
		char *msgline;
		int got_data = 1;

		/* Skip the "data"-only messages */
		if (t->senddata) continue;
		if (!req->contentcheck) continue;

		/* We have a content check */
		strcpy(cause, "Content OK");
		if (req->contstatus == 0) {
			/* The content check passed initial checks of regexp etc. */
			color = statuscolor(t->host, req->httpstatus);
			if (color == COL_GREEN) {
				/* We got the data from the server */
				int status = 0;

				switch (req->contentcheck) {
				  case CONTENTCHECK_REGEX:
					if (req->output) {
						regmatch_t foo[1];

						status = regexec((regex_t *) req->exp, req->output, 0, foo, 0);
						regfree((regex_t *) req->exp);
					}
					else {
						/* output may be null if we only got a redirect */
						status = STATUS_CONTENTMATCH_FAILED;
					}
					break;

				  case CONTENTCHECK_NOREGEX:
					if (req->output) {
						regmatch_t foo[1];

						status = (!regexec((regex_t *) req->exp, req->output, 0, foo, 0));
						regfree((regex_t *) req->exp);
					}
					else {
						/* output may be null if we only got a redirect */
						status = STATUS_CONTENTMATCH_FAILED;
					}
					break;

				  case CONTENTCHECK_DIGEST:
					if (req->digest == NULL) req->digest = strdup("");
					if (strcmp(req->digest, (char *)req->exp) != 0) {
						status = STATUS_CONTENTMATCH_FAILED;
					}
					else status = 0;

					req->output = (char *) malloc(strlen(req->digest)+strlen((char *)req->exp)+strlen("Expected:\nGot     :\n")+1);
					sprintf(req->output, "Expected:%s\nGot     :%s\n", 
						(char *)req->exp, req->digest);
					break;

				  case CONTENTCHECK_CONTENTTYPE:
					if (req->contenttype && (strcasecmp(req->contenttype, (char *)req->exp) == 0)) {
						status = 0;
					}
					else {
						status = STATUS_CONTENTMATCH_FAILED;
					}

					if (req->contenttype == NULL) req->contenttype = strdup("No content-type provdied");

					req->output = (char *) malloc(strlen(req->contenttype)+strlen((char *)req->exp)+strlen("Expected content-type: %s\nGot content-type     : %s\n")+1);
					sprintf(req->output, "Expected content-type: %s\nGot content-type     : %s\n",
						(char *)req->exp, req->contenttype);
					break;
				}

				req->contstatus = ((status == 0)  ? 200 : STATUS_CONTENTMATCH_FAILED);
				color = statuscolor(t->host, req->contstatus);
				if (color != COL_GREEN) strcpy(cause, "Content match failed");
			}
			else {
				/*
				 * Failed to retrieve the webpage.
				 * Report CLEAR, unless "alwaystrue" is set.
				 */
				if (failgoesclear && !t->alwaystrue) color = COL_CLEAR;
				got_data = 0;
				strcpy(cause, "Failed to get webpage");
			}

			if (nopage && (color == COL_RED)) color = COL_YELLOW;
		}
		else {
			/* This only happens upon internal errors in Hobbit test system */
			color = statuscolor(t->host, req->contstatus);
			strcpy(cause, "Internal Hobbit error");
		}

		/* Send the content status message */
		dbgprintf("Content check on %s is %s\n", req->url, colorname(color));

		if (req->bburl.columnname) {
			strcpy(conttest, req->bburl.columnname);
		}
		else {
			if (contentnum > 0) sprintf(conttest, "%s%d", contenttestname, contentnum);
			else strcpy(conttest, contenttestname);

			contentnum++;
		}

		msgline = (char *)malloc(4096 + (2 * strlen(req->url)));
		init_status(color);
		sprintf(msgline, "status %s.%s %s %s: %s\n", 
			commafy(host->hostname), conttest, colorname(color), timestamp, cause);
		addtostatus(msgline);

		if (!got_data) {
			if (host->hidehttp) {
				sprintf(msgline, "\nContent check failed\n");
			}
			else {
				sprintf(msgline, "\nAn error occurred while testing <a href=\"%s\">URL %s</a>\n", 
					req->url, req->url);
			}
		}
		else {
			if (host->hidehttp) {
				sprintf(msgline, "\n&%s Content check %s\n",
					colorname(color), ((color == COL_GREEN) ? "OK" : "Failed"));
			}
			else {
				sprintf(msgline, "\n&%s %s - Testing <a href=\"%s\">URL</a> yields:\n",
					colorname(color), req->url, req->url);
			}
		}
		addtostatus(msgline);
		xfree(msgline);

		if (req->output == NULL) {
			addtostatus("\nNo output received from server\n\n");
		}
		else if (!host->hidehttp) {
			/* Dont flood hobbitd with data */
			if (req->outlen > MAX_CONTENT_DATA) {
				*(req->output + MAX_CONTENT_DATA) = '\0';
				req->outlen = MAX_CONTENT_DATA;
			}

			if ( (req->contenttype && (strncasecmp(req->contenttype, "text/html", 9) == 0)) ||
			     (strncasecmp(req->output, "<html", 5) == 0) ) {
				char *bodystart = NULL;
				char *bodyend = NULL;

				bodystart = strstr(req->output, "<body");
				if (bodystart == NULL) bodystart = strstr(req->output, "<BODY");
				if (bodystart) {
					char *p;

					p = strchr(bodystart, '>');
					if (p) bodystart = (p+1);
				}
				else bodystart = req->output;

				bodyend = strstr(bodystart, "</body");
				if (bodyend == NULL) bodyend = strstr(bodystart, "</BODY");
				if (bodyend) {
					*bodyend = '\0';
				}

				addtostatus("<div>\n");
				addtostatus(bodystart);
				addtostatus("\n</div>\n");
			}
			else {
				addtostatus(req->output);
			}
		}

		addtostatus("\n\n");
		finish_status();
	}

	xfree(conttest);
}


void show_http_test_results(service_t *httptest)
{
	http_data_t *req;
	testitem_t *t;

	for (t = httptest->items; (t); t = t->next) {
		req = (http_data_t *) t->privdata;

		printf("URL                      : %s\n", req->url);
		printf("HTTP status              : %lu\n", req->httpstatus);
		printf("HTTP headers\n%s\n", textornull(req->headers));
		printf("HTTP output\n%s\n", textornull(req->output));
		printf("------------------------------------------------------\n");
	}
}

