
#include "webcit.h"
#include "webserver.h"

int is_uds = 0;
char serv_sock_name[PATH_MAX] = "";

HashList *EmbeddableMimes = NULL;
StrBuf *EmbeddableMimeStrs = NULL;


void SetInlinMimeRenderers(void)
{
	StrBuf *Buf;

	Buf = NewStrBuf();

	/* Tell the server what kind of richtext we prefer */
	serv_putbuf(EmbeddableMimeStrs);
	StrBuf_ServGetln(Buf);

	FreeStrBuf(&Buf);
}


void DeleteServInfo(ServInfo **FreeMe)
{
	if (*FreeMe == NULL)
		return;
	FreeStrBuf(&(*FreeMe)->serv_nodename);
	FreeStrBuf(&(*FreeMe)->serv_humannode);
	FreeStrBuf(&(*FreeMe)->serv_fqdn);
	FreeStrBuf(&(*FreeMe)->serv_software);
	FreeStrBuf(&(*FreeMe)->serv_bbs_city);
	FreeStrBuf(&(*FreeMe)->serv_sysadm);
	FreeStrBuf(&(*FreeMe)->serv_default_cal_zone);
	FreeStrBuf(&(*FreeMe)->serv_svn_revision);
	free(*FreeMe);
	*FreeMe = NULL;
}

/*
 * get info about the server we've connected to
 *
 * browser_host		the citadel we want to connect to
 * user_agent		which browser uses our client?
 */
ServInfo *get_serv_info(StrBuf *browser_host, StrBuf *user_agent)
{
	ServInfo *info;
	StrBuf *Buf;
	int a;
	int rc;

	Buf = NewStrBuf();

	/* Tell the server what kind of client is connecting */
	serv_printf("IDEN %d|%d|%d|%s|%s",
		    DEVELOPER_ID,
		    CLIENT_ID,
		    CLIENT_VERSION,
		    ChrPtr(user_agent),
		    ChrPtr(browser_host)
	);
	StrBuf_ServGetln(Buf);
	if (GetServerStatus(Buf, NULL) != 2) {
		syslog(0, "get_serv_info(IDEN): unexpected answer [%s]\n",
			ChrPtr(Buf));
		FreeStrBuf(&Buf);
		return NULL;
	}

	/*
	 * Tell the server that when we save a calendar event, we
	 * want invitations to be generated by the Citadel server
	 * instead of by the client.
	 */
	serv_puts("ICAL sgi|1");
	StrBuf_ServGetln(Buf);
	if (GetServerStatus(Buf, NULL) != 2) {
		syslog(0, "get_serv_info(ICAL sgi|1): unexpected answer [%s]\n",
			ChrPtr(Buf));
		FreeStrBuf(&Buf);
		return NULL;
	}

	/* Now ask the server to tell us a little bit about itself... */
	serv_puts("INFO");
	StrBuf_ServGetln(Buf);
	if (GetServerStatus(Buf, NULL) != 1) {
		syslog(0, "get_serv_info(INFO sgi|1): unexpected answer [%s]\n",
			ChrPtr(Buf));
		FreeStrBuf(&Buf);
		return NULL;
	}

	info = (ServInfo*)malloc(sizeof(ServInfo));
	memset(info, 0, sizeof(ServInfo));
	a = 0;
	while (rc = StrBuf_ServGetln(Buf),
	       (rc >= 0) &&
	       ((rc != 3) || 
		strcmp(ChrPtr(Buf), "000")))
	{
		switch (a) {
		case 0:
			info->serv_pid = StrToi(Buf);
			WC->ctdl_pid = info->serv_pid;
			break;
		case 1:
			info->serv_nodename = NewStrBufDup(Buf);
			break;
		case 2:
			info->serv_humannode = NewStrBufDup(Buf);
			break;
		case 3:
			info->serv_fqdn = NewStrBufDup(Buf);
			break;
		case 4:
			info->serv_software = NewStrBufDup(Buf);
			break;
		case 5:
			info->serv_rev_level = StrToi(Buf);
			break;
		case 6:
			info->serv_bbs_city = NewStrBufDup(Buf);
			break;
		case 7:
			info->serv_sysadm = NewStrBufDup(Buf);
			break;
		case 14:
			info->serv_supports_ldap = StrToi(Buf);
			break;
		case 15:
			info->serv_newuser_disabled = StrToi(Buf);
			break;
		case 16:
			info->serv_default_cal_zone = NewStrBufDup(Buf);
			break;
		case 20:
			info->serv_supports_sieve = StrToi(Buf);
			break;
		case 21:
			info->serv_fulltext_enabled = StrToi(Buf);
			break;
		case 22:
			info->serv_svn_revision = NewStrBufDup(Buf);
			break;
		case 23:
			info->serv_supports_openid = StrToi(Buf);
			break;
		case 24:
			info->serv_supports_guest = StrToi(Buf);
			break;
		}
		++a;
	}
	FreeStrBuf(&Buf);
	return info;
}

