/*
 * Copyright (C), 2000-2007 by the monit project group.
 * All Rights Reserved.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <config.h>

#ifdef HAVE_STDIO_H
#include <stdio.h>
#endif

#ifdef HAVE_ERRNO_H
#include <errno.h>
#endif

#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif

#ifdef HAVE_REGEX_H
#include <regex.h>
#endif

#ifdef HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif

#ifdef HAVE_STRING_H
#include <string.h>
#endif

#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif

#include "md5.h"
#include "sha.h"
#include "base64.h"
#include "protocol.h"
#include "httpstatus.h"


/**
 *  A HTTP test.
 *
 *  We send the following request to the server:
 *  'GET / HTTP/1.1'             ... if request statement isn't defined
 *  'GET /custom/page  HTTP/1.1' ... if request statement is defined
 *  and check the server's status code.
 *
 *  If the statement defines hostname, it's used in the 'Host:' header
 * otherwise a default (empty) Host header is set.
 *
 *  If the status code is >= 400, an error has occurred.
 *  Return TRUE if the status code is OK, otherwise FALSE.
 *
 *  @author Jan-Henrik Haukeland, <hauk@tildeslash.com>
 *  @author Martin Pala, <martinp@tildeslash.com>
 *  @version \$Id: http.c,v 1.54 2007/10/16 03:05:41 hauk Exp $
 *  @file
 */



/* ------------------------------------------------------------- Definitions */


#undef   READ_SIZE
#define  READ_SIZE  8192
#define  LINE_SIZE  512


/* -------------------------------------------------------------- Prototypes */


static int check_request(Socket_T s, Port_T P);
static char *get_auth_header(Port_T P, char *auth, int l);
static int do_regex(Socket_T s, long content_length, Request_T R);
static int check_request_checksum(Socket_T s, long content_length, char *checksum, int hashtype);


/* ------------------------------------------------------------------ Public */


int check_http(Socket_T s) {

  Port_T P;
  char host[STRLEN];
  char auth[STRLEN]= {0};
  const char *request= NULL;
  
  ASSERT(s);

  P= socket_get_Port(s);

  ASSERT(P);
  
  request= P->request?P->request:"/";
 
  if(socket_print(s, 
		  "GET %s HTTP/1.1\r\n"
		  "Host: %s\r\n"
		  "Accept: */*\r\n"
		  "Connection: close\r\n"
		  "User-Agent: %s/%s\r\n"
		  "%s\r\n",
		  request, Util_getHTTPHostHeader(s, host, STRLEN), 
		  prog, VERSION, get_auth_header(P, auth, STRLEN)) < 0) {
    LogError("HTTP: error sending data -- %s\n", STRERROR);
    return FALSE;
  }

  return check_request(s, P);
  
}


/* ----------------------------------------------------------------- Private */


/**
 * Check that the server returns a valid HTTP response as well as checksum
 * or content regex if required
 * @param s A socket
 * @return TRUE if the response is valid otherwise FALSE
 */
static int check_request(Socket_T s, Port_T P) {

  int status;
  char buf[LINE_SIZE];
  long content_length= -1;

    if(! socket_readln(s, buf, LINE_SIZE)) {
      LogError("HTTP: error receiving data -- %s\n", STRERROR);
      return FALSE;
    }

    Util_chomp(buf);

    if(! sscanf(buf, "%*s %d", &status)) {
      LogError("HTTP error: cannot parse HTTP status in response: %s\n", buf);
      return FALSE;
    }
         
    if(status >= 400) {
      LogError("HTTP error: Server returned status %d\n", status);
      return FALSE;
    }
        
    /* Get Content-Length header value */
    while(NULL != socket_readln(s, buf, LINE_SIZE)) {

      if((buf[0] == '\r' && buf[1] == '\n') || (buf[0] == '\n'))
        break;

      Util_chomp(buf);

      if(Util_startsWith(buf, "Content-Length")) {
        if(! sscanf(buf, "%*s%*[: ]%ld", &content_length)) {
          LogError("HTTP error: parsing Content-Length response header '%s'\n",
            buf);
          return FALSE;
        }
        if(content_length < 0) {
          LogError("HTTP error: Illegal Content-Length response header '%s'\n",
            buf);
          return FALSE;
        }
      }

    }
  
    if(P->url_request && P->url_request->regex) {
      if(! do_regex(s, content_length, P->url_request)) {
        LogError("HTTP error: Failed regular expression test on content"
          " returned from server\n");
        return FALSE;
      }
    }

    if(P->request_checksum) {  
      return check_request_checksum(s, content_length, P->request_checksum, 
        P->request_hashtype); 
    }

    return TRUE;

}


