/** BEGIN COPYRIGHT BLOCK
 * Copyright (C) 2001 Sun Microsystems, Inc.  Used by permission.
 * Copyright (C) 2005 Red Hat, Inc.
 * 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; version 2
 * of the License.
 *
 * 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, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 * END COPYRIGHT BLOCK **/
/*
 * httpcon.c - Make HTTP requests, do proper buffering of HTTP requests,
 *             etc.
 *
 * All blame to Mike McCool
 */

#include "libadmin/libadmin.h"
#include "libadmsslutil/admsslutil.h"
#include "libadmin/cluster.h"
/* Removed for new ns security
#include "sec.h"
*/
#include <key.h>
#include <cert.h>
#include <secmod.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
/* Stuff for making a new HTTP connection.  (Cross platform?) */
#include <sys/types.h>
#ifndef XP_WIN32
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#endif 

#include "nspr.h"
#include "private/pprio.h"
#include "ssl.h"

#include "libadmin/libadmin.h"

extern int XP_ERRNO_EWOULDBLOCK;

/* Hack here for ns security integration, do not know why SEC_Init is
   removed from public header  */
#if 0
extern void SEC_Init(void);
#endif

/* #define DEBUG_TRACE 1 */
#undef DEBUG_TRACE

#ifdef DEBUG_TRACE
FILE *dbf = NULL;
int numconns = 0;
char *dbd = "/tmp/http_trace.%d";
char dbp[256];
#endif

#define HBUF_SIZE 256
#define NBUF_SIZE 1024
#define SERVSSL_ERROR_LEN 1024

#define ADMIN_ID "Netscape-Administrator"
#define HEAD_REQ "HEAD / HTTP/1.0\n\n"

/* Wrappers so we can do SSL stuffs */
#ifdef XP_UNIX
#define sys_socket socket
#define sys_connect connect
#define sys_read read
#define sys_write write
#else /* XP_WIN32 */
#define sys_socket socket
#define sys_connect connect
#define sys_read(fd, line, linesize) recv(fd, line, linesize, 0)
#define sys_write(fd, line, linesize) send(fd, line, linesize, 0)
#endif /* XP_WIN32 */

/* Buffer grunt work */
NSAPI_PUBLIC bufstruct *
new_buffer(int bufsize)
{
    bufstruct *ans = (bufstruct *) MALLOC(sizeof(bufstruct) + 2);
    char *buffer = (char *) MALLOC(bufsize+2);
 
    ans->buf = buffer;
    ans->bufsize = bufsize;
    ans->curpos = 0;
    ans->inbuf = 0;
 
    ans->hbuf = (char *) MALLOC(HBUF_SIZE+2);
    ans->hbufsize = HBUF_SIZE;
    ans->hbufpos = 0;

    return ans;
}
 
NSAPI_PUBLIC void
flush_buffer(bufstruct *buf)
{
    buf->buf[0] = '\0';
    buf->curpos = 0;
    buf->inbuf = 0;

    buf->hbuf[0] = '\0';
    buf->hbufpos = 0;
}
 
NSAPI_PUBLIC void
delete_buffer(bufstruct *buf)
{
    FREE(buf->buf);
    FREE(buf->hbuf);
    FREE(buf);
}
 
char
_consume(PRFileDesc *sock, char *buf, int bufsize, int *curpos, int *inbuf)
{
    int retcode;
    int numread = 0;
    char ans;
 
    while (*inbuf == 0)  {
      
        numread = PR_Read(sock, buf, bufsize);
        if(numread < 0)  {
	   retcode = PR_GetOSError();
	   if ((retcode == PR_WOULD_BLOCK_ERROR) || (retcode == EAGAIN) || (retcode == 0)) {
	      continue;
	   } else {
	      return '\0';
	      /*
              report_error(SYSTEM_ERROR, "Read failed", "Network read failed.");
	       */
	   }
        }  else
        if(numread == 0)  {
            return '\0';
        }  else
        if(numread < bufsize)  {
            buf[numread] = '\0';
        }
        *inbuf = numread;
        *curpos = 0;
    }
 
    ans = buf[*curpos];
    (*curpos)++;
    (*inbuf)--;
 
    return ans;
}
 
/* stdio replacement for a network connection (so shoot me) */
NSAPI_PUBLIC char *
get_line_from_fd(PRFileDesc * fd, bufstruct *buf)
{
    char ch;
 
    /* Clear the header buffer before getting the next line. */
    buf->hbufpos=0;
    buf->hbuf[buf->hbufpos] = '\0';
    
    while( (ch = _consume(fd, buf->buf, buf->bufsize,
                                  &(buf->curpos), &(buf->inbuf))) )  {
	if (ch == CR) continue;
        buf->hbuf[buf->hbufpos] = ch;
        ++buf->hbufpos;
        if(buf->hbufpos >= buf->hbufsize)  {
            buf->hbufsize+=HBUF_SIZE;
            buf->hbuf = (char *) REALLOC(buf->hbuf, buf->hbufsize+2);
        }
        buf->hbuf[buf->hbufpos] = '\0';
 
 
        if(ch == LF)  {
            break;
        }
    }
    if(!ch) return NULL;
#ifdef DEBUG_TRACE
    fprintf(dbf, "R:%s", buf->hbuf);
#endif
    return buf->hbuf;
}
 