int GetConnected (void)
{
	StrBuf *Buf;
	wcsession *WCC = WC;

	if (WCC->ReadBuf == NULL)
		WCC->ReadBuf = NewStrBufPlain(NULL, SIZ * 4);
	if (is_uds) /* unix domain socket */
		WCC->serv_sock = uds_connectsock(serv_sock_name);
	else        /* tcp socket */
		WCC->serv_sock = tcp_connectsock(ctdlhost, ctdlport);
	
	if (WCC->serv_sock < 0) {
		WCC->connected = 0;
		FreeStrBuf(&WCC->ReadBuf);
		return 1;
	}
	else {
		long Status;
		int short_status;
		Buf = NewStrBuf();
		WCC->connected = 1;
		StrBuf_ServGetln(Buf);	/* get the server greeting */
		short_status = GetServerStatus(Buf, &Status);
		FreeStrBuf(&Buf);

		/* Server isn't ready for us? */
		if (short_status != 2) {
			if (Status == 571) {
				hprintf("HTTP/1.1 503 Service Unavailable\r\n");
				hprintf("Content-type: text/plain; charset=utf-8\r\n");
				wc_printf(_("This server is already serving its maximum number of users and cannot accept any additional logins at this time.  Please try again later or contact your system administrator."));
			}
			else {
				wc_printf("%ld %s\n",
					Status,
					_("Received unexpected answer from Citadel server; bailing out.")
				);
				hprintf("HTTP/1.1 502 Bad Gateway\r\n");
				hprintf("Content-type: text/plain; charset=utf-8\r\n");
			}
			end_burst();
			end_webcit_session();
			return 1;
		}

		/*
		 * From what host is our user connecting?  Go with
		 * the host at the other end of the HTTP socket,
		 * unless we are following X-Forwarded-For: headers
		 * and such a header has already turned up something.
		 */
		if ( (!follow_xff) || (StrLength(WCC->Hdr->HR.browser_host) == 0) ) {
			if (WCC->Hdr->HR.browser_host == NULL) {
				WCC->Hdr->HR.browser_host = NewStrBuf();
				Put(WCC->Hdr->HTTPHeaders, HKEY("FreeMeWithTheOtherHeaders"), 
				    WCC->Hdr->HR.browser_host, HFreeStrBuf);
			}
			locate_host(WCC->Hdr->HR.browser_host, WCC->Hdr->http_sock);
		}
		if (WCC->serv_info == NULL) {
			WCC->serv_info = get_serv_info(WCC->Hdr->HR.browser_host, WCC->Hdr->HR.user_agent);
		}
		if (WCC->serv_info == NULL){
			begin_burst();
			wc_printf(_("Received unexpected answer from Citadel server; bailing out."));
			hprintf("HTTP/1.1 502 Bad Gateway\r\n");
			hprintf("Content-type: text/plain; charset=utf-8\r\n");
			end_burst();
			end_webcit_session();
			return 1;
		}
		if (WCC->serv_info->serv_rev_level < MINIMUM_CIT_VERSION) {
			begin_burst();
			wc_printf(_("You are connected to a Citadel "
				  "server running Citadel %d.%02d. \n"
				  "In order to run this version of WebCit "
				  "you must also have Citadel %d.%02d or"
				  " newer.\n\n\n"),
				WCC->serv_info->serv_rev_level / 100,
				WCC->serv_info->serv_rev_level % 100,
				MINIMUM_CIT_VERSION / 100,
				MINIMUM_CIT_VERSION % 100
				);
			hprintf("HTTP/1.1 200 OK\r\n");
			hprintf("Content-type: text/plain; charset=utf-8\r\n");
			end_burst();
			end_webcit_session();
			return 1;
		}
		SetInlinMimeRenderers();
	}
	return 0;
}