static int check_request_checksum(Socket_T s, long content_length, char *checksum, int hashtype) {

  int n;
  long size;
  MD_T result;
  char buf[READ_SIZE];
  unsigned char hash[STRLEN];
  int  keylength=0;
  
  if(content_length <= 0) {
    DEBUG("HTTP warning: Response does not contain a valid Content-Length\n"
      "Cannot compute checksum\n");
    return TRUE;
  }
  
  switch (hashtype) {
    case HASH_MD5:
    {
      struct md5_ctx ctx;
      md5_init_ctx(&ctx);
      while(content_length > 0) {
        size= content_length>READ_SIZE?READ_SIZE:content_length;
        n= socket_read(s, buf, size);
        if(n<0) break;
        md5_process_bytes(buf, n, &ctx);
        content_length -= n; 
      }
      md5_finish_ctx(&ctx, hash);
      keylength=16; /* Raw key bytes not string chars! */
      break;
    }
    case HASH_SHA1: 
    {
      struct sha_ctx ctx;
      sha_init_ctx(&ctx);
      while(content_length > 0) {
        size= content_length>READ_SIZE?READ_SIZE:content_length;
        n= socket_read(s, buf, size);
        if(n<0) break;
        sha_process_bytes(buf, n, &ctx);
        content_length -= n; 
      }
      sha_finish_ctx(&ctx, hash);
      keylength=20; /* Raw key bytes not string chars! */
      break;
    }
    default:
    DEBUG("HTTP warning: Unknown hash type\n");
    return FALSE;
  }          

  if(strncasecmp(Util_digest2Bytes(hash, keylength, result), checksum, keylength*2) != 0) {
    DEBUG("HTTP warning: Document checksum mismatch\n");
    return FALSE;
  } else {
    DEBUG("HTTP: Succeeded testing document checksum\n");
  }

  return TRUE;

}


static int do_regex(Socket_T s, long content_length, Request_T R) {

  int n;
  int size= 0;
  int rv= TRUE;
  int length= 0;
  char *buf= NULL;
#ifdef HAVE_REGEX_H
  int regex_return;
#else
  char *regex_return;
#endif

  if(R->regex == NULL) {
    return TRUE;
  }

  if(content_length == 0) {
    LogError("HTTP error: Cannot test regex -- No content returned "
      "from server\n");
    return FALSE;
  }
 
  if(content_length < 0) /* Not defined in response */
    content_length= HTTP_CONTENT_MAX;
  else if(content_length > HTTP_CONTENT_MAX)
    content_length= HTTP_CONTENT_MAX;
  
  n= 0;
  size= 0;
  length= content_length;
  buf= xmalloc(content_length + 1);
  
  do {
      n= socket_read(s, &buf[size], length);
      if(n<=0)
        break;
      size+= n;
      length-= n;
  } while(length>0);
  
  if(size==0) {
    rv= FALSE;
    LogError("HTTP: error receiving data -- %s\n", STRERROR);
    goto error;
  }
  buf[size]= 0;

#ifdef HAVE_REGEX_H

      regex_return=regexec(R->regex,
			   buf,
			   0,
			   NULL,
			   0);
      switch(R->operator) {

      case OPERATOR_EQUAL:
	if(regex_return!=0) {
	  rv= FALSE;
	} else {
	  DEBUG("HTTP: Regular expression test succeeded\n");
	  
	}
	break;

      case OPERATOR_NOTEQUAL:
	if(regex_return == 0) {
	  rv= FALSE;
	} else {
	  DEBUG("HTTP: Regular expression test succeeded\n");
	  
	}
	break;

      default:
	LogError("HTTP error: Invalid content operator\n");
      }
	
#else
      /* w/o regex support */

      regex_return= strstr(buf, R->regex);
      switch(R->operator) {

      case OPERATOR_EQUAL:
	if(!regex_return) {
	  rv= FALSE;
	  DEBUG("HTTP: Regular expression does not match\n");
	}
	break;

      case OPERATOR_NOTEQUAL:
	if(regex_return) {
	  rv= FALSE;
	  DEBUG("HTTP: Regular expression match\n");
	}
	break;

      default:
	LogError("HTTP error: Invalid content operator\n");
      }
      
#endif
  
error:
  FREE(buf);
  return rv;
  
}


static char *get_auth_header(Port_T P, char *auth, int l) {
  
  char *b64;
  char buf[STRLEN];
  char *username= NULL;
  char *password= NULL;
  
  if(P->url_request) {
    URL_T U=  P->url_request->url;
    if(U) {
      username= U->user;
      password= U->password;
    }
  }
      
  if(! (username && password)) {
    return auth;
  } 

  snprintf(buf, STRLEN, "%s:%s", username, password);
  if(! (b64= encode_base64(strlen(buf), (unsigned char *)buf)) ) {
    return auth;
  }

  snprintf(auth, l, "Authorization: Basic %s\r\n", b64);
  FREE(b64);
  
  return auth;

}


syntax highlighted by Code2HTML, v. 0.9.1