/* send data to a fd */
NSAPI_PUBLIC int
send_line_to_fd(PRFileDesc * fd, char *line, int linesize)
{
#ifdef DEBUG_TRACE
    fprintf(dbf, "W:%s", line);
#endif
   return(PR_Write(fd, line, linesize));
}


/* Decompose a URL into protocol, server, port, and URI */
NSAPI_PUBLIC int
decompose_url(char *url, 	/* IN 	format: http[s]://server:port/uri */
	     char **protocol,	/* OUT  http or https or ftp or ... */
	     char **server,	/* OUT  nodename in url */
	     uint *port,	/* OUT	port number in url */
	     char **uri)	/* OUT	uri port in url */
{
    char *work = STRDUP(url);
    char *tmp;
    char *s_port = NULL;

    /* munge the URL */
    *protocol = work;
    tmp = strchr(work, ':');
    *tmp++ = '\0';

    /* 'tmp' should now be: //server:port/uri */
    if((*tmp++ != '/') || (*tmp++ != '/')) {
	/*
	 * suggest library routine to return status instead of aborting...
	 *
        report_error(INCORRECT_USAGE, url, "Decompose URL got non-URL.");
	 */
	return(-1);
    }

    *server = tmp;

    while((*tmp != ':') && (*tmp != '/')) 
        tmp++;
    if(*tmp == ':')  {
        char save;
        *tmp++ = '\0';

        s_port = tmp;
        while(isdigit(*tmp))
            tmp++;
        save = *tmp;
        *tmp = '\0';
        *port = atoi(s_port);
        *tmp = save;
    }  else  {
        if(!strcmp(*protocol, "https")) 
            *port=443;
        else
            *port=80;
    }
    if(*tmp == '\0')
        *uri = "/";
    else
        *uri = tmp;

   return(0);
}

NSAPI_PUBLIC int
is_end_of_headers(char *hline)
{
    return ((hline[0] == LF) || ((hline[0] == CR) && (hline[1] == LF)));
}

NSAPI_PUBLIC int
parse_status_line(char *statusline)
{
    char *hline = STRDUP(statusline);
    char *t1 = strchr(hline, ' ');
    char *t2;
    int ans = 0;
    if(t1)  {
        t1++;
        t2 = strchr(t1, ' ');
        if(t2)  {
            *t2 = '\0';
            ans = atoi(t1);
        }
    }
    FREE(hline);
    return ans;
}

/* Simple status report stuff */
void
_report_status(char *status)
{
    fprintf(stdout, "<i>%s</i><br>\n", status);
    fflush(stdout);
}

NSAPI_PUBLIC PRFileDesc*
make_http_request(char *protocol, char *server, unsigned int port, char *request, int timeout, int *errcode)
{
    PRFileDesc *req_socket= NULL;
    int retcode;
    PRNetAddr netAddr;
    /* removed for new ns security integration
    struct sockaddr_in *sin = (struct sockaddr_in *)
                              MALLOC(sizeof(struct sockaddr_in));
			      */
    PRHostEnt 	*hstruct; 
    PRHostEnt   hent;
    char        buf[PR_NETDB_BUF_SIZE];
    PRStatus    err = PR_FAILURE;
    const char *configdir = util_get_conf_dir();
    const char *secdir = util_get_security_dir();

    /* removed for new ns security integration
    memset((char *) sin, 0, sizeof(*sin));
    */
 
    if(!isdigit(server[0]))  {
        err = PR_GetHostByName(server,
				  buf,
				  PR_NETDB_BUF_SIZE,
				  &hent);
    }

    if (err == PR_FAILURE) {
      *errcode = ERR_FAIL_DNSLOOKUP;
      return NULL;
    }

    hstruct = &hent;

    PR_InitializeNetAddr(PR_IpAddrNull, (PRUint16)port, &netAddr);
    netAddr.inet.ip = *((PRUint32*)hstruct->h_addr_list[0]);

    /* removed for new ns security integration
    sin->sin_addr = *((struct in_addr *)hstruct->h_addr_list[0]);
    */

    req_socket = PR_NewTCPSocket();

    if(req_socket == NULL) {
	*errcode = ERR_FAIL_SSLSOCKET;
	return NULL;
    }
    /* 
    sin->sin_family = AF_INET;
    sin->sin_port = htons((unsigned short)port);
    */

    if (!strcmp(protocol, "https")) {
	
	req_socket = SSLSocket_init(req_socket, configdir, secdir);
	if (req_socket == NULL) {
	    *errcode = PR_GetError();
            report_error(SYSTEM_ERROR, "SEC Init Failed", 
			 "unable to initialize a secure socket");
	}
    }

    retcode = PR_Connect(req_socket, &netAddr, PR_SecondsToInterval(timeout));

    if (retcode != 0) {
       PR_Close(req_socket);
       *errcode = PR_GetError();
       return NULL;
    }
 
#ifdef DEBUG_TRACE
    sprintf(dbp, dbd, numconns++);
    dbf = fopen(dbp, "w");
    fprintf(dbf, "W:%s", request);
#endif

    retcode = PR_Write(req_socket, request, strlen(request));
    if (retcode == -1) {
       PR_Close(req_socket);
       *errcode = PR_GetError();
       return NULL;
    }

    return req_socket;
}

NSAPI_PUBLIC void
end_http_request(PRFileDesc *req_socket)
{
    PR_Close(req_socket);
#ifdef DEBUG_TRACE
    fclose(dbf);
    dbf = NULL;
#endif
}