void FmOut(StrBuf *Target, const char *align, const StrBuf *Source)
{
	const char *ptr, *pte;
	const char *BufPtr = NULL;
	StrBuf *Line = NewStrBufPlain(NULL, SIZ);
	StrBuf *Line1 = NewStrBufPlain(NULL, SIZ);
	StrBuf *Line2 = NewStrBufPlain(NULL, SIZ);
	int bn = 0;
	int bq = 0;
	int i;
	long len;
	int intext = 0;

	StrBufAppendPrintf(Target, "<div class=\"fmout-%s\">\n", align);

	if (StrLength(Source) > 0) 
		do 
		{
			StrBufSipLine(Line, Source, &BufPtr);
			bq = 0;
			i = 0;
			ptr = ChrPtr(Line);
			len = StrLength(Line);
			pte = ptr + len;

			if ((intext == 1) && (isspace(*ptr))) {
				StrBufAppendBufPlain(Target, HKEY("<br>"), 0);
			}
			intext = 1;
			if (isspace(*ptr)) while ((ptr < pte) &&
						  ((*ptr == '>') ||
						   isspace(*ptr)))
					   {
						   if (*ptr == '>')
							   bq++;
						   ptr ++;
						   i++;
					   }

			/*
			 * Quoted text should be displayed in italics and in a
			 * different colour.  This code understands Citadel-style
			 * " >" quotes and will convert to <BLOCKQUOTE> tags.
			 */
			if (i > 0) StrBufCutLeft(Line, i);
		

			for (i = bn; i < bq; i++)				
				StrBufAppendBufPlain(Target, HKEY("<blockquote>"), 0);
			for (i = bq; i < bn; i++)				
				StrBufAppendBufPlain(Target, HKEY("</blockquote>"), 0);
			bn = bq;

			if (StrLength(Line) == 0)
				continue;

			/* Activate embedded URL's */
			UrlizeText(Line1, Line, Line2);

			StrEscAppend(Target, Line1, NULL, 0, 0);

			StrBufAppendBufPlain(Target, HKEY("\n"), 0);
		}
		while ((BufPtr != StrBufNOTNULL) &&
		       (BufPtr != NULL));

	for (i = 0; i < bn; i++) {
		StrBufAppendBufPlain(Target, HKEY("</blockquote>"), 0);
	}
	StrBufAppendBufPlain(Target, HKEY("</div><br>\n"), 0);
	FreeStrBuf(&Line);
	FreeStrBuf(&Line1);
	FreeStrBuf(&Line2);
}



/*
 *  Transmit message text (in memory) to the server.
 */
void text_to_server(char *ptr)
{
	char buf[256];
	int ch, a, pos, len;

	pos = 0;
	buf[0] = 0;

	while (ptr[pos] != 0) {
		ch = ptr[pos++];
		if (ch == 10) {
			len = strlen(buf);
			while ( (isspace(buf[len - 1]))
				&& (buf[0] !=  '\0') 
				&& (buf[1] !=  '\0') )
				buf[--len] = 0;
			serv_puts(buf);
			buf[0] = 0;
			if (ptr[pos] != 0) strcat(buf, " ");
		} else {
			a = strlen(buf);
			buf[a + 1] = 0;
			buf[a] = ch;
			if ((ch == 32) && (strlen(buf) > 200)) {
				buf[a] = 0;
				serv_puts(buf);
				buf[0] = 0;
			}
			if (strlen(buf) > 250) {
				serv_puts(buf);
				buf[0] = 0;
			}
		}
	}
	serv_puts(buf);
}


