/* Copyright (C) 2000, 2001  SWsoft, Singapore
 *
 *  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 2 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, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

/*  $Id: sc.cpp,v 1.34 2001/08/21 16:58:18 kir Exp $
    Author : Alexander F. Avdonkin
	Uses parts of UdmSearch code
*/
#include <stdio.h>
#include <unistd.h>
#include <sys/utsname.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/time.h>
#include "sc.h"
#include "defines.h"
#include "datasource.h"
#include "charsets.h"
#include "documents.h"
#include "templates.h"
#include "paths.h"
#include "logger.h"
#include "timer.h"

CLogger logger(L_NONE);

extern char **environ;

int search(char* exe, char* arg) 
{
	char templ[STRSIZ] = "";
	char query_string[8*1024] = ""; // Apache allows up to 8190 bytes
	int empty = 1; // query is empty until "q=" not found
	char sitename[130] = "";
	char *env, *lasttok, *token;
	char* http_host;
	char host[STRSIZ] = "";
	struct timeval stime, etime;
	gettimeofday(&stime, NULL);
	int prps = 0;

	if ((http_host = getenv("HTTP_HOST")))
	{
		snprintf(host, STRSIZ, "http://%s", http_host);
	}

	// Determine template name
	if ((env = getenv("ASPSEEK_TEMPLATE")))
	{
		safe_strcpy(templ, env);
	}
	// some compatibility with UdmSearch
	else if ((env = getenv("UDMSEARCH_TEMPLATE")))
	{
		safe_strcpy(templ, env);
	}
	if ((env = getenv("HTTP_COOKIE")))
	{
		// Parse cookie to find preferences
		token = GetToken(env, "; ", &lasttok);
		while (token)
		{
			if (STRNCMP(token, "PS=") == 0)
			{
				prps = atoi(token + 3);
				if (prps > MAX_PS) prps = MAX_PS;
			}
			token = GetToken(NULL, "; ", &lasttok);
		}
	}
	if ((env = getenv("QUERY_STRING")))
	{
		if (strlen(env) > 8190) // max. length of query, from Apache
			return -1; // possible buffer overflow attack?
		safe_strcpy(query_string, env);
		if ((env = getenv("REDIRECT_STATUS")))
		{

			// Check Apache internal redirect via "AddHandler" and "Action"
//			printf("REDIRECT_STATUS: %s\n", env);

			env = getenv("REDIRECT_URL");
			safe_strcpy(self, env ? env : "s.cgi");
			if(!templ[0])
			{
				env = getenv("PATH_TRANSLATED");
				safe_strcpy(templ, env ? env : "");
			}
		}
	}
	else
	{
		// Executed from command line, or under server
		// which do not pass an empty QUERY_STRING
		if (arg)
		{
			snprintf(query_string, sizeof(query_string), "q=%s", arg);
		}
	}
	
	// Try to determine script name and template name if still not known
	// As for template name, some.cgi should use some.htm
	env = getenv("SCRIPT_NAME");
	if (!self[0])
	{
		snprintf(self, sizeof(self), "%s%s", host, env ? env : "/cgi-bin/s.cgi");
	}
	if (!templ[0])
	{
		if (env){ // we got name of CGI
			char *s;
			// remove .ext
			if ((s = strrchr(env, '.')))
				*s = '\0';
			// remove path
			if (!(s = strrchr(env, '/')))
				s = env;
			snprintf(templ, sizeof(templ), "%s/%s.htm", CONF_DIR, s);
		}
		else{ // last resort
			safe_strcpy(templ, CONF_DIR "/s.htm");
		}
	}
	
	/* Parse Query String */
	empty = cgiQuery.ParseCgiQuery(query_string, templ);
	if (cgiQuery.m_ps == 0)
	{
		cgiQuery.m_ps = prps;
	}

	InitRand();
	InitErrorMes();

	// Load query result template
	if (LoadTemplate(templ, cgiQuery.m_query_words.c_str()))
	{
		printf("Content-Type: text/plain\r\n\r\nCan't open template file %s: %s\n", templ, strerror(errno));
		return 0;
	}
	InitCharset();
	
	// Output Content-Type if run under web server
	// Some servers do not pass QUERY_STRING if the query was empty,
	// so we check REQUEST_METHOD as well to be safe
	if ((getenv("QUERY_STRING") || getenv("REQUEST_METHOD")) && (cgiQuery.m_cached.size() == 0))
	{
		// RFC 2068 states that default charset should be iso-8859-1
#ifdef UNICODE
		printf("Content-Type: text/html; charset=%s\r\n", (cgiQuery.m_charset.size()) ? cgiQuery.m_charset.c_str() : "iso-8859-1");
#else
		printf("Content-Type: text/html; charset=%s\r\n", (LocalCharset.size()) ? LocalCharset.c_str() : "iso-8859-1");
#endif
		if (cgiQuery.m_take == 0)
			printf("\r\n");
	}

	cgiQuery.FormQuery();
	// Print TOP and if no query BOTTOM

	CRemoteDataSource* datasource = NULL;
	if ((cgiQuery.m_cached.size() == 0) && ((cgiQuery.m_take == 0) || empty))
	{
#ifdef UNICODE 
		if (cgiQuery.m_charset.size())
			printf("<META HTTP-EQUIV=\"Content-type\" CONTENT=\"text/html; charset=%s\">\n", cgiQuery.m_charset.c_str());
#else
		if (LocalCharset.size())
			printf("<META HTTP-EQUIV=\"Content-type\" CONTENT=\"text/html; charset=%s\">\n", LocalCharset.c_str());
#endif
 		// Print top template only if not processing "cached" link
		PrintTemplate(*datasource, "top", "", "", "");
		if (empty)
		{
			// Print bottom template if query parameter is not present
			PrintTemplate(*datasource, "bottom", "", "", "");
			exit(0);
		}
	}
	int con;
	if (daemons.size() <= 1)
	{
		// Single daemon
		int address, port;
		if (daemons.size() == 1)
		{
			// Use configuration domain parameters
			CDaemonSet::iterator it = daemons.begin();
			address = it->m_address;
			port = it->m_port;
		}
		else
		{
			// Find out local address
			struct utsname un;
			uname(&un);
			struct hostent* phe = gethostbyname(un.nodename);
			memcpy(&address, *phe->h_addr_list, sizeof(address));
			port = Port;
		}
		// Create datasource and connect
		CTcpDataSource* d = new CTcpDataSource;
		con = d->Connect(address, port);
		datasource = d;
	}
	else
	{
		// Multiple daemons
		CMultiTcpDataSource* d = new CMultiTcpDataSource;
		con = d->Connect(daemons);
		datasource = d;
	}
	if (con == 0)
	{
		if (cgiQuery.m_cached.size())
		{
			// Print "cached" copy of content
			datasource->PrintCached(cgiQuery);
			delete datasource;
			return 0;
		}
		struct timeval tm;
		gettimeofday(&tm, NULL);
		int ps1 = cgiQuery.m_ps ? cgiQuery.m_ps : DEFAULT_PS;
		ULONG counts[5];
		// First, find site count, matching query
		int r = datasource->GetSiteCount(cgiQuery, counts);
		if (r == 0)
		{
			// No error in query occured
			gettimeofday(&etime, NULL);
			if (counts[0])
			{
				// Not empty site set
				if (cgiQuery.m_gr)
				{
					// Turn off grouping if only 1 site is found
					datasource->SetGroup(counts[0]);
				}
				// Receive the rest of query result statistics
				datasource->GetCounts(counts + 1);
				ttime = timedif(etime, stime);
				ULONG nFirst = (cgiQuery.m_np + 1) * ps1;
				// Allocate buffer for results and receive results themselves
				CUrlW* urls = (CUrlW*)alloca(counts[1] * sizeof(CUrlW));
				counts[1] = datasource->GetResults(counts[1], urls, cgiQuery.m_gr, nFirst);
				// Find out words to highlight
				datasource->GetWords(LightWords);
				// Collect words with symbols '*' and '?'
				LightWords.CollectPatterns();
				// Fill string with words info statistics
				LightWords.PrintWordInfo(wordinfo);
				if (counts[0] <= 1)
				{
					cgiQuery.m_gr = 0;
					if (counts[0] == 1)
					{
						// Find out site name, if only 1 site is found
						datasource->GetSite(urls, sitename);
					}
				}
				CUrlW* urle = urls + counts[1];
				// Write query statistics to the database
				datasource->AddStat(cgiQuery.m_query_words.c_str(), cgiQuery.m_ul_unescaped.c_str(), cgiQuery.m_np, ps1,
					(int)counts[3], (int)counts[0], stime, etime, cgiQuery.m_cspaces, cgiQuery.m_site);
				if (cgiQuery.m_take == 0)
				{
					first = ps1 * cgiQuery.m_np + 1;
					docnum = 1;
					last = min(nFirst, counts[2]);
					num_pages = (counts[2] + ps1 - 1) / ps1;
					is_next = last < counts[2];
					found = counts[2];
					// Build URL string for next result pages and for "more from site pages"
					cgiQuery.FormHrefs(href, hrefwg);
					if (counts[4] & 1)
					{
						PrintTemplate(*datasource, "complexPhrase", "", "", "");
					}
					if (counts[4] & 2)
					{
						PrintTemplate(*datasource, "complexExpression", "", "", "");
					}
					// Print results top
					PrintTemplate(*datasource, "restop", sitename, "", "");
					urlw = urls;
					// Print each result
					while (urlw < urle)
					{
						ULONG site = urlw->m_siteID;
						int u = 0;
						// Print each url for site, if grouping by site is on
						for ( ; (urlw < urle) && (site == urlw->m_siteID) && ((u == 0) || cgiQuery.m_gr); urlw++, u++)
						{
							// Skip results belonging to the first pages
							if (docnum >= first)
							{
								CSrcDocumentS doc;
								datasource->GetDocument(urlw, doc);

//								HtmlSpecialChars(doc.m_title.c_str(), title);
								safe_strcpy(title, doc.m_title.c_str());
								if (title[0] == 0)
								{
									safe_strcpy(title, "No title");
								}
								texts.insert(texts.begin(), doc.m_excerpts.begin(), doc.m_excerpts.end());
#ifdef UNICODE
								textH = &doc.m_excerptsH;
#endif
								size = doc.m_size;
								HtmlSpecialChars(doc.m_descr.c_str(), descr);
								HtmlSpecialChars(doc.m_keywords.c_str(), keywords);
								crc = doc.m_crc.c_str();
#ifdef UNICODE
								titleH = &doc.m_titleH;
								descrH = &doc.m_descrH;
								keywordsH = &doc.m_keywordsH;
#endif
								// Print result
								PrintTemplate(*datasource, u == 0 ? "res" : "res2", doc.m_url.c_str(), doc.m_content_type.c_str(), doc.m_last_modified.c_str());
								texts.clear();
							}
						}
						docnum++;
					}
					// Print result bottom
					PrintTemplate(*datasource, "resbot", "", "", "");
				}
				else
				{
					CSrcDocumentS doc;
					datasource->GetDocument(urls, doc);
					printf("Cache-Control: No-Cache\nStatus: 302\nLocation: %s\n\n", doc.m_url.c_str());
					datasource->Quit();
					delete datasource;
					return 0;
				}
			}
			else
			{
				// Add empty result statistics
				datasource->AddStat(cgiQuery.m_query_words.c_str(), cgiQuery.m_ul_unescaped.c_str(), cgiQuery.m_np, ps1, 0, 0, 
					stime, etime, cgiQuery.m_cspaces, cgiQuery.m_site);
				if (cgiQuery.m_take)
				{
					printf("\r\n");
					PrintTemplate(*datasource, "top", "", "", "");
				}
				// Print "not found" template
				PrintTemplate(*datasource, "notfound", "", "", "");
			}
		}
		else
		{
			// Add error statistics
			gettimeofday(&etime, NULL);
			datasource->AddStat(cgiQuery.m_query_words.c_str(), cgiQuery.m_ul_unescaped.c_str(), cgiQuery.m_np, ps1, -1, 0,
				stime, etime, cgiQuery.m_cspaces, cgiQuery.m_site);
			if (cgiQuery.m_take)
			{
				printf("\r\n");
				PrintTemplate(*datasource, "top", "", "", "");
			}
			// Print query error template
			datasource->GetError(error);
			PrintTemplate(*datasource, "queryerror", "", "", "");
		}
		// Finish processing query
		datasource->Quit();
	}
	else // Can't connect to searchd
	{
		safe_strcpy(error, ER_NOSEARCHD);
		// Print error template
		PrintTemplate(*datasource, "error", "", "", "");
	}
	// Print bottom
	PrintTemplate(*datasource, "bottom", "", "", "");
	delete datasource;
	return 0;
}

int main(int argc, char** argv)
{
	return search(argv[0], argc > 0 ? argv[1] : NULL);
}