/*
 * Transmit message text (in memory) to the server, converting to Quoted-Printable encoding as we go.
 */
void text_to_server_qp(char *ptr)
{
	unsigned char ch, buf[256];
	int pos;
	int output_len = 0;

	pos = 0;
	buf[0] = 0;
	output_len = 0;

	while (ptr[pos] != 0) {
		ch = (unsigned char)(ptr[pos++]);

		if (ch == 13) {
			/* ignore carriage returns */
		}
		else if (ch == 10) {
			/* hard line break */
			if (output_len > 0) {
				if (isspace(buf[output_len-1])) {
					sprintf((char *)&buf[output_len-1], "=%02X", buf[output_len-1]);
					output_len += 2;
				}
			}
			buf[output_len++] = 0;
			serv_puts((char *)buf);
			output_len = 0;
		}
		else if (ch == 9) {
			buf[output_len++] = ch;
		}
		else if ( (ch >= 32) && (ch <= 60) ) {
			buf[output_len++] = ch;
		}
		else if ( (ch >= 62) && (ch <= 126) ) {
			buf[output_len++] = ch;
		}
		else {
			sprintf((char *)&buf[output_len], "=%02X", ch);
			output_len += 3;
		}
		
		if (output_len > 72) {
			/* soft line break */
			if (isspace(buf[output_len-1])) {
				sprintf((char *)&buf[output_len-1], "=%02X", buf[output_len-1]);
				output_len += 2;
			}
			buf[output_len++] = '=';
			buf[output_len++] = 0;
			serv_puts((char *)buf);
			output_len = 0;
		}
	}

	/* end of data - transmit anything that's left */
	if (output_len > 0) {
		if (isspace(buf[output_len-1])) {
			sprintf((char *)&buf[output_len-1], "=%02X", buf[output_len-1]);
			output_len += 2;
		}
		buf[output_len++] = 0;
		serv_puts((char *)buf);
		output_len = 0;
	}
}




/*
 * translate server message output to text (used for editing room info files and such)
 */
void server_to_text()
{
	char buf[SIZ];

	int count = 0;

	while (serv_getln(buf, sizeof buf), strcmp(buf, "000")) {
		if ((buf[0] == 32) && (count > 0)) {
			wc_printf("\n");
		}
		wc_printf("%s", buf);
		++count;
	}
}




/*
 * Read text from server, appending to a string buffer until the
 * usual 000 terminator is found.  Caller is responsible for freeing
 * the returned pointer.
 */
int read_server_text(StrBuf *Buf, long *nLines)
{
	wcsession *WCC = WC;
	StrBuf *ReadBuf;
	long nRead;
	long nTotal = 0;
	long nlines;
	
	nlines = 0;
	ReadBuf = NewStrBuf();
	while ((WCC->serv_sock!=-1) &&
	       (nRead = StrBuf_ServGetln(ReadBuf), (nRead >= 0) &&
		((nRead != 3)||(strcmp(ChrPtr(ReadBuf), "000") != 0))))
	{
		StrBufAppendBuf(Buf, ReadBuf, 0);
		StrBufAppendBufPlain(Buf, HKEY("\n"), 0);
		nTotal += nRead;
		nlines ++;
	}
	FreeStrBuf(&ReadBuf);
	*nLines = nlines;
	return nTotal;
}


int GetServerStatusMsg(StrBuf *Line, long* FullState, int PutImportantMessage, int MajorOK)
{
	int rc;
	if (FullState != NULL)
		*FullState = StrTol(Line);
	rc = ChrPtr(Line)[0] - 48;
	if ((!PutImportantMessage) || 
	    (MajorOK == rc)||
	    (StrLength(Line) <= 4))
		return rc;

	AppendImportantMessage(ChrPtr(Line) + 4, StrLength(Line) - 4);
	return rc;
}


void tmplput_serv_ip(StrBuf *Target, WCTemplputParams *TP)
{
	StrBufAppendPrintf(Target, "%d", WC->ctdl_pid);
}

void tmplput_serv_admin(StrBuf *Target, WCTemplputParams *TP)
{
	wcsession *WCC = WC;
	if (WCC->serv_info == NULL)
		return;
	StrBufAppendTemplate(Target, TP, WCC->serv_info->serv_sysadm, 0);
}

void tmplput_serv_nodename(StrBuf *Target, WCTemplputParams *TP)
{
	wcsession *WCC = WC;
	if (WCC->serv_info == NULL)
		return;
	StrBufAppendTemplate(Target, TP, WCC->serv_info->serv_nodename, 0);
}

void tmplput_serv_humannode(StrBuf *Target, WCTemplputParams *TP)
{
	wcsession *WCC = WC;
	if (WCC->serv_info == NULL)
		return;
	StrBufAppendTemplate(Target, TP, WCC->serv_info->serv_humannode, 0);
}

void tmplput_serv_fqdn(StrBuf *Target, WCTemplputParams *TP)
{
	wcsession *WCC = WC;
	if (WCC->serv_info == NULL)
		return;
	StrBufAppendTemplate(Target, TP, WCC->serv_info->serv_fqdn, 0);
}

void tmplput_serv_software(StrBuf *Target, WCTemplputParams *TP)
{
	wcsession *WCC = WC;
	if (WCC->serv_info == NULL)
		return;
	StrBufAppendTemplate(Target, TP, WCC->serv_info->serv_software, 0);
}

void tmplput_serv_rev_level(StrBuf *Target, WCTemplputParams *TP)
{
	wcsession *WCC = WC;
	if (WCC->serv_info == NULL)
		return;
	StrBufAppendPrintf(Target, "%d.%02d",
			    WCC->serv_info->serv_rev_level / 100,
			    WCC->serv_info->serv_rev_level % 100);
}
int conditional_serv_newuser_disabled(StrBuf *Target, WCTemplputParams *TP)
{
	wcsession *WCC = WC;
	if (WCC->serv_info == NULL)
		return 0;
	return WCC->serv_info->serv_newuser_disabled != 0;
}

int conditional_serv_supports_guest(StrBuf *Target, WCTemplputParams *TP)                                                                                                                                       
{
	wcsession *WCC = WC;
        if (WCC->serv_info == NULL)
		return 0;
        return WCC->serv_info->serv_supports_guest != 0;
}

int conditional_serv_supports_openid(StrBuf *Target, WCTemplputParams *TP)
{
	wcsession *WCC = WC;
	if (WCC->serv_info == NULL)
		return 0;
	return WCC->serv_info->serv_supports_openid != 0;
}

int conditional_serv_fulltext_enabled(StrBuf *Target, WCTemplputParams *TP)
{
	wcsession *WCC = WC;
	if (WCC->serv_info == NULL)
		return 0;
	return WCC->serv_info->serv_fulltext_enabled != 0;
}

int conditional_serv_ldap_enabled(StrBuf *Target, WCTemplputParams *TP)
{
	wcsession *WCC = WC;
	if (WCC->serv_info == NULL)
		return 0;
	return WCC->serv_info->serv_supports_ldap != 0;
}

void tmplput_serv_bbs_city(StrBuf *Target, WCTemplputParams *TP)
{
	wcsession *WCC = WC;
	if (WCC->serv_info == NULL)
		return;
	StrBufAppendTemplate(Target, TP, WC->serv_info->serv_bbs_city, 0);
}

void tmplput_mesg(StrBuf *Target, WCTemplputParams *TP)
{
	int n = 0;
	int Done = 0;
	StrBuf *Line;
	StrBuf *Buf;

	Buf = NewStrBuf();
	Line = NewStrBuf();
	serv_printf("MESG %s", TP->Tokens->Params[0]->Start);

	StrBuf_ServGetln(Line);
	if (GetServerStatus(Line, NULL) == 1) {
		while (!Done &&  (StrBuf_ServGetln(Line)>=0)) {
			if ( (StrLength(Line)==3) && 
			     !strcmp(ChrPtr(Line), "000")) 
				Done = 1;
			else
			{
				if (n > 0)
					StrBufAppendBufPlain(Buf, "\n", 1, 0);
				StrBufAppendBuf(Buf, Line, 0);
			}
			n++;
		}
	
		FlushStrBuf(Line);
		FmOut(Line, "center", Buf);
		StrBufAppendTemplate(Target, TP, Line, 1);
	}
	FreeStrBuf(&Buf);
	FreeStrBuf(&Line);
}


void RegisterEmbeddableMimeType(const char *MimeType, long MTLen, int Priority)
{
	StrBuf *MT;
	MT = NewStrBufPlain(MimeType, MTLen);
	Put(EmbeddableMimes, IKEY(Priority), MT, HFreeStrBuf);
}

void CreateMimeStr(void)
{
	HashPos  *it;
	void *vMime;
	long len = 0;
	const char *Key;

	it = GetNewHashPos(EmbeddableMimes, 0);
	while (GetNextHashPos(EmbeddableMimes, it, &len, &Key, &vMime) &&
               (vMime != NULL)) {
		if (StrLength(EmbeddableMimeStrs) > 0)
			StrBufAppendBufPlain(EmbeddableMimeStrs, HKEY("|"), 0);
		else 
			StrBufAppendBufPlain(EmbeddableMimeStrs, HKEY("MSGP "), 0);
		StrBufAppendBuf(EmbeddableMimeStrs, (StrBuf*) vMime, 0);
	}
	DeleteHashPos(&it);
}

void
ServerStartModule_SERV_FUNC
(void)
{
	EmbeddableMimes = NewHash(1, Flathash);
	EmbeddableMimeStrs = NewStrBuf();
}


void
ServerShutdownModule_SERV_FUNC
(void)
{
	FreeStrBuf(&EmbeddableMimeStrs);
	DeleteHash(&EmbeddableMimes);
}

void 
InitModule_SERVFUNC
(void)
{
	is_uds = strcasecmp(ctdlhost, "uds") == 0;
	if (is_uds)
		snprintf(serv_sock_name, PATH_MAX, "%s/citadel.socket", ctdlport);

	RegisterConditional(HKEY("COND:SERV:OPENID"), 2, conditional_serv_supports_openid, CTX_NONE);
	RegisterConditional(HKEY("COND:SERV:NEWU"), 2, conditional_serv_newuser_disabled, CTX_NONE);
	RegisterConditional(HKEY("COND:SERV:FULLTEXT_ENABLED"), 2, conditional_serv_fulltext_enabled, CTX_NONE);
	RegisterConditional(HKEY("COND:SERV:LDAP_ENABLED"), 2, conditional_serv_ldap_enabled, CTX_NONE);
        RegisterConditional(HKEY("COND:SERV:SUPPORTS_GUEST"), 2, conditional_serv_supports_guest, CTX_NONE);
	RegisterNamespace("SERV:PID", 0, 0, tmplput_serv_ip, NULL, CTX_NONE);
	RegisterNamespace("SERV:NODENAME", 0, 1, tmplput_serv_nodename, NULL, CTX_NONE);
	RegisterNamespace("SERV:HUMANNODE", 0, 1, tmplput_serv_humannode, NULL, CTX_NONE);
	RegisterNamespace("SERV:FQDN", 0, 1, tmplput_serv_fqdn, NULL, CTX_NONE);
	RegisterNamespace("SERV:SOFTWARE", 0, 1, tmplput_serv_software, NULL, CTX_NONE);
	RegisterNamespace("SERV:REV_LEVEL", 0, 0, tmplput_serv_rev_level, NULL, CTX_NONE);
	RegisterNamespace("SERV:BBS_CITY", 0, 1, tmplput_serv_bbs_city, NULL, CTX_NONE);
	RegisterNamespace("SERV:MESG", 1, 2, tmplput_mesg, NULL, CTX_NONE);
	RegisterNamespace("SERV:ADMIN", 0, 1, tmplput_serv_admin, NULL, CTX_NONE);
}



void 
SessionDestroyModule_SERVFUNC
(wcsession *sess)
{
	DeleteServInfo(&sess->serv_info);
}
