#include "gb-include.h"

#include "Proxy.h"
#include "Statsdb.h"
#include "Msg13.h"
#include "XmlDoc.h"
//#include "seo.h" // g_secret_tran_key and api_key


char *g_secret_tran_key = NULL;
char *g_secret_api_key  = NULL;


Proxy g_proxy;


// turn this off now
//bool g_isYippy = true;
bool g_isYippy = false;

#define MINCHARGE 5.00


static void gotTcpReplyWrapper ( void *state , TcpSocket *s ) ;

static void proxyHandlerWrapper ( TcpSocket *s );
//static void gotReplyWrapperPage( void *state, TcpSocket *s );
static void gotHttpReplyWrapper ( void *state, UdpSlot *slot ) ;
//static void gotDataFeedRequestWrapper( void *state );
static void uncountStripe ( class StateControl *stC ) ;

static bool sendPageAccount ( class TcpSocket *s , class HttpRequest *r ) ;

struct StateControl{
	int32_t m_userId32;
	float m_price;
	int32_t m_accessType;

	int32_t m_pageNum;
	bool m_isYippySearch;
	int64_t m_start;
	int32_t m_reqNum;
	SafeBuf m_sb;
	TcpSocket *m_s;
	int64_t m_startTime;
	bool m_isQuery;
	uint32_t m_hash;
	int32_t m_hostId;
	int32_t m_raw;
	int32_t m_stripe        ;
	int32_t m_numQueryTerms ;
	// hash32() of "code"
	int32_t m_ch;
	UdpSlot *m_slot;
	char    *m_slotReadBuf;
	int32_t     m_slotReadBufMaxSize;
	//int32_t     m_forward;
	int32_t     m_retries;
	int64_t     m_timeout;
	HttpRequest m_hr;
	Host *m_forwardHost;
	float m_pending;
	bool m_isEventGuru;
};

#define UIF_ADMIN   0x01
#define UIF_OLDUSER 0x02

// PageRoot.cpp's pageaddurl now uses userinfo...
class UserInfo {
public:
	// unique userid
	int32_t m_userId32; 
	// outstanding requests costs this much
	float m_pending;
	int32_t m_signUpDate;
	// strings include terminating \0
	char m_login[32];
	char m_password[32];
	char m_xmlFeedCode[16];
	char m_creditCardNum[64]; // no punct, just digits
	char m_cvv[5]; // "1234\0"
	char m_creditCardExpires[6]; // "05/13\0"
	char m_creditCardType[32]; // visa

	// new stuff
	char m_email[80];
	char m_phone[30];

	// european stuff for credit card processing
	char m_firstName[40];
	char m_lastName[40];
	char m_address[80];
	char m_city[30];
	char m_state[30];
	char m_country[30];
	char m_zip[20];

	// session info:
	int64_t m_lastSessionId64;
	int32_t m_lastActionTime;
	int32_t m_lastLoginIP;
	float m_accountBalance;

	int32_t m_flags;
};

// values for SummaryRec::m_accessType
#define AT_SEARCHFEED_OLD       1
#define AT_SEARCHFEED_NEW       2
#define AT_COMPETITOR_BACKLINKS 3
#define AT_RELATED_QUERIES      4
#define AT_MISSING_TERMS        5
#define AT_MATCHING_QUERIES     6
#define AT_COMPETITOR_PAGES     7
#define AT_ADDURL               8
#define MAX_ACCESS_TYPE         8


static void freeStateControl ( StateControl *stC );

Proxy::Proxy() {
	m_proxyId = -1;
	m_proxyRunning = false;
	for (int32_t i =0; i < MAX_HOSTS; i++)
		m_numOutstanding[i] = 0;
	for ( int32_t i = 0 ; i < MAX_STRIPES ; i++ ) {
		m_termsOutOnStripe   [i] = 0;
		m_queriesOutOnStripe [i] = 0;
		m_stripeLastHostId   [i] = -1;
	}
	m_nextStripe = 0;
	m_lastHost = 0;
	m_mainHost = 0;
        //m_msg3c    = NULL;
}

Proxy::~Proxy() {
	/*
        if(m_msg3c) {
                // free the msg3c
                mdelete(m_msg3c, sizeof(Msg3c), "Proxy-Msg3c");
                delete m_msg3c;
                m_msg3c = NULL;
        }
	*/
}

bool Proxy::initHttpServer ( uint16_t httpPort, 
			uint16_t httpsPort ) {
	if ( ! g_httpServer.init( httpPort, httpsPort, 
				  proxyHandlerWrapper ) ) {
		return false;
	}
	return true;
}

bool Proxy::initProxy ( int32_t proxyId, uint16_t udpPort,
			uint16_t udpPort2,UdpProtocol *dp ) {
	// let's ensure our core file can dump
	struct rlimit lim;
	lim.rlim_cur = lim.rlim_max = RLIM_INFINITY;
	if ( setrlimit(RLIMIT_CORE,&lim) ){
		log("proxy: setrlimit: core: %s", mstrerror(errno) );
		return false;
	}
	//lim.rlim_cur = lim.rlim_max = 4000;
	//if ( setrlimit(RLIMIT_NOFILE,&lim) ){
	//	log("proxy: setrlimit: numfds: %s", mstrerror(errno) );
	//	return false;
	//}
	//log("proxy: set max fds to 4000");

	m_proxyId = proxyId;

	// set this in Hostdb too!
	g_hostdb.m_hostId = proxyId;

	m_proxyRunning = true;
	// load up hosts.conf
	/*char *hostsConf = "./hosts.conf";
	g_hostdb.reset();
	if ( ! g_hostdb.init(hostsConf, m_proxyId, NULL, 
			     true  ) ) {//isproxy
		log("db: hostdb init failed." ); return 1; }*/
	
	//gb.conf should be in the same directory as gb
 	//if ( ! g_conf.init ( "./" ) ) { // , h->m_hostId ) ) {
	//	log("db: Conf init failed." ); return 1; }

	//We log the http requests, although this is directly done by us
	g_conf.m_logHttpRequests = true;

	//Don't send email alerts, the machines on the cluster can do that
	//g_conf.m_sendEmailAlerts = false;

	// my new email
	
	// if proxy, always have autosave on so we can save user accounting
	// info regularly for billing feed access. save every 5 minutes.
	g_conf.m_autoSaveFrequency = 5;


	// no, now proxies do too! for out of socket conditions in tcpserver
	g_conf.m_sendEmailAlerts = true;

	g_conf.m_sendEmailAlertsToEmail1 = true;
	// verizon bought us out... smtp-sl.vtext.com
	// was messages.alltel.com
	strcpy ( g_conf.m_email1Addr , "5054503518@vtext.com");
	strcpy ( g_conf.m_email1From , "sysadmin@gigablast.com");
	// got ip 69.78.67.53 for 'smtp-sl.vtext.com'
	//strcpy ( g_conf.m_email1MX   , "gbmxrec-vtext.com");
	strcpy ( g_conf.m_email1MX   , "10.5.54.47");





	//if ( ! g_hostdb2.validateIps ( &g_conf ) ) {
	//	log("db: Failed to validate ips." ); return 1;}

	// init the loop, needs g_conf
	//if ( ! g_loop.init() ) {
	//	log("db: Loop init failed." ); return false; }
	
	// . autoban must be on
	// . MDW: why? leave it alone. not good for buzz.
	//g_conf.m_doAutoBan = true;
	if (!g_autoBan.init()){
		log("autoban: init failed.");
		return false;
	}
	
	//for pingserver
	//if ( ! g_hostdb.validateIps ( &g_conf ) ) {
	//	log("db: Failed to validate ips." ); return 1;}
	
	//if ( ! g_udpServer.init( udpPort ,dp,2/*niceness*/,
	//			  10000000 ,   // readBufSIze
	//			  10000000 ,   // writeBufSize
	//			  60       ,   // pollTime in ms
	//			  10000     )){ // max udp slots
	//	log("db: UdpServer init failed." ); return 0; 
	//}

	// start pinging right away, udpServer has already been init'ed
	if ( ! g_pingServer.init() ) {
		log("db: PingServer init failed." ); return false; 
	}

	if ( ! g_pingServer.registerHandler() ) 
		return false;

	//Also have to init pages because we need to know which requests to
	//forward. html/gif's, etc can be taken care here itself.
	g_pages.init ( );
	// load up the dmoz categories here
	char structureFile[283];
	sprintf(structureFile, "%scatdb/gbdmoz.structure.dat", g_hostdb.m_dir);
	g_categories = &g_categories1;
	if (g_categories->loadCategories(structureFile) != 0) {
		log("cat: Loading Categories From %s Failed.",
		    structureFile);
		//return 1;
	}
	log(LOG_INFO, "cat: Loaded Categories From %s.",
	structureFile);

	Msg13 msg13;	if ( ! msg13.registerHandler () ) return false;	

	// . then dns Distributed client
	// . server should listen to a socket and register with g_loop
	// . Only the distributed cache shall call the dns server.
	if ( ! g_dns.init( g_hostdb.m_myHost->m_dnsClientPort ) ) {
		log("db: Dns distributed client init failed." ); return 1; }

	MsgC msgc; if ( ! msgc.registerHandler() ) return false;

	//need to init collectiondb too because of addurl
	//set isdump to true because we aren't going to store any data in the
	//collection
	if ( !g_collectiondb.loadAllCollRecs( ) ){ //isDump
		log ("db: collectiondb init failed.");
		return false;
	}
	//init g_msg
	g_msg = "";

	// load accounting info
	if ( ! loadUserBufs ( ) )
		return log("proxy: failed to load user bufs");

	return true;
}

void proxyHandlerWrapper ( TcpSocket *s ){
	g_proxy.handleRequest (s);
}

static int32_t s_yippySearchesOut = 0;

bool Proxy::handleRequest (TcpSocket *s){

	// if we are a spider compression proxy, do not really act like
	// a proxy at all!
	if ( g_hostdb.m_myHost->m_type == HT_SCPROXY ) {
		//httprequest changes the buf
		g_httpServer.requestHandler(s);
		g_msg = "";
		return true;
	}

	/*
	char buf[MAX_REQ_LEN+10];
	int32_t bufSize = s->m_readOffset;
	if ( bufSize > MAX_REQ_LEN - 1 )
		bufSize = MAX_REQ_LEN - 1;
	gbmemcpy( buf, s->m_readBuf, bufSize );
	buf[bufSize] = '\0';
	*/

        //m_s = s;

	HttpRequest hr;

	//bool status = hr.set ( buf , bufSize , s ) ;
	bool status = hr.set ( s->m_readBuf , s->m_readOffset , s ) ;
	if ( ! status ) {
		// log a bad request
		log("http: Got bad request from %s: %s",
		    iptoa(s->m_ip),mstrerror(g_errno));
		// cancel the g_errno, we'll send a BAD REQUEST reply to them
		g_errno = 0;
		// . this returns false if blocked, true otherwise
		// . this sets g_errno on error
		// . this will destroy(s) if cannot malloc send buffer
		g_httpServer.sendErrorReply ( s , 400, "Bad Request" );
		return false;
	}

	bool isAdmin = g_conf.isMasterAdmin(s,&hr);

	int32_t redirLen = hr.getRedirLen() ;
	char *redir = NULL;
	if(redirLen > 0) redir = hr.getRedir();

	// redirect everyone away if we should
	if ( !redir &&
	     !isAdmin && 
	     // . we put this here to redirect all traffic somewhere else
	     // . you can set that url in the master controls
	     // . it only redirects there if the raw/code/site/sites is NULL
	     *g_conf.m_redirect != '\0' &&
	     hr.getLong("xml", -1) == -1 &&
	     hr.getLong("raw", -1) == -1 &&
	     hr.getString("code")  == NULL &&
	     hr.getString("site")  == NULL &&
	     hr.getString("sites") == NULL) {
		//direct all non-raw, non admin traffic away.
		redir = g_conf.m_redirect;
		redirLen = gbstrlen(g_conf.m_redirect);
	}


	// . we may be serving multiple hostnames
	// . www.gigablast.com, gigablast.com, www.inifinte.info,
	//   infinite.info, www.microdemocracy.com
	// . get the host: field from the MIME
	// . should be NULL terminated
	char *host  = hr.getHost();
	char *hdom = host;
	if ( strncasecmp(hdom,"www.",4) == 0 ) hdom += 4;
	if ( strncasecmp(hdom,"www2.",5) == 0 ) hdom += 5;
	if ( strncasecmp(hdom,"www1.",5) == 0 ) hdom += 5;
	// auto redirect eventguru.com to www.eventguru.com so cookies
	// are consistent
	if ( ! redir && 
	     ( strcasecmp ( host , "eventguru.com" ) == 0 ||
	       strcasecmp ( hdom , "flurbit.com" ) == 0 ||
	       strcasecmp ( hdom , "flurbits.com" ) == 0 ||
	       strcasecmp ( hdom , "flurpit.com" ) == 0 ||
	       strcasecmp ( hdom , "flurbot.com" ) == 0 ||
	       strcasecmp ( hdom , "flurbits.com" ) == 0 ||
	       strcasecmp ( hdom , "flurbyte.com" ) == 0 ||
	       strcasecmp ( hdom , "eventstereo.com" ) == 0 ||
	       strcasecmp ( hdom , "eventcrier.com" ) == 0 ||
	       strcasecmp ( hdom , "eventwidget.com" ) == 0 ) ) {
		redir = "http://www.eventguru.com/";
		redirLen = gbstrlen(redir);
	}

	bool isEventGuru = false;
	if ( strcasecmp(hdom,"eventguru.com") == 0 )
		isEventGuru = true;

#ifdef MATTWELLS
#define HTTPS_REDIR 1
#endif


	if ( redirLen > 0 && redir ) {
#ifdef HTTPS_REDIR
	redirect:
#endif
		HttpMime m;
		m.makeRedirMime (redir,redirLen);
		// . move the reply to a send buffer
		// . don't make sendBuf bigger than g_httpMaxSendBufSize
		int32_t sendBufSize = m.getMimeLen();
		if ( sendBufSize > g_conf.m_httpMaxSendBufSize ) 
			sendBufSize = g_conf.m_httpMaxSendBufSize;
		char *sendBuf    = (char *) mmalloc (sendBufSize,"HttpServer");
		if ( ! sendBuf ) 
			return g_httpServer.sendErrorReply(s,500,
							   mstrerror(g_errno));
		gbmemcpy ( sendBuf , m.getMime() , sendBufSize );
		// . send it away
		// . this returns false if blocked, true otherwise
		// . this sets g_errno on error
		// . this will destroy "s" on error
		// . "f" is the callback data
		// . it's passed to cleanUp() on completion/error before socket
		//   is recycled/destroyed
		// . this will call getMsgPiece() to fill up sendBuf from file
		TcpServer *tcp = s->m_this;
		if (  ! tcp->sendMsg ( s           , 
				       sendBuf     ,
				       sendBufSize ,
				       sendBufSize ,
				       sendBufSize ,
				       NULL        ,   // data for callback
				       NULL        ) ) // callback
			return false;
		// it didn't block or there was an error
		return true;
	}



	// . just requesting a static file, like rants.html or logo.gif?
	// . if so just handle that as a normal html/image file
	int32_t n = g_pages.getDynamicPageNumber ( &hr );
	char *path = hr.getPath();
	//int32_t pathLen = hr.getPathLen();

	// serve events on the gigablast.com domain:
	if ( path && strncmp(path,"/events",7) == 0 )
		isEventGuru = true;
	
	/*
	bool badPage = false;
	if ( n < 0 ) badPage = true;
	if ( hr.getRedirLen() > 0 ) badPage = true;
	if (pathLen == 11 && strncmp ( path , "/index.html" ,11 ) ==0 )
		badPage = false;
	if (pathLen >= 4 && strncmp ( path , "/seo" ,4 ) ==0 )
		badPage = false;
	if ( g_isYippy )
		badPage = false;
	if ( badPage ) {
		//httprequest changes the buf
		g_httpServer.requestHandler(s);
		g_msg = ""; 
		return true; 
	}
	*/

	// . i guess right now the proxy is handling admin pages itself
	// . i am changing this so that if &forward=<hostid> is in the url then

	//   the proxy forwards the control page request to the given hostid
	int32_t forward = hr.getLong("forward",-1);

	bool handleIt = true;
	if ( forward != -1       ) handleIt = false;
	//if ( n == PAGE_ROOT      ) handleIt = false;
	if ( n == PAGE_GET       ) handleIt = false;
	if ( n == PAGE_RESULTS   ) handleIt = false;
	if ( n == PAGE_DIRECTORY ) handleIt = false;

	// proxy now handles the shell addurl page, the actual request
	// made by the ajax in the shell to add the url has a &id= in it
	// and should go to the backend for adding the url. but we can
	// handle the shell, just the add url page html including the ajax
	// script.
	int32_t cgiId = hr.getLong("id",0);
	if ( n == PAGE_ADDURL && cgiId ) handleIt = false;

	// send this to gk144 
	if ( strncmp(path,"/seo",4) == 0 ) handleIt = false;

	if ( strncmp(path,"/seoapi",7) == 0 ) handleIt = true;

	// we're just a tcp proxy if yippy
	if ( g_isYippy ) 
		handleIt = false;

	// special pages. any html page will now need to call
	// msgfb to get the user info, like the name "Matt Wells" to
	// post in the black bar, so we can't really do this on the
	// proxy any more, we have to route all the way to the cluster.
	//if ( pathLen >= 7 && strnstr(path,".html",pathLen) ) handleIt =false;
	// same for xml... (eventguru.xml search provider list)
	//if ( pathLen >= 7 && strnstr(path,".xml",pathLen) ) handleIt = false;

	// for stats pages etc for yippy proxy
	if ( g_isYippy && ! strncmp(path,"/master",7) ) 
		handleIt = true;

	/*
	if ( ! strncmp(path,"/blog.html"   ,10) ) handleIt = false;
	if ( ! strncmp(path,"/terms.html"  ,11) ) handleIt = false;
	if ( ! strncmp(path,"/privacy.html",13) ) handleIt = false;
	if ( ! strncmp(path,"/account.html",13) ) handleIt = false;
	if ( ! strncmp(path,"/bio.html",13) ) handleIt = false;
	*/
	// our new cached page representation format
	if ( ! strncmp(path,"/?id="        ,5 ) ) handleIt = false;


	// log the request iff filename does not end in .gif .jpg .
	char *f = NULL;
	int32_t  flen = 0;
	if ( isEventGuru ) {
		f     = hr.getFilename();
		flen  = hr.getFilenameLen();
	}

	// proxy will handle eventguru images i guess
	bool  isGif = ( f && flen >= 4 && strncmp(&f[flen-4],".gif",4) == 0 );
	bool  isJpg = ( f && flen >= 4 && strncmp(&f[flen-4],".jpg",4) == 0 );
	bool  isBmp = ( f && flen >= 4 && strncmp(&f[flen-4],".bmp",4) == 0 );
	bool  isPng = ( f && flen >= 4 && strncmp(&f[flen-4],".png",4) == 0 );
	bool  isIco = ( f && flen >= 4 && strncmp(&f[flen-4],".ico",4) == 0 );
	bool  isPic = (isGif | isJpg | isBmp | isPng || isIco);

	// use event guru favicon?
	//if ( isEventGuru && isIco && strcmp(f,"favicon.ico") == 0 ) {
	//	f = "eventguru_favicon.ico";
	//	flen = gbstrlen(f);
	//}

	// eventguru.com host: in mime?
	if ( isEventGuru && ! isPic )
		handleIt = false;

	// only proxy holds the accounting info
	if ( ! strncmp ( path ,"/account", 8 ) ) {
		printRequest(s, &hr);
		return sendPageAccount ( s , &hr );
	}


	// get the server this socket uses
	TcpServer *tcp = s->m_this;
	int32_t max;
	if ( tcp == &g_httpServer.m_ssltcp ) max = g_conf.m_httpsMaxSockets;
	else                                 max = g_conf.m_httpMaxSockets;

#ifdef HTTPS_REDIR
	// if hitting root page then tell them to go to https
	// if not autobanned... but if it is an autobanned request on root
	// page it should have go the turing test above!
	if ( n == PAGE_ROOT && 
	     ! g_isYippy &&
	     // not event guru homepage
	     ! isEventGuru &&
	     // if not already on https
	     tcp != &g_httpServer.m_ssltcp &&
	     // do not redirect http://www.gigablast.com/?c=dmoz3 (directory)!
	     hr.m_cgiBufLen <=1 ) {
		// redirect to https site
		redir = "https://www.gigablast.com/";
		//
		// if we are gk267 then redirect to https://www2.gigablast.com/
		//
		static int32_t s_ip2 = 0;
		if ( ! s_ip2 ) s_ip2 = atoip("10.5.56.77");
		if ( (int32_t)g_hostdb.m_myIp == s_ip2 )
			redir = "https://www2.gigablast.com/";
		redirLen = gbstrlen(redir);
		goto redirect;
	}
#endif


	// . page addurl uses the udpserver to send the addurl stuff to one of
	//   the hosts, so we need udpserver.
	// . handle the request ourselves if it is not one of these
	//   pages and "forward" was not specified in the url cgi fields
	if ( handleIt ) {
		//httprequest changes the buf
		g_httpServer.requestHandler(s);
		g_msg = "";
		return true;
	}

	// limit yippy's "GET /search" requests to 50 out...
	int32_t ymax = 150; // 150;//50;//25; 300 for one process is good
	ymax = g_conf.m_maxYippyOut;
	bool isYippySearch = false;
	if ( g_isYippy && ! strncmp(path,"/search?",8) )
		isYippySearch = true;

	// is it a search request from a toolbar? we do not want to fuck
	// with those requests with the anti-bot code below
	bool isYippyToolBarRequest = false;
	if ( g_isYippy && isYippySearch ) {
		int32_t iflen;
		char *ifs = hr.getString("input-form",&iflen,NULL);
		if ( ! ifs ) isYippyToolBarRequest = true;
	}

	// just a safety catch
	if ( max < 20 ) max = 20;

	// enforce the open socket quota iff not admin and not from intranet
	if ( ! isAdmin && tcp->m_numIncomingUsed >= max && 
	     !tcp->closeLeastUsed()) {
		static int32_t s_last = 0;
		static int32_t s_count = 0;
		int32_t now = getTimeLocal();
		if ( now - s_last < 5 ) 
			s_count++;
		else {
			log("query: Too many sockets open. Sending 500 "
			    "http status code to %s. (msgslogged=%" INT32 ")[2]",
			    iptoa(s->m_ip),s_count);
			s_count = 0;
			s_last = now;
		}
		g_stats.m_closedSockets++;; 
		return g_httpServer.sendErrorReply ( s , 500 , 
						     "Too many sockets open.");
	}

	//
	// yippy traffic control
	//
	if ( isYippySearch && s_yippySearchesOut >= ymax ) {
		//
		// log a note
		//
		bool banned = false;
		int32_t yqLen;
		char *yq = hr.getString("query",&yqLen,NULL);
		if ( ! yq ) yq = "";
		if( ! isYippyToolBarRequest &&
		    !g_autoBan.hasPerm(s->m_ip, 
				      NULL,0,//code, codeLen, 
				      0,0,//uip, uipLen, 
				      s, &hr, NULL,//&testBuf,
				      true ) )  { // justCheck )) 
			banned = true;
			log("proxy: got banned search req cutoff %s (%s)",
			    iptoa(s->m_ip),yq);
		}
		else
			log("proxy: got cutoff search req %s (%s)",
			    iptoa(s->m_ip),yq);


		if ( ! banned ) {
			static int32_t s_last = 0;
			static int32_t s_count = 0;
			int32_t now = getTimeLocal();
			if ( now - s_last < 5 ) 
				s_count++;
			else {
				log("query: Too many outstanding yippy search "
				    "requests, %" INT32 ". closing socket on %s. "
				    "(repeats=%" INT32 ")",
				    ymax,
				    iptoa(s->m_ip),s_count);
				s_count = 0;
				s_last = now;
			}
		}
		g_stats.m_closedSockets++;
		return g_httpServer.sendErrorReply ( s , 500 , 
						"Too many search requests. "
						     "Please reload your "
						     "browser to try again.");
	}

	/////
	//
	// who dare accesses us?  manually or automatically...
	//
	/////
	int32_t USERID32 = 0;
	UserInfo *UI = NULL;

	//
	// the type of thing being accessed...
	//
	int32_t accessType = getAccessType ( &hr );

	//
	// the cost of the accesses in USD
	//
	float price = getPrice ( accessType );

	//


	//////////
	//
	// automated feed access verification
	//
	//////////

	// did they provide an access code?
	int32_t codeLen = 0;
	char *code = hr.getString("code", &codeLen, NULL);
	// only feed access supplies a userid in the http get request
	int32_t userId32b = hr.getLong("userid",0);
	// if there is a code but no userid, it might be an old style
	// request from client, etc... so search for those guys by
	// code...
	if ( code && userId32b == 0 ) {
		// get the userid from the provided code...
		int32_t nu = m_userInfoBuf.length()/sizeof(UserInfo);
		if ( nu > 10 ) nu = 10;
		UserInfo *uis = (UserInfo *)m_userInfoBuf.getBufStart();
		for ( int32_t i =0 ; i < nu ; i++ ) {
			UserInfo *ui = &uis[i];
			if ( strcmp ( ui->m_xmlFeedCode,code) ) continue;
			userId32b = ui->m_userId32;
			break;
		}
		// code is invalid if is not for an old client
		if ( userId32b == 0 ) code = NULL;
	}
	// if we have both a code and userid, check to see if it is correct
	if ( code ) {
		// get it
		UserInfo *ui = getUserInfoFromId ( userId32b );
		// not found is bad! or if code does not match
		if ( price!=0.0 && (! ui || strcmp(ui->m_xmlFeedCode,code))){
			char *msg = "Permission denied. Check your "
				"<i>userid</i> and <i>code</i> cgi parms.";
			return g_httpServer.sendErrorReply(s,500,msg);
		}
		// check funds. m_pending is a positive amount, so is price.
		// accountBalance is how much money they have in their acct
		float cur = 0.0;
		if ( ui ) cur = ui->m_accountBalance - ui->m_pending - price;
		if ( ui && cur < 0.0 && 
		     // we do not use the new system to track ...
		     // we still need to bill them the old way
		     ! ( ui->m_flags & (UIF_OLDUSER | UIF_ADMIN) ) ) {
			char *msg = "Not enough funds in account to "
				"process request.";
			return g_httpServer.sendErrorReply(s,500,msg);
		}
		// set this
		USERID32 = userId32b;
		// save it
		UI = ui;
		// otherwise, it is pending!
		//stC->m_pending = price;
		//ui->m_pending += price;
	}

	////////
	//
	// paid manual access
	//
	////////
	if ( ! code ) {
		// get logged in user, if any
		UserInfo *ui = getLoggedInUserInfo2 ( &hr , s , NULL );
		// get access type
		if ( accessType == AT_SEARCHFEED_NEW ) price = 0.0;
		if ( accessType == AT_SEARCHFEED_OLD ) price = 0.0;
		// store it
		if ( ui ) USERID32 = ui->m_userId32;
		// save it
		UI = ui;
	}

        ////////
	//
	// the almighty autoban
	// 
	////////

	bool doAutoBan = true;//g_conf.m_doAutoBan;
	// if doing gigablast, only do autoban on PAGE_RESULTS
	if ( n != PAGE_RESULTS ) doAutoBan = false;
	// if they provided a valid code do not do autoban. if the code
	// was invalid we will have returned an error msg above
	if ( code ) doAutoBan = false;
	// assume not banned
	bool banned = false;
	// only check it for search results that have no valid "code"
	char testBufSpace[2048];
	SafeBuf testBuf(testBufSpace, 2048);
	if ( doAutoBan ) {
		int32_t uipLen;
		char *uip = hr.getString("uip", &uipLen, NULL);
                int32_t  ip  = s->m_ip;
		bool good = g_autoBan.hasPerm(ip, 
					      NULL,0,//code, codeLen, 
					      uip, uipLen, 
					      s, &hr, &testBuf,
					      false ); // justCheck
		if ( ! good ) banned = true;
	}
	// special yippy logging case
	if ( banned && isYippySearch ) {
		// log it
		int32_t yqLen;
		char *yq = hr.getString("query",&yqLen,NULL);
		if ( ! yq ) yq = "";
		log("proxy: got banned search req %s (%s)",
		    iptoa(s->m_ip),yq);
	}
	// print the turing test if autoban gave us one
	if ( banned ) {
		g_msg = " (error: autoban rejected.)";
		// this is the turing test i guess in testBuf
		if( testBuf.length() > 0 ) {
			printRequest(s, &hr);
			//g_stats.m_numSuccess++;
			return g_httpServer.
				sendDynamicPage(s, 
						testBuf.getBufStart(),
						testBuf.length(), 
						0);
		}
		printRequest(s, &hr);
		//g_stats.m_numSuccess++;
		int32_t rawFormat = hr.getLong("xml", 0); // was "raw"
		// support old raw=9 crap as well
		rawFormat = hr.getLong("raw",rawFormat);
		if ( rawFormat > 0 )
			return g_httpServer.sendQueryErrorReply
				(s,402, mstrerror(EBUYFEED),
				 rawFormat, g_errno, 
				 "You have exceeded the allowed "
				 "amount of free searches. You can buy "
				 "credits by creating an account at "
				 "https://www.gigablast.com/account");
		// if html format just return msg ...
		return g_httpServer.sendErrorReply(s,500,mstrerror(EBUYFEED));
	}

	// hash the code
	int32_t ch3 = 0; if ( code ) ch3 = hash32n ( code );
	
	// . increment their "outstanding requests" count
	// . customers now have a limit of outstanding requests to
	//   prevent abuse... vivisimo does not use "uip" and they 
	//   frequently get attacked by a spammer
	// . if autoban is off we still increment these counts, so if autoban
	//   gets turned on the counts will be unaffected
	/*
	int32_t bytesReceived = hr.getRequestLen();
	bool overLimit = g_autoBan.incRequestCount ( ch3 , bytesReceived );
	if ( g_conf.m_doAutoBan && overLimit ) {
		g_msg = " (error: too many outstanding requests)";
		printRequest ( s , &hr );
		// get how many bytes were sent out
		int32_t bs;
		bool st = g_httpServer.sendErrorReply(s,500,g_msg,&bs);
		g_autoBan.decRequestCount ( ch3 , bs );
		return st;
	}
	*/

	bool err2 = false;
	if ( err2 ) {
	hadError2:
		g_errno = ENOMEM;
		log("proxy: new(%i): %s",(int32_t)sizeof(StateControl),
		    mstrerror(g_errno));
		g_msg = " (error: out of memory.)";
		printRequest(s, &hr);
		int32_t bs;
		bool st;
		st=g_httpServer.sendErrorReply(s,500,mstrerror(g_errno),&bs);
		g_autoBan.decRequestCount ( ch3 , bs );
		return s;
	}

	// if we get here that means we've got something to forward.
	StateControl *stC;
	try { stC = new (StateControl) ; }
	// return true and set g_errno if couldn't make a new File class
	catch ( ... ) { 
	  goto hadError2;
	}
	mnew ( stC, sizeof(StateControl), "Proxy");

	// make a copy of this now
	if ( ! stC->m_hr.copy ( &hr ) ) {
		mdelete(stC,sizeof(StateControl),"Proxy");
		delete(stC);
		goto hadError2;
	}

	// reset to -1 in case freeStateControl is called
	stC->m_hostId = -1;
	stC->m_slot = NULL;

	// store the code for decementing the outstanding request count below
	stC->m_ch = ch3;

	// support &xml=1 or &raw=9 or &raw=8 to indicate xml output is wanted
	stC->m_raw = hr.getLong ( "xml", 0 );
	stC->m_raw = hr.getLong("raw",stC->m_raw);
	
	stC->m_s = s;

	stC->m_pageNum = n;

	stC->m_startTime = gettimeofdayInMilliseconds();

	stC->m_isQuery = false;

	//check if we've got a query
	if ( n == PAGE_RESULTS )
		stC->m_isQuery = true;

	stC->m_hash = hash32( hr.getRequest(), hr.getRequestLen() );

	// assume we are not doing a search query (stripe load balancing)
	stC->m_stripe = -1;

	stC->m_isYippySearch = isYippySearch;

	// log it
	if ( isYippySearch ) {
		int32_t yqLen;
		char *yq = hr.getString("query",&yqLen,NULL);
		if ( ! yq ) yq = "";
		log("proxy: got ok search req %s (%s)",  iptoa(s->m_ip),yq);
	}

	// yippy tcp proxy?
	int32_t x;
	char *sendBuf      ;
	int32_t  sendBufSize  ;
	int32_t  sendBufUsed  ;
	int32_t  msgTotalSize ;
	int32_t  timeout      ;
	if ( g_isYippy ) {
		// make it sticky, based on ip
		uint32_t iph = hash32((char *)&stC->m_s->m_ip,4);
		x = iph % 4;
		char *hn[] = { "10.36.14.4" , // teaski1
			       "10.36.14.5" , // teaski2
			       "10.36.14.17" , // teaski3
			       "10.36.14.44" }; // teaski4
		char *host = hn[x];
		// debug
		//host = "www2.gigablast.com";
		SafeBuf *sb = &stC->m_sb;
		sb->safeMemcpy(s->m_readBuf,s->m_readBufSize);
		sendBuf      = sb->getBufStart();
		sendBufSize  = sb->length();
		sendBufUsed  = sb->length();
		msgTotalSize = sb->length();
		// make it a int32_t time so we are less likely to overload
		// the teaski servers, wait for reply from reach one...
		timeout      = 60 * 1000; // in milliseconds
		// note it
		//log("proxy: sending request \"%s\" to %s",sendBuf,host);
		static int32_t s_reqNum = 0;
		stC->m_reqNum = s_reqNum;
		s_reqNum++;
		stC->m_start = gettimeofdayInMilliseconds();
		// debug log debug
		//log("proxy: forwarding reqNum=%" INT32 " from %s to %s",
		//    stC->m_reqNum,iptoa(stC->m_s->m_ip),host);
		// only allow so many outstanding to avoid overloading
		// the teaski servers
		if ( stC->m_isYippySearch ) 
			s_yippySearchesOut++;
		// forward to a teaski
		if ( ! g_httpServer.m_tcp.sendMsg ( host, // hostname
						    gbstrlen(host),
						    80,
						    sendBuf,
						    sendBufSize,
						    sendBufUsed,
						    msgTotalSize,
						    stC,
						    gotTcpReplyWrapper,
						    timeout,
						    -1,
						    -1))
		     return false;
		// it did not block, wtf?
		return true;
	}


	// we need to know how many terms (excluding synonyms)
	// so we can do stripe load balancing by number of query terms.
	// i.e. sending 3 queries of only one term each is about the
	// same as sending one larger query to a single stripe.
	char *qs = hr.getString("q",NULL);
	Query q;
	if ( qs ) 
		q.set2 ( qs , langUnknown , false ); // 2 = autodetect bool
	// clear g_errno in case Query::set() set it
	g_errno = 0;
	// save it. might be zero!
	stC->m_numQueryTerms = q.getNumTerms();


	Host *h = NULL;
	//if ( forward >= 0 )
	//	h = g_hostdb.getHost ( forward );
	//if we want the main page, or if it is addurl, cannot send addurl to
	// another host if turing is on. Pick 1 host and keep sending to it
	if ( n == PAGE_REINDEX ||
	     n == PAGE_INJECT  ||
	     n == PAGE_ADDURL  ||
	     //n == PAGE_ROOT   || // FOR DEBUG!!
	     //n == PAGE_RESULTS || // FOR DEBUG!!
	     n == PAGE_SITEDB   )
		// get host #0
		h = g_hostdb.getHost ( 0 );
	/*
	  no longer - flurbit root page is the search page...
	else if ( n == PAGE_ADDURL || pathLen == 1 || 
	     ( pathLen == 11 && strncmp ( path , "/index.html" ,11 ) == 0 ) ){
		int32_t numTries = 0;
		while ( g_hostdb.isDead(m_mainHost) && 
			numTries++ < g_hostdb.getNumHosts() ){
			m_mainHost++;
			if ( m_mainHost >= g_hostdb.getNumHosts() )
				m_mainHost = 0;
		}
		h = g_hostdb.getHost( m_mainHost );
		m_numOutstanding[m_mainHost]++;
	}
	*/
	else 
		h = pickBestHost ( stC );

	// save in case of timeout below
	//stC->m_forward = -1;// forward;

	stC->m_retries = 0;

	stC->m_forwardHost = h;

	// . TODO: make both this and Multicast.cpp use a getTimeout() function
	// . default timeout to 8 seconds
	stC->m_timeout = 8 * 1000;
	// set the timeout
	int32_t  firstResult = hr.getLong("s", 0);
	int32_t  docsWanted  = hr.getLong("n", 10);
	// how many docsids request? first 4 bytes of request.
	int32_t  rr          = hr.getLong("rerank",-1);
	// . how many milliseconds of waiting before we re-route?
	// . 100 ms per doc wanted, but if they all end up 
	//   clustering then docsWanted is no indication of the
	//   actual number of titleRecs (or title keys) read
	// . it may take a while to do dup removal on 1 million docs
	int64_t wait = 5000 + 100  * (docsWanted+firstResult);
	// those big UOR queries should not get re-routed all the time
	wait += 1000 * stC->m_numQueryTerms;
	// a min of 8 seconds is good
	if ( wait < 8000 ) wait = 8000;
	// seems like buzz is hammering the cluster and 0x39's are 
	// timing out too much because of huge title recs taking 
	// forever with Msg20
	//if ( wait < 120000 ) wait = 120000;
	// never re-route if it has a rerank, those take forever
	if ( rr >= 0 ) wait = 3000 * 1000;
	// set it
	stC->m_timeout = wait;

	///////////////////////////////
	//
	// HACK: PRICE GATEWAY INTERCEPTION
	//
	// . page add url first loads the main page, then it has some 
	//   ajax to re-get the same url but with &id=xxx where xxx != 0
	// . so when gk144 gets a /addurl?id=123 request it will trigger
	//   the injection and send back the injection status which the ajax
	//   prints below the text box containing the url. the returned html
	//   is just a little msg which the ajax sets the content of the msgbox
	//   div to.
	// . so, we being the proxy will now pre-check any /addurl?id=123 
	//   request to make sure they are logged in. if not we will just 
	//   return a simple, you need to login, msg
	// . and if signed in, make sure they have to $10 available otherwise
	//   tell them they need to add money (check m_pending too)
	// . otherwise we have to let is pass through and look at the reply
	//   msg from gk144. if it says "Success" then we deduct from their
	//   m_accountBalance. otherwise we do not charge them...
	// . we should use this same logic for the seo html pages as well...
	//
	//

	//
	// . who is accessing what and for how much...
	// . we don't store "UI" since safebuf of UserInfos might realloc on us
	//
	stC->m_userId32   = USERID32;
	stC->m_accessType = accessType;
	stC->m_price      = price;

	// if they have "code=" in the url then that is an xml feed and
	// we forawrd the request to the back-end right away now
	if ( code ) {
		// UI Must be non-NULL since code was valid
		UI->m_pending += price;
		return forwardRequest ( stC );
	}

	// . forward any request besides addurl through
	// . this add url request is the one sent from the ajax on the add url
	//   shell page because we check way above for "id" where cgiId is.
	//if ( accessType != AT_ADDURL ) {
		// do not charge for anything else!
		stC->m_price = 0;
		// for now the tool and free and you don't have to login
		//if ( UI ) UI->m_pending += price;
		return forwardRequest ( stC );
	//}

	SafeBuf msg;
	float toolPrice = getPrice(AT_ADDURL);

	if ( ! UI )
		msg.safePrintf("<b>You need to "
			       "<a href=/account>login</a> to use "
			       "this tool. Each added url is $%.02f</b>",
			       toolPrice);

	if ( UI && UI->m_accountBalance - UI->m_pending - toolPrice<0 )
		msg.safePrintf("<b>The Add Url tool costs $%.02f and your "
			       "account balance is below this. "
			       "You need to <a href=/account>add more funds"
			       "</a> to use this "
			       "tool.</b>",
			       toolPrice);

	// . for every tool we ask "Are you sure? [yes] [no]" 1 or 0
	// . must be confirmed
	int32_t confirmed = hr.getLong("confirmed",0);
	if ( msg.length() == 0 && confirmed != 1 ) {
		// make another onclick ajax event
		msg.safePrintf("<b>Are you sure you want to add this url "
				"for $%.02f? "
			       //"<input type=submit value=Yes "
			       "<input type=button value=Yes "
				"onclick=\""
				"var client = new XMLHttpRequest();\n"
				"client.onreadystatechange = handler;\n"
				"var url='/addurl?u="
				,toolPrice);
		char *urlToAdd = hr.getString("u",NULL);
		msg.urlEncode ( urlToAdd );
		uint32_t h32 = hash32n(urlToAdd);
		if ( h32 == 0 ) h32 = 1;
		uint64_t rand64 = gettimeofdayInMillisecondsLocal();
		msg.safePrintf( "&id=%" UINT32 "&rand=%" UINT64 "&confirmed=1';\n"
				 "client.open('GET', url );\n"
				 "client.send();\n"
				 "\">"
				 "<input type=button value=Cancel>"
				 ,h32
				 ,rand64);
	}

	//
	// return the reason why the tool could not charge the user
	//
	if ( msg.length() ) {
		printRequest( s, &hr );
		g_httpServer.sendDynamicPage ( s      , 
					       msg.getBufStart(),
					       msg.length(),
					       // cachetime in secs
					       // make it forever
					       // to avoid hitting
					       // back button and
					       // reinjecting a url
					       86400*100
					       );
		freeStateControl ( stC );
		return true;
	}

	// assume the addurl goes through
	UI->m_pending += price;

	// send the request
	return forwardRequest ( stC );
}


bool Proxy::forwardRequest ( StateControl *stC ) {

	// this was a function arg... now it is in "stC"
	Host *h = stC->m_forwardHost;

	stC->m_hostId = h->m_hostId;

	TcpSocket *s = stC->m_s;

	//log (LOG_DEBUG,"query: proxy: (hash=%" UINT32 ") %s from "
	//     "hostId #%" INT32 ", port %i", stC->m_hash, hr.getRequest(), 
	//     h->m_hostId,h->m_httpPort);

	// if sending to the temporary network, add one to port
	int32_t port = h->m_httpPort;
	if ( g_conf.m_useTmpCluster ) port += 1;

	// put ip at end of request
	char *req     = s->m_readBuf;
	int32_t  reqSize = s->m_readOffset;
	// but then TcpServer.cpp leaves some room for a \0 and ip
	char *p = req + reqSize;
	// NULL terminate it
	*p = '\0';
	p += 1;
	// then add in ip
	*(int32_t *)p = s->m_ip;
	p += 4;

	bool isQCProxy = (g_hostdb.m_myHost->m_type & HT_QCPROXY);

	// . alter the request buffer
	// . set the please compress reply flag
	if ( isQCProxy && *req == 'G' ) *req = 'Z'; 

	// update size
	reqSize = p - req;
	// sanity check
	if ( reqSize > s->m_readBufSize ) { char *xx=NULL;*xx=0;}

	// sanity check
	if ( h->m_isProxy ) { char *xx=NULL;*xx=0; }

	// if we are a QUERY COMPRESSION proxy send to the specified address
	int32_t dstIp   = h->m_ip;
	int32_t dstPort = h->m_port;
	int32_t dstId   = h->m_hostId;
	if ( isQCProxy ) {
		dstIp   = g_hostdb.m_myHost->m_forwardIp;
		dstPort = g_hostdb.m_myHost->m_forwardPort;
		dstId   = -1;
	}

	// . default is to use the old engine. 
	// . only precise=1 uses new engine.
	HttpRequest *hr = &stC->m_hr;
	bool sendToNewEngine = false;
	int32_t precise = hr->getLong("precise",-1);
	if ( precise == 1 ) sendToNewEngine = true;
	// or if precise not given, an no code, send to new engine
	if ( precise == -1 && ! stC->m_ch ) sendToNewEngine = true;
	// if precise not given, and a code, sent to fast engine
	if ( precise == -1 &&   stC->m_ch ) sendToNewEngine = false;
	// explicit precise?
	if ( precise == 0 ) sendToNewEngine = false;
	// if no code (not a search feed) then do new engine
	//if ( ! stC->m_ch ) sendToNewEngine = true;
#ifndef _USE_GK144_
	sendToNewEngine = false;
#endif

	//
	// . HACK: always send to gk144 unless code= is specified
	// . test on www2.gigablast.com proxy first...
	//
	if ( //! stC->m_ch && 
	     sendToNewEngine &&
	     stC->m_pageNum != PAGE_DIRECTORY &&
	     ! g_isYippy ) {
		dstIp = atoip("10.5.54.154",11);
		dstPort = 9000; // udp (not dns)
		dstId = -1; // not a host in our hosts.conf
	}

	// rewrite &xml=1 as &raw=8 so old search engine sends back xml
	if ( ! sendToNewEngine && 
	     req[0]=='G' && 
	     req[1]=='E' && 
	     req[2]=='T' &&
	     req[3] == ' ' ) {
		// replace &xml=1 in request with &raw=8 to support others
		char *p = req + 4;
		char *pend = req + reqSize;
		// skip GET
		for ( ; p < pend ; p++ ) {
			// stop after url is over
			if ( *p == ' ' ) break;
			// match?
			if ( p[0] != '?' && p[0] != '&' ) continue;
			if ( p[1] != 'x' ) continue;
			if ( p[2] != 'm' ) continue;
			if ( p[3] != 'l' ) continue;
			if ( p[4] != '=' ) continue;
			if ( p[5] != '1' ) continue;
			p[1] = 'r';
			p[2] = 'a';
			p[3] = 'w';
			p[5] = '9';
			break;
		}
		// code is invalid if is not for an old client
		//if ( userId32b == 0 ) code = NULL;
	}


	// . let's use the udp server instead because it quickly switches
	//   to using eth1 if eth0 does two or more resends without an ACK,
	//   and vice versa. this ensure that if a network switch fails then
	//   we won't notice it besides a possible one-time 100ms delay.
	// . additionally, we can now accept tcp requests for admin pages
	//   even if such requests come from the proxy ip! because now they
	//   will just have to be from our ssh tunnel!!
	// . returns false and sets g_errno on error, true on success
	// . after resending the request 4 times with no ACK recv'd, call
	//   it a EUDPTIMEDOUT error and deal with that below...
	bool status;
	status = g_udpServer.sendRequest ( req         ,
					   reqSize     ,
					   0xfd        , //msgType 0xfd for fwd
					   dstIp , // h->m_ip     ,
					   dstPort , // h->m_port   ,
					   dstId , // h->m_hostId ,
					   NULL        , // the slotPtr
					   stC         , // state
					   gotHttpReplyWrapper ,
					   stC->m_timeout  ,
					   -1          , // backoff
					   -1          , // maxwait
					   NULL        , // replyBuf
					   0           , // replyBufMaxSize
					   0           , // niceness
					   4           );// maxResends

	// if no error, return false, we blocked
	if ( status ) return false;

	// wtf? if it fails unchage the pending...
	UserInfo *ui = getUserInfoFromId ( stC->m_userId32 );
	if ( ui ) ui->m_pending -= stC->m_price;

	//bool status;	
	/*
	status =  g_httpServer.getDoc ( h->m_ip,
					port , // h->m_httpPort,
					s->m_readBuf,
					s->m_readOffset,
					stC,
					gotReplyWrapperPage,
					timeout,
					10000000,
					10000000,
					false );
	// return false if it blocked
	if (!status)
		return false;
	*/
	//if not, we've got an error
	g_httpServer.sendErrorReply(s,500,mstrerror(g_errno)); 
	freeStateControl(stC);
	// we send out what we read, s->m_readOffset bytes
	g_autoBan.decRequestCount ( stC->m_ch , s->m_readOffset );
	return true;
}
	    
void gotHttpReplyWrapper ( void *state, UdpSlot *slot ) { // TcpSocket *s ){
	g_proxy.gotReplyPage(state,slot);
}

//void Proxy::gotReplyPage ( void *state, TcpSocket *s ){
void Proxy::gotReplyPage ( void *state, UdpSlot *slot ) {

	StateControl *stC = (StateControl *) state;

	char *reply = slot->m_readBuf;
	int32_t  size  = slot->m_readBufSize;

	char *req = slot->m_sendBufAlloc;

	// . AND this is what we forwarded to a host in the flock
	// . we can free this because it reference the tcp buffer
	slot->m_sendBufAlloc = NULL;

	// decrement the outstanding request count
	g_autoBan.decRequestCount ( stC->m_ch , slot->m_readBufSize );

	// . try another host if this one times out
	// . if it is dead before we send to it then it will not ACK our
	//   requests, we will resend 10 times within about 300 ms and then
	//   we will get slot->m_errno set to EUDPTIMEDOUT
	// . it will also set the errno to EUDPTIMEDOUT if the timeout we
	//   gave sendRequest() above is reached.
	if ( slot->m_errno == EUDPTIMEDOUT && //stC->m_forward < 0 &&
	     // try this thrice i guess... hopefully we won't pick the same
	     // host we did before!
	     ++stC->m_retries <= 3 ) {
		// reduce the query load counts
		uncountStripe ( stC );
		// pick another host! should NEVER return NULL
		Host *h = pickBestHost ( stC );
		log("proxy: hostid #%" INT32 " timed out. req=%s Rerouting "
		    "forward request "
		    "to hostid #%" INT32 " instead.",stC->m_hostId,
		    req,//stC->m_s->m_readBuf,
		    h->m_hostId);
		// . try a resend!
		// . it will block or it will call sendErrorReply
		stC->m_forwardHost = h;
		forwardRequest ( stC );//, h );
		// all done
		return;
	}

	// save it so we can free "reply" when state is destroyed
	stC->m_slot = slot;

	// save reply so we can free it when this state is freed
	// no i am just transferring into the socket's sendbuf now
	stC->m_slotReadBuf = NULL;
	//stC->m_slotReadBuf        = slot->m_readBuf;
	//stC->m_slotReadBufMaxSize = slot->m_readBufMaxSize;

	// should we uncompress the reply?
	bool doUncompress = ( req[0] == 'Z' );
	// do not allow regular proxy to uncompress it though!
	if ( ! (g_hostdb.m_myHost->m_type & HT_QCPROXY ) ) doUncompress=false;

	// don't let udp server free the reply, we forward this to end user
	slot->m_readBuf = NULL;

	// sanity check
	//if ( s->m_readOffset < 0 ) { char *xx=NULL;*xx=0; }
	if ( slot->m_readBufSize < 0 ) { char *xx=NULL;*xx=0; }

	int64_t nowms = gettimeofdayInMilliseconds();

	//m_numOutstanding[stC->m_hostId]--;
	//if ( s->m_readOffset == 0 ){
	if ( size == 0 ){
		log (LOG_WARN,"query: Proxy: Lost the request");
		// give a 500 httpstatus to just decrement UserInfo::m_pending
		addAccessPoint ( stC , nowms , 500 );
		g_errno = EBADREQUEST;
	hadError:
		log(LOG_WARN,"proxy: error=%s req=%s",mstrerror(g_errno),req);
		g_httpServer.sendErrorReply(stC->m_s,500,mstrerror(g_errno));
		freeStateControl(stC);
		return;
	}

	uint64_t took = nowms - stC->m_startTime;

	// if reply was compressed then uncompress it
	if ( doUncompress ) {
		// sanity check
		if ( size < 12 ) { char *xx=NULL;*xx=0; }
		// parse it up
		unsigned char *p = (unsigned char *)reply;
		// get the sizes
		int32_t need  = *(int32_t *)p; p += 4; // uncompressed total size
		int32_t size1 = *(int32_t *)p; p += 4; // size of compressed mime
		int32_t size2 = *(int32_t *)p; p += 4; // size of compressed content
		// note it
		//logf(LOG_DEBUG,"proxy: uncompressing from %" INT32 " to %" INT32 "",
		//     size1+size2+12,need);
		// make the decompressed buf
		unsigned char *dbuf = (unsigned char *)mmalloc ( need,"pdbuf");
		unsigned char *dend = dbuf + need;
		if ( ! dbuf ) goto hadError;
		unsigned char *dptr = dbuf;

		uint32_t bytes1 = dend - dptr;
		// ucompress the http mime
		int err1 = gbuncompress (dptr , &bytes1, p , size1 );
		p += size1;
		dptr += bytes1;

		if ( size2 ) {
			uint32_t bytes2 = dend - dptr;
			// uncompress the http content
			int err2 = gbuncompress ( dptr , &bytes2, p , size2 );
			p += size2;
			dptr += bytes2;
			if ( err2 != Z_OK || err1 != Z_OK ) {
				g_errno = EUNCOMPRESSERROR;
				goto hadError;
			}
		}

		// sanity check
		if ( dptr - dbuf != need ) { char *xx=NULL;*xx=0; }

		// free original compressed reply
		mfree ( reply , size , "origreply");

		// now re-set these to the uncompressed mime/pagecontent
		reply = (char *)dbuf;
		size  = need;
		// . and this is for freeing it after it is transmitted
		// . likewise, let's directly transmit this reply and
		//   let udpserver free it when done
		//stC->m_slotReadBuf        = (char *)dbuf;
		//stC->m_slotReadBufMaxSize = need;
	}


	// if we are a regular proxy forwarding a compressed reply to a
	// query compression proxy, then the reply is compressed, just leave
	// it alone
	if ( ( req[0] == 'Z' ) && (g_hostdb.m_myHost->m_type & HT_PROXY ) ) {
		// . now record the request in our accounting system.
		// . we assume the reply is error-free at this point
		// . it might be for a GET /seo too, not just stC->m_isQuery
		addAccessPoint ( stC , nowms , 200 ); // httpStatus
		//now should be able to print
		HttpRequest r;
		r.set(stC->m_s->m_readBuf, stC->m_s->m_readOffset, stC->m_s);
		// log the request
		printRequest(stC->m_s, &r, took, NULL,0);//content,contentLen);
		// add stat for stats graph
		if ( stC->m_isQuery ) {
			g_stats.logAvgQueryTime(stC->m_startTime);
			// i dont check if query is raw or not
			int32_t color = 0x00b58869;
			if ( stC->m_raw ) color = 0x00753d30;
			int64_t nowms = gettimeofdayInMilliseconds();
			// . add the stat
			// . use brown for the stat
			g_stats.addStat_r ( 0               ,
					    stC->m_startTime , 
					    nowms ,
					    //"query",
					    color ,
					    STAT_QUERY );
			// add to statsdb as well
			g_statsdb.addStat ( 0 , // niceness
					    "query" ,
					    stC->m_startTime ,
					    nowms            ,
					    stC->m_numQueryTerms );
			g_stats.m_numSuccess++;
		}
		// forward it to the qcproxy. true -> do not re-compress!
		//g_httpServer.sendReply2(NULL,0,reply,size,stC->m_s,true);
		// let tcp server free it when done
		g_httpServer.m_tcp.sendMsg ( stC->m_s ,
					     reply ,
					     size ,
					     size ,
					     size ,
					     NULL ,
					     NULL );
		// free mem
		freeStateControl(stC);
		return;
	}
	//char *reply = s->m_readBuf;
	//int32_t size = s->m_readOffset;
	HttpMime mime;
	// re-store original mime from uncompressed mime
	mime.set ( reply, size, NULL);
	int32_t httpStatus = mime.getHttpStatus();
	if ( httpStatus != 200 )
		g_msg = " (error: unknown.)";

	// . now record the request in our accounting system.
	// . we assume the reply is error-free at this point
	// . it might be for a GET /seo too, not just stC->m_isQuery
	// . this should update the account balance
	addAccessPoint ( stC , nowms , httpStatus );


	if ( stC->m_isQuery && httpStatus == 200 ){
		g_stats.logAvgQueryTime(stC->m_startTime);
		// i dont check if query is raw or not
		int32_t color = 0x00b58869;
		if ( stC->m_raw ) color = 0x00753d30;
		int64_t nowms = gettimeofdayInMilliseconds();
		// . add the stat
		// . use brown for the stat
		g_stats.addStat_r ( 0               ,
				    stC->m_startTime , 
				    nowms ,
				    //"query",
				    color ,
				    STAT_QUERY );
		// add to statsdb as well
		g_statsdb.addStat ( 0 , // niceness
				    "query" ,
				    stC->m_startTime ,
				    nowms            ,
				    stC->m_numQueryTerms );
		g_stats.m_numSuccess++;
		/*m_numSuccess++;
		m_totalQueryTime += took;
		if ( m_numSuccess % 20 == 0 ){
			int32_t avgTime = m_totalQueryTime / m_numSuccess;
			log ( LOG_INFO,"proxy: did the last %" INT32 " successful "
			      "queries in %" UINT64 " ms, latency %" INT32 " ms. Total "
			      "queries %" INT32 "",
			      m_numSuccess,m_totalQueryTime,avgTime,
			      m_numQueries );
		}
		//we just log the last 2000 successful queries
		if ( m_numSuccess > 2000 ){
			m_numSuccess = 0;
			m_totalQueryTime = 0;
			m_numQueries = 0;
			}*/
	}
	else if ( stC->m_isQuery && httpStatus != 200 )
		g_stats.m_numFails++;
	
	//now should be able to print
	HttpRequest r;
	r.set(stC->m_s->m_readBuf, stC->m_s->m_readOffset, stC->m_s);

	/*	if ( g_conf.m_logQueryTimes )
		logf( LOG_TIMING,"query: proxy: got back %" INT32 " bytes page "
		      "with status %" INT32 " for request %s in %" INT32 " ms",
		      size, stC->m_hash, httpStatus, r.getRequest(), took  );*/

	//char *content = s->m_readBuf + mime.getMimeLen();
	char *content    = reply + mime.getMimeLen();
	int32_t  contentLen = size  - mime.getMimeLen();

	printRequest(stC->m_s, &r, took, content,contentLen);

	/*
	char charset[1024];
	gbmemcpy ( charset, mime.getCharset(), mime.getCharsetLen() );
	charset[mime.getCharsetLen()] = '\0';

	char *contentType;
	switch ( mime.getContentType() ){
	case CT_UNKNOWN : contentType = " ";
		break;
	case CT_HTML : contentType = "text/html";
		break;
	case CT_TEXT : contentType = "text/plain";
		break;
	case CT_XML  : contentType = "text/xml";
		break;
	case CT_PDF  : contentType = "application/pdf";
		break;
	case CT_DOC  : contentType = "application/msword";
		break;
	case CT_XLS  : contentType = "application/vnd.ms-excel";
		break;
	case CT_PPT  : contentType = "application/mspowerpoint";
		break;
	case CT_PS   : contentType = "application/postscript";
		break;
	}
	*/

	/*
	g_httpServer.sendDynamicPage ( stC->m_s      , 
				       content       ,
				       contentLen    , 
				       25            , // cachetime in secs
				       // pick up key changes
				       // this was 0 before
				       false      , // POSTREply? 
				       contentType, // content type
				       -1         , // http status -1->200
				       // CRAP WHAT ABOUT COOKIE IN MIME???
				       NULL       , // cookie
				       charset    );
	*/


	/*
	g_httpServer.sendReply2 ( mime.getMime() ,
				  mime.getMimeLen() ,
				  content ,
				  contentLen ,
				  stC->m_s ,
				  false );
	*/

	// . add the login bar to all pages we send back
	// . we could also use to automatically update copyright years 
	//   and add any common elements to every page...
	// . make a new reply to send back...
	// . it may free the old "reply" or it may set newReply=reply...
	int32_t newReplySize = size;
	char *newReply = reply;

	// make sure it is HTTP/1.0 not HTTP/1.1
	if ( reply[0] == 'H' &&
	     reply[1] == 'T' &&
	     reply[2] == 'T' &&
	     reply[3] == 'P' &&
	     reply[4] == '/' &&
	     reply[5] == '1' &&
	     reply[6] == '.' &&
	     reply[7] == '1' )
		reply[7] = '0';
	     

	// do not print login bars in the xml!! do not print for ixquick
	// which gets results in html...
	if ( ! stC->m_raw && ! stC->m_ch && ! stC->m_isEventGuru )
		newReply = storeLoginBar ( reply , 
					   size ,  // transmit size
					   size , // allocsize
					   // so we can quickly jump over the
					   // mime in the reply...
					   mime.getMimeLen() ,
					   //stC->m_s->m_userId32 ,
					   //stC->m_userId32,
					   &newReplySize ,
					   &stC->m_hr );

	// . try this one instead
	// . returns false if blocked
	TcpServer *tcp = &g_httpServer.m_tcp;
	// are we using ssl?
	if ( stC->m_s->m_ssl ) tcp = &g_httpServer.m_ssltcp;
	tcp->sendMsg ( stC->m_s ,
		       newReply , 
		       newReplySize ,
		       newReplySize ,
		       newReplySize ,
		       NULL,
		       NULL);
	
	// do not let udpslot free that we are sending it off
	//slot->m_readBuf = NULL;
	
	freeStateControl(stC);
}


void freeStateControl ( StateControl *stC ){
	if ( ! stC ) return;

	if ( ! g_isYippy && stC->m_hostId >= 0 ) {
		g_proxy.m_numOutstanding[stC->m_hostId]--;
		uncountStripe ( stC );
	}

	// free the reply buffer
	if ( stC->m_slot && ! g_isYippy ) {
		// save reply so we can free it when this state is freed
		char *reply = stC->m_slotReadBuf;
		int32_t  size  = stC->m_slotReadBufMaxSize;
		if ( reply ) mfree ( reply , size , "proxy" );
		// do not double free!
		stC->m_slotReadBuf = NULL;
	}

	mdelete(stC,sizeof(StateControl),"Proxy");
	delete(stC);
}

void uncountStripe ( StateControl *stC ) {
	// if stripe is -1, it was not a search query request
	int32_t stripe = stC->m_stripe;
	if ( stripe < 0 ) return;
	// a more refined load balancing act
	g_proxy.m_termsOutOnStripe[stripe] -= stC->m_numQueryTerms;
	// dec this too
	g_proxy.m_queriesOutOnStripe[stripe]--;
}


// . now do stripe balancing
// . this prevents one machine from receiving all the Msg39 requests while its
//   twin gets none
Host *Proxy::pickBestHost( StateControl *stC ) {

	// sanity check, for m_stripeLastHostId array size, which is only 8 now
	int32_t numStripes = g_hostdb.getNumStripes();
	if ( numStripes > MAX_STRIPES ) { char*xx=NULL;*xx=0; }

	// see which stripes have non-dead hosts!
	char stripeDead[MAX_STRIPES];
	bool allDead = true;
	memset ( stripeDead , 1 , MAX_STRIPES );
	int32_t nh = g_hostdb.getNumHosts();
	for ( int32_t i = 0 ; i < nh ; i++ ) {
		Host *h = g_hostdb.getHost(i);
		if ( g_hostdb.isDead ( h ) ) continue;
		// hey, we are not all dead!
		allDead = false;
		// clear it
		stripeDead [ h->m_stripe ] = 0;
	}

	// . get the stripe with the least # of outstanding query terms
	// . in the event of a tie, give each stripe an equal shot so we
	//   balance out the wear-n-tear on the drives.
	//int32_t mini = m_nextStripe;
	//int32_t min  = m_termsOutOnStripe[mini];
	//bool tied = false;

	// start at this stripe
	int32_t ns = m_nextStripe;

	int32_t min ;
	int32_t minns = -1;
	for ( int32_t i = 0 ; i < numStripes ; i++ ) {
		// get stripe number
		if ( ++ns >= numStripes ) ns = 0;
		// skip if whole stripe is dead, and other stripes are not dead
		if ( stripeDead[ns] && ! allDead ) continue;
		// how loaded is this stripe?
		int32_t termsOut = m_termsOutOnStripe[ns];
		// skip if his load is tied or higher than our current winner
		if ( minns != -1 && termsOut >= min ) continue;
		// got a new winner
		minns = ns;
		min   = termsOut;
	}

	// sanity check
	if ( minns == -1 ) { char *xx=NULL;*xx=0; }

	// rotate the preferred next stripe
	if ( ++m_nextStripe >= numStripes ) m_nextStripe = 0;

	// find the next host in line for stripe #minns
	int32_t bestHostId = m_stripeLastHostId[minns];
	// count iterations
	//int32_t count = 0;
 loop:
	// inc it, wrap it
	if ( ++bestHostId >= g_hostdb.getNumHosts() ) bestHostId = 0;
	// do not do infinite loops
	//if ( count++ >= g_hostdb.getNumHosts() ) {
	//	log ("proxy: all hosts are dead. critical error.");
	//	g_errno = EBADENGINEER;
	//	g_httpServer.sendErrorReply(stC->m_s,500,mstrerror(g_errno));
	//	freeStateControl(stC);
	//	return;
	//}
	// skip if dead
	if ( g_hostdb.isDead( bestHostId ) && ! allDead ) goto loop;
	// get the host
	Host *h = g_hostdb.getHost ( bestHostId );
	// saity check
	if ( h->m_isProxy ) { char *xx=NULL;*xx=0; }
	// advance until it is from the least-loaded stripe
	if ( h->m_stripe != minns ) goto loop;


	// add query terms to it for load balancing purposes
	m_termsOutOnStripe[minns] += stC->m_numQueryTerms;
	// inc this too
	m_queriesOutOnStripe[minns]++;
	// save it
	m_stripeLastHostId[minns] = bestHostId;

	// store ino into state so we can reduce the count when we get a reply
	stC->m_stripe        = minns;
	//stC->m_numQueryTerms = numQueryTerms;

	// return it
	return g_hostdb.getHost( bestHostId );
}

/*
Host *Proxy::pickBestHost( ) {
	int32_t  bestHost = m_lastHost;
	bestHost++;
	if ( bestHost >= g_hostdb.getNumHosts() )
		bestHost = 0;
	//check if the host is dead. if dead cycle through for a live host
	//Also check if it has got outstanding requests
	int32_t numTried = 0;
	while ((m_numOutstanding[bestHost]>0 || g_hostdb.isDead(bestHost)) &&
	       numTried < 10 ){
		bestHost++;
		if ( bestHost >= g_hostdb.getNumHosts() )
			bestHost = 0;
		numTried++;
		//This could go into an infinite loop if no hosts are on
		//so adding numTried here too. Of course if no hosts are
		//on, we're gonna lose the request
		while ( numTried < 10 && g_hostdb.isDead( bestHost ) ){
			bestHost++;
			if ( bestHost >= g_hostdb.getNumHosts() )
				bestHost = 0;
			numTried++;
		}
	}
	m_lastHost = bestHost;
	m_numOutstanding[bestHost]++;
	Host *h = g_hostdb.getHost( bestHost );
	return h;
}
*/

void Proxy::printRequest(TcpSocket *s, HttpRequest *r, 
			 uint64_t took ,
			 char *content,
			 int32_t contentLen ) {
	//LOG THE REQUEST
	/*
	// . if it is a post request, log the posted data, too
	char cgi[20058];
	cgi[0] = '\0';
	if ( r->isPOSTRequest() ) {
		int32_t  plen = r->m_cgiBufLen;
		if (  plen >= 20052 ) plen = 20052;
		char *pp1 = cgi ;
		char *pp2 = r->m_cgiBuf;
		// . when parsing cgi parms, HttpRequest converts the 
		//   &'s to \0's so it can avoid having to malloc a 
		//   separate m_cgiBuf
		// . now it also converts ='s to 0's, so flip flop back
		//   and forth
		char dd = '=';
		for ( int32_t i = 0 ; i < plen ; i++ , pp1++, pp2++ ) {
			if ( *pp2 == '\0' ) { 
				*pp1 = dd;
				if ( dd == '=' ) dd = '&';
				else             dd = '=';
				continue;
			}
			if ( *pp2 == ' ' ) *pp1 = '+';
			else               *pp1 = *pp2;
		}
		if ( r->m_cgiBufLen >= 20052 ) {
			pp1[0]='.'; pp1[1]='.'; pp1[2]='.'; pp1 += 3; }
		*pp1 = '\0';
	}
	*/

	// get time format: 7/23/1971 10:45:32
	time_t tt = getTimeLocal();
	struct tm *timeStruct = localtime ( &tt );
	char bufTime[64];
	strftime ( bufTime , 63 , "%b %d %T", timeStruct);
	//char *ref = r->getReferer ();

	// if autobanned and we should not log, return now
	if (g_msg&&!g_conf.m_logAutobannedQueries && strstr(g_msg,"autoban")){ 
		g_msg = ""; 
		return; 
	}

	/*
	// fix cookie for logging
	char cbuf[5000];
	char *pc  = r->m_cookiePtr;
	int32_t  pclen = r->m_cookieLen;
	if ( pclen >= 4998 ) pclen = 4998;
	char *pcend = r->m_cookiePtr + pclen;
	char *dst = cbuf;
	for ( ; pc < pcend ; pc++ ) {
		*dst = *pc;
		if ( ! *pc ) *dst = ';';
		dst++;
	}
	*dst = '\0';
	
	if ( ! cgi[0] ) 
		logf (LOG_INFO,"http: %s %s %s %s cookie=\"%s\" %s %s",
		      bufTime,iptoa(s->m_ip),r->getRequest(),
		      ref,cbuf,r->getUserAgent(),g_msg);
	else 
		logf (LOG_INFO,"http: %s %s %s %s %s cookie=\"%s\" %s %s",
		      bufTime,iptoa(s->m_ip),r->getRequest(),
		      cgi,ref,cbuf,r->getUserAgent(),g_msg);
	*/

	char *req = s->m_readBuf;
	//int32_t  reqLen = s->m_readOffset;

	logf (LOG_INFO,"http: %s %s %s %s",
	      bufTime,iptoa(s->m_ip),req,//r->getRequest(),
	      g_msg);


	//reset g_msg
	g_msg = "";
	if ( (int32_t)took < g_conf.m_logQueryTimeThreshold ) return;

	if ( ! g_conf.m_logQueryReply || ! content || contentLen <= 0 ) {
		logf (LOG_INFO,"http: Took %" UINT64 " ms "
		      "(len=%" INT32 " bytes) "
		      "for request %s",
		      took, contentLen, r->getRequest());
		return;
	}


	// copy into buf
	char *p = (char *)mmalloc ( contentLen+1,"proxycont");
	if ( ! p ) return;

	for ( int32_t i = 0 ; i < contentLen ; i++ ) {
		if ( content[i] && ! is_binary_a(content[i]) ) { 
			p[i]=content[i]; continue; }
		// fix 0's and binary stuff
		p[i]='?';
	}
	// null terminate
	p[contentLen]=0;

	logf (LOG_INFO,"http: Took %" UINT64 " ms "
	      "(len=%" INT32 " bytes) "
	      "for request %s reply=%s",
	      took, contentLen, r->getRequest(),content);

	mfree ( p , contentLen+1, "proxycont");
}

// for yippy only!
void gotTcpReplyWrapper ( void *state , TcpSocket *s ) {

	class StateControl *stC = (StateControl *)state;

	// i guess s->m_sendBuf is in StateControl::m_sb safebuf so 
	// avoid double free. it directly called TcpServer::sendMsg which
	// does not do a copy like httpserver
	s->m_sendBuf = NULL;

	if ( stC->m_isYippySearch ) 
		s_yippySearchesOut--;
	
	// get the reply from the teaski machine
	char *reply = s->m_readBuf;
	int32_t replySize = s->m_readOffset;
	//int64_t took = gettimeofdayInMilliseconds() - stC->m_start;

	if ( ! reply ) {
		g_errno = EBADREPLY;
		replySize = 0;
		char ipbuf[64];
		sprintf(ipbuf,"%s",iptoa(s->m_ip));
		char ipbuf2[64];
		sprintf(ipbuf2,"%s",iptoa(stC->m_s->m_ip));
		char *creq = "";
		if ( stC->m_s &&
		     stC->m_s->m_readBuf &&
		     stC->m_s->m_readOffset )
			creq = stC->m_s->m_readBuf;
		log("proxy: got a zero length reply from %s. err=%s "
		    "client=%s clientreq=%s",
		    ipbuf,
		    mstrerror(g_errno),
		    ipbuf2,
		    creq );
		// debug log debug
		//log("proxy: returning reply to %s replysize=%" INT32 " "
		//    "reqnum=%" INT32 " (took=%" INT64 "ms)",
		//    iptoa(stC->m_s->m_ip),
		//    replySize,stC->m_reqNum,took);
		g_httpServer.sendErrorReply(stC->m_s,500,mstrerror(g_errno));
		freeStateControl(stC);
		return;
	}


	if ( g_errno ) {
		log("proxy: got error in reply from %s. err=%s",
		    iptoa(s->m_ip),mstrerror(g_errno));
		// debug log debug
		//log("proxy: returning reply to %s replysize=%" INT32 " "
		//    "reqnum=%" INT32 " (took=%" INT64 "ms) (err=%s)",
		//    iptoa(stC->m_s->m_ip),
		//    replySize,stC->m_reqNum,took,mstrerror(g_errno));
		g_httpServer.sendErrorReply(stC->m_s,500,mstrerror(g_errno));
		freeStateControl(stC);
		return;
	}

	/*
	// debug log debug
	int32_t max;
	char c;
	if ( reply ) {
		max = 1500;
		if ( replySize < max ) max = replySize;
		max--;
		if ( max < 0 ) max = 0;
		c = reply[max];
		reply[max] = 0;
	}

	//log("proxy: returning reply back to client. size=%" INT32 " reply=%s",
	//    replySize,reply);

	log("proxy: returning  reqNum=%" INT32 " for  %s replysize=%" INT32 " "
	    "(took=%" INT64 "ms)",
	    stC->m_reqNum,
	    iptoa(stC->m_s->m_ip),
	    replySize,took);

	if ( reply )
		reply[max] = c;
	*/

	// . forward the teaski reply back to client's browser
	// . it should free the reply buf when done
	g_httpServer.sendReply2(NULL, // mime
				0, // mimelen
				reply, // content
				replySize, // contentlen
				stC->m_s,
				true); // already compressed?

	freeStateControl(stC);
}

///////////////////////////
//
// USER ACCOUNTING FUNCTIONS
//
///////////////////////////


// every day has a summary rec
class SummaryRec {
 public:
	int32_t      m_userId32;
	char      m_accessType;
	// how many times this access type was done:
	int32_t      m_numAccesses; 
	// how much user was charged for all these accesses:
	float     m_totalCost;
	// how long all replies took in milliseconds:
	int64_t m_totalProcessTime;
	char      m_month;
	char      m_day;
	int16_t     m_year;
};

#define DRF_DEPOSIT 1
#define DRF_WITHDRAW 2
#define DRF_WITHDRAW_FEE 3

// every time a deposit or withdrawal is made we have one of these
class DepositRec {
public:
	int32_t      m_userId32;
	float     m_depositAmount;
	int32_t      m_depositDate;
	// . use transactionid for doing CREDITs back to user
	// . i've seen it > 5B so use a int64_t
	int64_t m_authorizeNetTransactionId;
	int32_t      m_flags;
};


// returns NULL and sets g_errno on error
UserInfo *Proxy::getUserInfoForFeedAccess ( HttpRequest *hr ) {
	
	// assume no error
	g_errno = 0;

	//char *user = hr->getString("user",NULL);
	// we also store the username along with session id
	//if ( ! user ) user = r->getStringFromCookie("user",NULL);
	int32_t userId32 = hr->getLong("userid",0);

	char *code = hr->getString("code",NULL);

	// if no userid or code given, just rely on autoban then,
	// so do not set g_errno, just return NULL
	if ( ! userId32 && ! code ) return NULL;

	// allow others to just specify their codes to get
	// results and not the userid
	if ( ! userId32 && code ) {
		UserInfo *uis = (UserInfo *)m_userInfoBuf.getBufStart();
		int32_t ni = m_userInfoBuf.length() / sizeof(UserInfo) ;
		for ( int32_t i = 0 ; i < ni && i < 5 ; i++ ) {
			// int16_tcut
			UserInfo *ui = &uis[i];
			// must be an "old" user like others
			if ( ! (ui->m_flags & (UIF_OLDUSER|UIF_ADMIN))) 
				continue;
			// then check for code match
			if ( strcmp(code,ui->m_xmlFeedCode) ) continue;
			// matched!
			return ui;
		}
		// they gave a code, amd no userid, and their code was
		// unmatched... that's perm denied
		g_errno = EPERMDENIED;
		return NULL;
	}

	UserInfo *ui = getUserInfoFromId ( userId32 );
	
	// if user name has no record, that's permission denied
	if ( ! ui ) {
		log("proxy: user not found for userid=%" INT32 "",userId32);
		g_errno = EPERMDENIED;
		return NULL;
	}

	// codes must match, too!
	if ( ! code || strcmp(code,ui->m_xmlFeedCode) ) {
		g_errno = EPERMDENIED;
		log("proxy: permission denied for userid=%" INT32 " code=%s",
		    userId32,code);
		return NULL;
	}

	return ui;
}

int32_t Proxy::getAccessType ( HttpRequest *hr ) {

	char *path    = hr->getPath();
	int32_t  pathLen = hr->getPathLen();

	char c = path[pathLen];
	path[pathLen] = '\0';

	int32_t accessType = 0;

	if ( strncmp(path,"/addurl",7) == 0 ) {
		// assume old
		accessType = AT_ADDURL;
	}

	bool isSearch = false;

	// old access method (in Xml.cpp/Pages.cpp)
	if ( strncmp(path,"/cgi/0.cgi?",11) == 0 )
		// assume old
		isSearch = true;

	// another form in Xml.cpp/Pages.cpp
	if ( strncmp(path,"/index.php?",11) == 0 )
		// assume old
		isSearch = true;

	// searching the old index?
	if ( strncmp(path,"/search?",8) == 0 ) 
		isSearch = true;

	// default will be fast search
	if ( isSearch ) {
		// assume fast search
		accessType = AT_SEARCHFEED_OLD;
		// new index search?
		if ( hr->getLong("precise",0)==1) 
			accessType = AT_SEARCHFEED_NEW;
	}

	// seo page?
	if ( strncmp(path,"/seo?",5) == 0 ) {
		char *page = hr->getString("page",NULL);
		if ( page && strcmp(page,"matchingqueries") == 0 )
			accessType = AT_MATCHING_QUERIES;
		if ( page && strcmp(page,"competitorpages") == 0 )
			accessType = AT_COMPETITOR_PAGES;
		if ( page && strcmp(page,"competitorbacklinks") == 0 )
			accessType = AT_COMPETITOR_BACKLINKS;
		if ( page && strcmp(page,"relatedqueries") == 0 )
			accessType = AT_RELATED_QUERIES;
		if ( page && strcmp(page,"missingterms") == 0 )
			accessType = AT_MISSING_TERMS;
	}

	// revert
	path[pathLen] = c;

	return accessType;
}

// in dollars!
float Proxy::getPrice ( int32_t accessType ) {

	if ( accessType == 0 )
		return 0.0;

	if ( accessType == AT_SEARCHFEED_OLD )
		// $1 CPM
		return 1.00 / 1000.0;

	if ( accessType == AT_SEARCHFEED_NEW )
		// $2.50 CPM
		return 2.50 / 1000.0;

	if ( accessType == AT_MATCHING_QUERIES )
		return 9.99; // $10

	if ( accessType == AT_COMPETITOR_PAGES )
		return 9.99; // $10

	if ( accessType == AT_COMPETITOR_BACKLINKS )
		return 9.99; // $10

	if ( accessType == AT_RELATED_QUERIES )
		return 14.99; // $15

	if ( accessType == AT_MISSING_TERMS )
		return 19.99; // $20

	if ( accessType == AT_ADDURL )
		return 4.99; // $10

	char *xx=NULL;*xx=0;
	return 0.0;
}


// . return false with g_errno set on error, true otherwise
// . when a reply has been generated we call this to accumulate stats
// . we update the SummaryRec and UserInfo record, as well as adding a
//   record to statsdb!
// . make the proxy run on ssds for extra reliability
// . accesses are also logged by back-end machines in case we need
//   to rebuild access points, however, we'd lose CC info etc.
// . so backup userdb periodically maybe using cron to gk37 or something.
// . now they must have "&user=username&code=sdfsdfdfs"
// . processTime is in milliseconds (ms)
bool Proxy::addAccessPoint ( StateControl *stC , 
			     int64_t nowms ,
			     int32_t httpStatus ) {

	HttpRequest *hr = &stC->m_hr;

	//UserInfo *ui = getUserInfoForFeedAccess ( hr );

	// it might be an XML feed access OR an add url or seo tool access
	UserInfo *ui = getUserInfoFromId ( stC->m_userId32 );

	// ALWAYS CALL THIS!
	if ( ui ) ui->m_pending -= stC->m_price;

	// wtf? if no code or no user or incorrect code, it will return NULL
	// with g_errno set on error
	if ( ! ui && ! g_errno ) return true;
	// error? wtf...
	if ( g_errno ) return false;

	// error getting results? do not charge for it then...
	if ( httpStatus != 200 ) {
		log("proxy: got http reply error status=%" INT32 "",httpStatus);
		return true;
	}

	char accessType = getAccessType ( hr );

	// no cost?
	if ( accessType == 0 ) return true;

	return addAccessPoint2 ( ui , 
				 accessType , 
				 nowms ,
				 stC->m_startTime );
}

bool Proxy::addAccessPoint2 ( UserInfo *ui , 
			      char accessType ,
			      int64_t nowms ,
			      int64_t startTime ) {


	if ( ! ui ) {
		log("proxy: addaccesspoint2 ui was NULL!" );
		return false;
	}

	float price = getPrice ( accessType );

	// get the summary rec for this day and user...
	SummaryRec *sr = getSummaryRec ( ui->m_userId32 , accessType );
	// wtf? error!
	if ( ! sr ) return false;

	// the account balance of course
	ui->m_accountBalance -= price;

	int64_t processTime = nowms - startTime; // stC->m_startTime;

	// increment counters, all else should be fixed by getSummaryRec()
	sr->m_numAccesses++;
	sr->m_totalProcessTime += processTime;
	sr->m_totalCost        += price;

	//
	// . a new statsdb key/data type i guess
	// . but now let's add 
	//
	g_statsdb.addStat ( 0 , // niceness
			    "query" ,
			    startTime ,
			    nowms            ,
			    processTime , // value
			    // set parmHash to 1 so we add oldVal/newVal
			    0 ,
			    0 , // oldval
			    0 ,// newval
			    // this is unique for each user! and must NOT
			    // be recycled in the event you delete a user...
			    ui->m_userId32 );


	return true;
}


// . use Proxy::m_sumBuf to hold the summary recs
// . one summaryrec per day/user/accesstype tuple so they can see how many 
//   queries they did of each type per day
SummaryRec *Proxy::getSummaryRec ( int32_t userId32 , char accessType ) {

	// . get epoch time utc. make sure proxy time is always in sync.
	// . add that to Process.cpp to check to make sure time sync server
	//   process is running!
	int32_t now = getTimeLocal();

	static int32_t s_nextDay   = -1;
	static int32_t s_thisDay   =  0;
	static int32_t s_thisMonth =  0;
	static int32_t s_thisYear  =  0;

	if ( s_nextDay == -1 || now > s_nextDay ) {
		struct tm *timeStruct = gmtime ( (time_t *)&now );
		s_thisDay   = timeStruct->tm_mday;
		s_thisMonth = timeStruct->tm_mon+1; // 0..11 so make it 1..12
		s_thisYear  = timeStruct->tm_year + 1900;
		int32_t elapsed = timeStruct->tm_min * 60 + timeStruct->tm_sec;
		int32_t left = 86400 - elapsed;
		s_nextDay   = now + left;
	}

	// make a unique key for this summary rec, one per day per user
	uint64_t h64 = (uint32_t)userId32;
	uint32_t t = s_thisYear;
	t <<= 16;
	t |= s_thisMonth;
	t <<= 8;
	t |= s_thisDay;
	t <<= 8;
	t |= accessType;
	h64 <<= 32;
	h64 |= t;

	// use hashtable of summary rec ptrs
	int32_t *sumOffPtr;
	sumOffPtr = (int32_t *)m_srht.getValue ( &h64 );
	//SummaryRec **srp = (SummaryRec **)m_srht.getValue ( &h64 );

	// if there, return it!
	//if ( srp  ) return *srp;
	if ( sumOffPtr ) {
		SummaryRec *sr;
		sr = (SummaryRec *)(m_sumBuf.getBufStart() + *sumOffPtr);
		return sr;
	}


	//
	// otherwise, we gotta make one!
	//

	// g_errno should be set if this fails!
	if ( ! m_sumBuf.reserve ( sizeof(SummaryRec) ) )
		return NULL;

	// ref it
	SummaryRec *sr = (SummaryRec *)m_sumBuf.getBuf();

	// init it
	memset(sr,0,sizeof(SummaryRec));
	sr->m_accessType = accessType;
	sr->m_year  = s_thisYear;
	sr->m_month = s_thisMonth;
	sr->m_day   = s_thisDay;
	sr->m_totalCost = 0;
	sr->m_totalProcessTime = 0;
	sr->m_userId32 = userId32;

	// advance it
	m_sumBuf.incrementLength ( sizeof(SummaryRec) );

	int32_t sumOff = (int32_t)(((char *)sr) - m_sumBuf.getBufStart());

	// hash it
	if ( ! m_srht.addKey ( &h64 , &sumOff ) )
		return NULL;
				
	return sr;
}


//////////////////
//
// PRINT THE USER INFO
//
//////////////////


class StateUser {
public:
	//int32_t m_errno;
	TcpSocket *m_socket;
	//Msg0 m_msg0;
	//Msg4 m_msg4;
	int64_t m_sessionId64;
	int32_t m_userId32;
	//HttpRequest m_hr;
	SafeBuf m_sb;
	SafeBuf m_sb2;
	SafeBuf m_authNetMsg;
	bool m_transactionSuccessful;
	bool m_attemptedTransaction;
	float m_deposit; // dollar amount
	float m_refund;  // dollar amount (95%)
	float m_refundFee; // 5%
	int64_t m_refundTransId;
	//bool m_doWithdraw;
	//float m_deposit;
	// authorize.net's reply
	TcpSocket *m_docSocket;
	HttpRequest m_hr;
	int32_t m_submittingNewUser;
	// is this really the admin logged in as another user?
	bool m_isMasterAdmin;
	int64_t m_adminSessId;
	int32_t m_adminId;

	// for holding error msg and pointing m_depositErr to it
	SafeBuf m_tmpBuf1;

	char m_tmpBuf2[20];

	//
	/// set these from get request
	//
	char *m_user;
	char *m_pwd;
	char *m_pwd2;
	char *m_ccNum;
	char *m_ccType;
	char *m_cvv; 
	char *m_exp;
	char *m_fc;
	//float m_deposit;
	char *m_email;
	char *m_phone;
	char  m_terms;

	// european stuff
	char *m_firstName;
	char *m_lastName;
	char *m_city;
	char *m_state;
	char *m_country;
	char *m_zip;
	char *m_address;

	char *m_userError;
	char *m_pwdError;
	char *m_pwd2Error;
	char *m_ccNumError;
	char *m_ccTypeError;
	char *m_cvvError; 
	char *m_expError;
	char *m_fcError;
	char *m_depositError;
	char *m_refundError;
	char *m_emailError;
	char *m_phoneError;
	char *m_termsError;

	//int32_t m_transactionId;

	int32_t  m_error;
};

char *getAccessTypeString ( int32_t at ) {
	if ( at == AT_SEARCHFEED_OLD ) 
		return "fast search feed";
	if ( at == AT_SEARCHFEED_NEW ) 
		return "precise search feed";
	if ( at == AT_COMPETITOR_BACKLINKS ) 
		return "competitor backlinks tool";
	if ( at == AT_RELATED_QUERIES ) 
		return "related queries tool";
	if ( at == AT_MISSING_TERMS )
		return "missing terms tool";
	if ( at == AT_ADDURL )
		return "add url tool";
	return "unknown access type";
}

void gotGifWrapper ( void *su ) {
	g_proxy.gotGif ( (StateUser *)su );
}

// userId32 is always < 0x7fffffff to avoid sign bit issues
UserInfo *Proxy::getUserInfoFromId ( int32_t userId32 ) {
	UserInfo *uis = (UserInfo *)m_userInfoBuf.getBufStart();
	int32_t ni = m_userInfoBuf.length() / sizeof(UserInfo) ;
	// we added 1 to the userid32 to avoid using 0
	if ( userId32-1 >= ni ) return NULL;
	if ( userId32-1 < 0 ) return NULL;
	return &uis[userId32-1];
}

UserInfo *Proxy::getLoggedInUserInfo2 ( HttpRequest *hr , 
					TcpSocket *socket,
					SafeBuf *errmsg ) {
	// are they logging in for the first time, or carrying a sessionid
	// in their cookie?
	char *login = hr->getString("login",NULL);
	char *password = hr->getString("password",NULL);

	// userid32 is used with sessionid
	int64_t sessionId64 = hr->getLongLongFromCookie("sessionid",0LL);
	int32_t userId32 = hr->getLongFromCookie("userid",0);

	// if supplying "user", then they must also supply "pwd"!
	if ( ! password ) login = NULL;

	// print login page?
	if ( ! login && ! sessionId64 ) return NULL;

	// let this override in case sessionid expires
	if ( login ) sessionId64 = 0LL;

	int32_t now = getTimeLocal();

	// try to match user password or session id
	UserInfo *uis = (UserInfo *)m_userInfoBuf.getBufStart();
	int32_t ni = m_userInfoBuf.length() / sizeof(UserInfo) ;
	UserInfo *ui;
	int32_t i; for ( i = 0 ; i < ni ; i++ ) {
		// int16_tcut
		ui = &uis[i];
		// login with existing session id?
		if ( sessionId64 ) {
			// check it
			if ( ui->m_lastSessionId64 != sessionId64 ) continue;
			// userid32 must match too!
			if ( ui->m_userId32 != userId32 ) continue;
			// check time though. give them 10 minutes...
			if ( now - ui->m_lastActionTime > 10*60 ) {
				if ( errmsg )
					errmsg->safePrintf("Session expired. "
							   "Please "
							   "re-login.");
				sessionId64 = 0;
				return NULL;
			}
			// update timestamp so it is 10 minutes since time
			// of LAST action...
			ui->m_lastActionTime = getTimeLocal();
			return ui;
			//break;
		}
		// . login? "user" must be non-null, and "pwd" too
		// . skip if no username match
		if ( strcmp ( ui->m_login , login ) ) continue;
		// if pwd does not match, stop! error!
		if ( strcmp ( ui->m_password, password ) ) break;
		// ok, i guess password matched, set a session id
		// assign a sessionid now. make it always positive!
		int32_t num1 = rand() % 0x7fffffff;
		int32_t num2 = rand() % 0x7fffffff;
		uint64_t newSessionId64 = num1;
		newSessionId64 <<= 32;
		newSessionId64 |= num2;
		// ensure not 0
		if ( newSessionId64 == 0 ) newSessionId64 = 1;
		// add this session if to rec and re-add
		ui->m_lastSessionId64 = newSessionId64;
		ui->m_lastActionTime = getTimeLocal();
		ui->m_lastLoginIP   = socket->m_ip;
		// save session id!
		g_proxy.saveUserBufs();
		return ui;
	}

	if ( errmsg )
		errmsg->safePrintf("Login error. Incorrect username "
				   "or password.");

	return NULL;
}

// call this once at start of functions in sendPageAccount()
UserInfo *Proxy::getLoggedInUserInfo ( StateUser *su , SafeBuf *errmsg ) {

	HttpRequest *hr = &su->m_hr;
	TcpSocket *socket = su->m_socket;

	// reset shit
	su->m_userId32 = -1;
	su->m_sessionId64 = 0;
	su->m_isMasterAdmin = false;
	su->m_adminSessId = 0LL;
	su->m_adminId = 0;

	UserInfo *ui = getLoggedInUserInfo2 ( hr , socket , errmsg );

	if ( ! ui ) return NULL;

	// ok, it's good!
	su->m_userId32 = ui->m_userId32;
	su->m_sessionId64 = ui->m_lastSessionId64;

	// are they really the admin, logged in as a user?
	int64_t asi = hr->getLongLongFromCookie("adminsessid",0LL);
	if ( ! asi ) return ui;

	// admin IP be local ip for security!
	if ( ! hr->m_isLocal ) return ui;

	// see if it matches
	UserInfo *uis = (UserInfo *)m_userInfoBuf.getBufStart();
	int32_t ni = m_userInfoBuf.length() / sizeof(UserInfo) ;
	int32_t i; for ( i = 0 ; i < ni ; i++ ) {
		// int16_tcut
		UserInfo *ui = &uis[i];
		// skip if not admin
		if ( ! ( ui->m_flags & UIF_ADMIN ) ) continue;
		// check it
		if ( ui->m_lastSessionId64 != asi ) continue;
		// got a match
		su->m_isMasterAdmin = true;
		// save the underlying admin user info
		su->m_adminSessId = asi;
		su->m_adminId = ui->m_userId32;
	}

	return ui;
}

void removeSpaceTrails ( char *s , int32_t maxBytes ) {
	//char *end = s + gbstrlen(s) - 1;
	//while ( *end == ' ' ) {
	//	*end = '\0';
	//	end--;
	//}
	if ( ! s ) return;
	// empty already?
	if ( ! *s ) return;
	// remove all spaces now too!
	char *src = s; 
	char *dst = s;
	char *max = s + maxBytes - 1;
	for ( ; *src ; src++ ) {
		if ( *src == ' ' ) continue;
		*dst = *src;
		dst++;
		if ( dst >= max ) break;
	}
	*dst = '\0';
	return;
}


bool Proxy::doesUsernameExist ( char *user ) {
	UserInfo *uis = (UserInfo *)m_userInfoBuf.getBufStart();
	int32_t ni = m_userInfoBuf.length() / sizeof(UserInfo) ;
	int32_t i; for ( i = 0 ; i < ni ; i++ ) {
		// int16_tcut
		UserInfo *ui = &uis[i];
		// skip if no match
		if ( strcmp ( ui->m_login , user ) ) continue;
		return true;
	}
	return false;
}

void setFieldErrors ( StateUser *su ) {

	// is this submitting a new user?
	HttpRequest *hr = &su->m_hr;
	int32_t new1 = hr->getLong("new",0);

	// check email address format
	bool hadAt = false;
	bool hadFirstChar = false;
	bool badChar = false;
	bool hadCharAfterAt = false;
	bool hadDot = false;
	bool hadCharAfterDot = false;
	for ( char *p = su->m_email ; *p ; p++ ) {
		if ( ! hadAt && is_alnum_a(*p) ) hadFirstChar = true;
		if ( ! is_ascii(*p) ) badChar = true;
		if ( *p == '@' ) hadAt = true;
		if ( hadAt && is_alnum_a(*p ) ) hadCharAfterAt = true;
		if ( hadCharAfterAt && *p =='.' ) hadDot = true;
		if ( hadDot && is_alnum_a(*p) ) hadCharAfterDot = true;
	}
	if ( ! hadAt || ! hadFirstChar || badChar || 
	     ! hadCharAfterAt || ! hadDot || ! hadCharAfterDot ) {
		su->m_emailError = "Bad email address";
		su->m_error = 1;
	}
	// check phone #
	int32_t numDigits = 0;
	bool hadAlpha = false;
	bool badPhoneChar = false;
	for ( char *p = su->m_phone ; *p ; p++ ) {
		if ( is_alpha_a(*p) ) hadAlpha = true;
		if ( is_digit(*p) ) { numDigits++; continue; }
		if ( *p == '(' ) continue;
		if ( *p == ')' ) continue;
		if ( *p == '-' ) continue;
		if ( *p == ' ' ) continue;
		badPhoneChar = true;
	}
	if ( badPhoneChar ) {
		su->m_phoneError = "Use only digits and ( , ) "
			"or - in the phone number";
		su->m_error = 1;
	}
	if ( hadAlpha || numDigits < 7 ) {
		su->m_phoneError = "Bad phone number";
		su->m_error = 1;
	}
	// check username
	bool badUserChar = false;
	for ( char *p = su->m_user ; *p ; p++ ) {
		if ( ! is_alnum_a(*p) ) badUserChar = true;
	}
	if ( badUserChar ) {
		su->m_userError = "Username can only contain letters "
			"and numbers in ASCII";
		su->m_error = 1;
	}
	if ( ! su->m_user[0] ) {
		su->m_userError = "Username is required";
		su->m_error = 1;
	}
	if ( ! su->m_pwd[0] ) {
		su->m_pwdError = "Password required";
		su->m_error = 1;
	}
	if ( ! su->m_pwd2[0] ) {
		su->m_pwd2Error = "Repeated password required";
		su->m_error = 1;
	}
	if ( ! su->m_email[0] ) {
		su->m_emailError = "Email address required";
		su->m_error = 1;
	}
	if ( ! su->m_phone[0] ) {
		su->m_phoneError = "Phone number required";
		su->m_error = 1;
	}
	// check cvv
	bool badCVVChar = false;
	for ( char *p = su->m_cvv ; *p ; p++ ) {
		if ( ! is_digit(*p) ) badCVVChar = true;
	}
	if ( badCVVChar ) {
		su->m_cvvError = "CVV can only be digits, no spaces";
		su->m_error = 1;
	}
	if ( ! su->m_cvv[0] ) {
		su->m_cvvError = "cvv is required";
		su->m_error = 1;
	}
	// check ccnum
	bool badCC = false;
	for ( char *p = su->m_ccNum ; *p ; p++ ) {
		if ( ! is_digit(*p) &&
		     *p != ' ' &&
		     *p != '*' &&
		     *p != '-' ) 
			badCC = true;
	}
	if ( badCC ) {
		su->m_ccNumError = "Credit Card Number can only "
			"have digits spaces and hyphens in it";
		su->m_error = 1;
	}
	if ( ! su->m_ccNum[0] ) {
		su->m_ccNumError = "credit card # is required";
		su->m_error = 1;
	}
	// MM/YY
	int32_t digitsBefore = 0;
	bool hadSlash = false;
	int32_t digitsAfter = 0;
	bool badExpChar = false;
	for ( char *p = su->m_exp ; *p ; p++ ) {
		if ( ! is_digit(*p) && *p != '/' )
			badExpChar = true;
		if ( ! hadSlash && is_digit(*p) )
			digitsBefore++;
		if ( *p == '/' ) hadSlash = true;
		if ( hadSlash && is_digit(*p) )
			digitsAfter++;
	}
	if ( digitsBefore <= 0 ||
	     digitsBefore > 2 ||
	     ! hadSlash ||
	     digitsAfter != 2 ) {
		su->m_expError = "Format must be \"MM/YY\" where "
			"MM is the month number and YY is the year "
			"number";
		su->m_error = 1;
	}
	int32_t elen = 0;
	if ( su->m_exp ) elen = gbstrlen(su->m_exp);
	if ( ! su->m_expError[0] && elen >= 4 ) {
		int32_t yy = atoi(su->m_exp + elen-2);
		if ( yy < 13 ) {
			su->m_expError = "Expiration year can not be "
				"before 2013";
			su->m_error = 1;
		}
	}
	if ( ! su->m_exp[0] ) {
		su->m_expError = "credit card expiration is required";
		su->m_error = 1;
	}
	if ( ! su->m_ccType[0] ) {
		su->m_ccTypeError = "credit card type is required";
		su->m_error = 1;
	}
	if ( ! su->m_fc[0] ) {
		su->m_fcError = "xml feed pass code is required";
		su->m_error = 1;
	}
	// TODO: check other aspects of the provided data...
	if ( su->m_pwd[0] && 
	     su->m_pwd2[0] && 
	     strcmp(su->m_pwd,su->m_pwd2) ){
		su->m_pwd2Error = "Password does not match the "
			"above password";
		su->m_error = 1;
	}
	// make sure username is new
	if ( new1 && 
	     su->m_user[0] &&
	     g_proxy.doesUsernameExist ( su->m_user ) ) {
		su->m_userError = "Username already exists. "
			"Try another.";
		su->m_error = 1;
	}
	if ( new1 && ! su->m_terms ) {
		su->m_termsError = "You must agree to the terms to "
			"proceed";
		su->m_error = 1;
	}
	if ( new1 && su->m_deposit < MINCHARGE ) {
		su->m_tmpBuf1.safePrintf("You must initially deposit "
					 "a minimum of $%.02f to set up "
					 "an account", MINCHARGE);
		su->m_depositError = su->m_tmpBuf1.getBufStart();
		su->m_error = 1;
	}
}

bool printLoginPage ( StateUser *su , SafeBuf *errmsg ) {

	HttpRequest *hr = &su->m_hr;

	// print login page?
	char *bs1 = "<br><font color=red>";
	char *bs2 = "</font>";
	char *msg = "";
	if ( errmsg ) msg = errmsg->getBufStart();
	if ( errmsg && errmsg->length() == 0 ) {
		bs1 = "";
		bs2 = "";
		msg = "";
	}
	char *u = hr->getString("login",NULL,"");
	char *pwd = hr->getString("password",NULL,"");
	SafeBuf sb;
	sb.safePrintf("<html><body>"
		      "<title>Gigablast - Login</title>"
		      
		      "<center>"
		      "<a href=/>"
		      "<img src=http://www.gigablast.com/logo-med.jpg "
		      "height=122 width=500>"
		      "</a>"
		      "</center>"
		      "<br>"
		      
		      "<form method=post action=/account>"
		      
		      "<input type=hidden name=fromloginpage value=1>"
		      
		      "<table width=100%% cellpadding=5 cellspacing=0 "
		      "border=0>"
		      
		      "<tr bgcolor=#0340fd>"
		      "<th colspan=2>"
		      "<font color=33dcff>"
		      "User Login</font>"
		      "</th>"
		      "</tr>"
		      "<tr><td><br>"
		      
		      "<table cellpadding=10>"
		      "<tr>"
		      "<td>Username:</td>"
		      "<td><input type=text name=login size=20 value="
		      "\"%s\"></td>"
		      "</tr>"
		      "<tr>"
		      "<td>"
		      "Password:</td>"
		      "<td><input type=password name=password size=20 "
		      "value=\"%s\">"
		      "%s"
		      "%s"
		      "%s"
		      "</td>"
		      "</tr>"
		      "<tr><td colspan=2>"
		      "<input type=submit name=loggingin value=OK>"
		      "</td></tr>"
		      "</table>"
		      
		      "</form>"
		      
		      "<br>"
		      "<b>OR</b>"
		      "<br>"
		      "<br>"
		      "<a href=/account?new=1>Create a new account"
		      "</a>"
		      
		      "</table>"
		      
		      "</body>"
		      "</html>"
		      , u 
		      , pwd
		      , bs1
		      , msg
		      , bs2
		      );
	g_httpServer.sendDynamicPage ( su->m_socket, 
				       sb.getBufStart(), 
				       sb.length(),
				       0 , // cachetime in secs
				       false , // postreply?
				       "text/html", // content type
				       -1, // http status -1->200
				       NULL,//cookiePtr,
				       "utf-8" );
	mdelete(su,sizeof(StateUser),"usprox");
	delete(su);
	return true;
}

bool printLogoutPage ( StateUser *su ) {

	//HttpRequest *hr = &su->m_hr;
	// print login page?
	SafeBuf sb;
	sb.safePrintf("<html><body>"
		      "<title>Gigablast - Logout</title>"
		      
		      "<center>"
		      "<a href=/>"
		      "<img src=http://www.gigablast.com/logo-med.jpg "
		      "height=122 width=500>"
		      "</a>"
		      "</center>"
		      "<br>"
		      );

	if ( su->m_userId32 <= 0 ) 
		sb.safePrintf("You were not logged in. Why are you trying "
			      "to logout again?");
	else
		sb.safePrintf("You have been successfully logged out.");

	sb.safePrintf("<br><br>"
		      "<a href=/>Return to homepage</a> or"
		      "<br><br>"
		      "<a href=/account>Return to login page</a>");

	sb.safePrintf("</table>"
		      "</body>"
		      "</html>"
		      );
	// getLoggedInUserId should have set the sessionId64
	SafeBuf cb;


	// if we are the admin logged in as someone else in 
	// disguise, then redirect back to our main page. but if we are
	// the admin and NOT logged in as someone else, then log us out
	// as normal!
	if ( su->m_isMasterAdmin && su->m_adminSessId != su->m_sessionId64 ) {
		sb.reset();
		sb.safePrintf("<META HTTP-EQUIV=refresh "
			      "content=\"0;URL=/account\">");
		cb.safePrintf("Set-Cookie: sessionid=%" INT64 ";\r\n"
			      "Set-Cookie: userid=%" INT32 ";\r\n"
			      , su->m_adminSessId
			      , su->m_adminId );
	}
	else
		cb.safePrintf("Set-Cookie: sessionid=0;\r\n"
			      "Set-Cookie: userid=0;\r\n"
			      "Set-Cookie: adminsessid=0;\r\n"
			      );

	char *cookiePtr = NULL;
	if ( cb.length() ) cookiePtr = cb.getBufStart();


	g_httpServer.sendDynamicPage ( su->m_socket, 
				       sb.getBufStart(), 
				       sb.length(),
				       0 , // cachetime in secs
				       false , // postreply?
				       "text/html", // content type
				       -1, // http status -1->200
				       cookiePtr,
				       "utf-8" );
	mdelete(su,sizeof(StateUser),"usprox");
	delete(su);
	return true;
}

bool sendRedirect ( StateUser *su ) {

	//HttpRequest *hr = &su->m_hr;

	// they logged in successfully from the post, redirect
	SafeBuf rb;
	rb.safePrintf("<META HTTP-EQUIV=refresh "
		      "content=\"0;URL=/account?rd=1\">");

	// this must be legit!
	if ( su->m_sessionId64 <= 0 ) { char *xx=NULL;*xx=0; }
	if ( su->m_userId32    <= 0 ) { char *xx=NULL;*xx=0; }

	// getLoggedInUserId should have set the sessionId64
	SafeBuf cb;
	cb.safePrintf("Set-Cookie: sessionid=%" INT64 ";\r\n"
		      "Set-Cookie: userid=%" INT32 ";\r\n"
		      ,su->m_sessionId64
		      ,su->m_userId32);
	char *cookiePtr = NULL;
	if ( cb.length() ) cookiePtr = cb.getBufStart();
	// . send this page
	// . encapsulates in html header and tail
	// . make a Mime
	g_httpServer.sendDynamicPage ( su->m_socket, 
				       rb.getBufStart(), 
				       rb.length(),
				       0 , // cachetime in secs
				       false , // postreply?
				       "text/html", // content type
				       -1, // http status -1->200
				       cookiePtr,
				       "utf-8" );
	// i guess it copies the safebuf contents..
	mdelete(su,sizeof(StateUser),"usprox");
	delete(su);
	return true;
}




// . returns false if blocked, true otherwise
// . sets errno on error
// . make a web page displaying user info
// . use https only!
// . password and username transmitted with post for login
// . just set cookie to the session id then
// . call g_httpServer.sendDynamicPage() to send it
bool sendPageAccount ( TcpSocket *s , HttpRequest *hr2 ) {

	// only call this from host #0! so we can use msg5 not msg0
	// and store all of userdb on host #0 and twins.
	if ( ! g_proxy.isProxy() ) { char *xx=NULL;*xx=0; }

	// ensure only https! this is sensitive stuff
	if ( ! s->m_ssl ) {
		char *msg = "Only access account info using https!";
		g_httpServer.sendErrorReply ( s, 500, msg );
		return true; 
	}

	bool err5 = false;
	if ( err5 ) {
	hadError5:
		g_errno = ENOMEM;
		log("proxy: new(%" INT32 "): %s",(int32_t)sizeof(StateUser),mstrerror(g_errno));
		g_httpServer.sendErrorReply(s,500,mstrerror(g_errno));
		return true;
	}

	//
	// shit, we gotta save state since makeGif can block
	//
	StateUser *su;
	try { su = new (StateUser) ; }
	catch ( ... ) { 
	  goto hadError5;
	}
	mnew ( su, sizeof(StateUser), "suprox" );

	// copy shit. if it fails return 500 http error.
	if ( ! su->m_hr.copy ( hr2 ) ) {
		mdelete(su,sizeof(StateUser),"suprox");
		delete(su);
		goto hadError5;
	}

	// use that now
	HttpRequest *hr = &su->m_hr;

	// copy socket and ui offset
	su->m_socket = s;

	// bogus values
	su->m_sessionId64 = 0;
	su->m_userId32 = -1;

	// reset flags
	su->m_transactionSuccessful = false;
	su->m_attemptedTransaction = false;
	su->m_deposit = hr->getFloat ("deposit",0.0);
	su->m_refund  = hr->getFloat ("refund",0.0);
	su->m_refundFee = 0.0;
	// refunds require the original transaction's id from authorize.net
	su->m_refundTransId = hr->getLongLong("transid",0LL);
	
	// set this crap from the GET request
	su->m_user    = hr->getString("user",NULL,"");
	su->m_pwd     = hr->getString("pwd",NULL,"");
	su->m_pwd2    = hr->getString("pwd2",NULL,"");
	su->m_ccNum   = hr->getString("cc",NULL,"");
	su->m_ccType  = hr->getString("cctype",NULL,"");
	su->m_cvv     = hr->getString("cvv",NULL,"");
	su->m_exp     = hr->getString("exp",NULL,"");
	su->m_fc      = hr->getString("fc",NULL,"");
	su->m_phone   = hr->getString("phone",NULL,"");
	su->m_email   = hr->getString("email",NULL,"");
	su->m_terms   = hr->getLong("terms",0);

	// clear these
	su->m_error       = 0;
	su->m_userError   = "";
	su->m_pwdError    = "";
	su->m_pwd2Error   = "";
	su->m_cvvError    = "";
	su->m_ccNumError  = "";
	su->m_expError    = "";
	su->m_ccTypeError = "";
	su->m_depositError= "";
	su->m_refundError = "";
	su->m_fcError     = "";
	su->m_phoneError  = "";
	su->m_emailError  = "";
	su->m_termsError  = "";
	// european stuff
	su->m_firstName   = "";
	su->m_lastName    = "";
	su->m_address     = "";
	su->m_city        = "";
	su->m_state       = "";
	su->m_country     = "";
	su->m_zip         = "";

	// remove trailing spaces from all. include max bytes too including \0
	removeSpaceTrails ( su->m_user , 32 );
	removeSpaceTrails ( su->m_pwd , 32 );
	removeSpaceTrails ( su->m_pwd2 , 32 );
	removeSpaceTrails ( su->m_ccNum , 64 );
	removeSpaceTrails ( su->m_ccType , 32);
	removeSpaceTrails ( su->m_cvv , 5);
	removeSpaceTrails ( su->m_exp , 6 );
	removeSpaceTrails ( su->m_fc ,16 );
	removeSpaceTrails ( su->m_phone ,30);
	removeSpaceTrails ( su->m_email ,80);
	// european stuff for credit card processing
	removeSpaceTrails ( su->m_firstName,40);
	removeSpaceTrails ( su->m_lastName,40);
	removeSpaceTrails ( su->m_address,80);
	removeSpaceTrails ( su->m_city,30);
	removeSpaceTrails ( su->m_state,30);
	removeSpaceTrails ( su->m_country,30);
	removeSpaceTrails ( su->m_zip,30);

	// are we dealing with a form submission?
	int32_t submit = hr->getLong("submitted",0);

	int32_t new1 = hr->getLong("new",0);
	
	int32_t edit = hr->getLong("edit",0);

	int32_t logout = hr->getLong("logout",0);

	if ( new1 ) edit = 0;

	if ( new1 ) su->m_submittingNewUser = 1;
	else        su->m_submittingNewUser = 0;

	// they must be logged in
	SafeBuf errmsg;

	// this will set su->m_userId32 and su->m_sessionId64
	UserInfo *ui = g_proxy.getLoggedInUserInfo ( su , &errmsg ) ;

	if ( logout )
		return printLogoutPage ( su );

	// if not logged in, print the login page
	if ( su->m_userId32 <= 0 && ! new1 ) 
		return printLoginPage( su , &errmsg );


	// if not doing a submit of an edit or a new user action, 
	// then take these values from the UserInfo
	if ( ! submit && ! new1 ) {
		su->m_user = ui->m_login;
		su->m_pwd  = ui->m_password;
		su->m_pwd2 = ui->m_password;
		su->m_ccNum   = ui->m_creditCardNum;
		su->m_ccType  = ui->m_creditCardType;
		su->m_cvv     = ui->m_cvv;
		su->m_exp     = ui->m_creditCardExpires;
		su->m_fc      = ui->m_xmlFeedCode;
		//su->m_deposit = 0.0;
		su->m_phone   = ui->m_phone;
		su->m_email   = ui->m_email;
		su->m_firstName = ui->m_firstName;
		su->m_lastName = ui->m_lastName;
		su->m_city = ui->m_city;
		su->m_state = ui->m_state;
		su->m_country = ui->m_country;
		su->m_zip = ui->m_zip;
		su->m_address = ui->m_address;
	}



	// if they posted to us from the login page, then redirect them
	// so the client can refresh on their browser or go back to the page
	// on their browser. otherwise it asks if you want to confirm the post
	// submission.
	if ( hr->getLong("fromloginpage",0) )
		return sendRedirect ( su );

	// we are logged in at this point, so get this
	//UserInfo *ui = g_proxy.getUserInfoFromId ( su->m_userId32 );

	// they submitted a deposit/withdrawal request?
	if ( su->m_deposit != 0.0 && ui ) {

		if ( su->m_deposit < 0.0 ) {
			su->m_depositError = "Deposit amount must be positive";
			su->m_error = 1;
			return g_proxy.printAccountingInfoPage ( su );
		}
		// error?
		if ( su->m_deposit < MINCHARGE ) {
			su->m_tmpBuf1.safePrintf("Minimum deposit is $%.02f",
						 MINCHARGE);
			su->m_depositError = su->m_tmpBuf1.getBufStart();
			su->m_error = 1;
			return g_proxy.printAccountingInfoPage ( su );
		}
		// this will ultimately display the account info page
		// on success i guess!
		return g_proxy.hitCreditCard ( su );
	}

	if ( su->m_refund != 0.0 ) {
		// deal with bad refund amounts
		if ( su->m_refund < 0.0 ) {
			su->m_refundError = "Refund amount must be positive";
			su->m_error = 1;
			return g_proxy.printAccountingInfoPage ( su );
		}
		// ensure they have enough if they are withdrawing, not deposit
		if ( ui->m_accountBalance < su->m_refund ) {
			su->m_refundError = "You can not refund more than "
				"the account balance. ";
			su->m_error = 1;
			return g_proxy.printAccountingInfoPage ( su );
		}
		return g_proxy.hitCreditCard ( su );
	}



	// if adding new user with blank form generate some defaults
	if ( new1 && ! submit ) {
		su->m_deposit = MINCHARGE;
		// make a random number for the search feed code
		int32_t rc = rand() & 0x7fffffff;
		sprintf(su->m_tmpBuf2,"%" INT32 "",rc);
		su->m_fc = su->m_tmpBuf2;
	}


	// . try to detect errors on submitted data
	// . sets StateUser::m_userError, m_pwdError, etc.
	// . sets StateUser::m_error on any error being set
	if ( submit ) 
		setFieldErrors ( su );

	// if the submitted data has something wrong then tell the user
	// what is wrong and allow them to fix it and resubmit
	if ( submit && su->m_error )
		return g_proxy.printEditForm( su );

	// . if we couldn't find anything wrong, try to make a deposit
	// . do not add the userinfo record itself until deposit is made ok
	if ( submit && new1 )
		return g_proxy.hitCreditCard ( su );


	if ( submit && edit ) {
		// get the userinfo class
		SafeBuf errmsg;
		// copy edited info over
		strcpy(ui->m_password,su->m_pwd);
		strcpy(ui->m_xmlFeedCode,su->m_fc);
		// . do not update this if it has ****'s in it
		// . but if they entered a whole new credit card # then
		//   update it, in which case we will have no *'s in it
		if ( su->m_ccNum[0] != '*' )
			strcpy(ui->m_creditCardNum,su->m_ccNum);
		strcpy(ui->m_cvv,su->m_cvv);
		strcpy(ui->m_creditCardExpires,su->m_exp);
		strcpy(ui->m_creditCardType,su->m_ccType);
		strcpy(ui->m_phone,su->m_phone);
		strcpy(ui->m_email,su->m_email);
		// save the new info
		g_proxy.saveUserBufs();
		// now redirect to accounting info page so they can hit the
		// back button to go back to this page without having to
		// confirm a post resubmission
		log("proxy: sending redirect 2");
		return sendRedirect ( su );		
		// now display it in the normal page display
		//return g_proxy.printAccountingInfoPage ( su );
	}

	// sanity test. submit should NOT be set here!!!
	if ( submit ) { 
		mdelete(su,sizeof(StateUser),"suprox");
		delete(su);
		log("proxy: bad submit");
		g_errno = EBADENGINEER;
		g_httpServer.sendErrorReply(s,500,mstrerror(g_errno));
		return true;
	}

	// print blank add new user form. "GET /account?new=1"
	if ( new1 ) 
		return g_proxy.printEditForm ( su );

	// edit existing user account. "GET /account?edit=1"
	if ( edit ) 
		return g_proxy.printEditForm ( su );


	return g_proxy.printAccountingInfoPage ( su );
}

void gotDepositDocWrapper ( void *st , TcpSocket *ts ) {
	StateUser *su = (StateUser *)st;
	su->m_docSocket = ts;
	g_proxy.gotDepositDoc ( su );
}

/*
int32_t Proxy::getNextTransactionId ( ) {
	// make sure s_lastTransId is set to our last transaction id PLUS one!
	static int32_t s_lastTransId = -1;

	if ( s_lastTransId == -1 ) {
		int32_t nd = m_depositBuf.length() / sizeof(DepositRec);
		DepositRec *drs = (DepositRec *)m_depositBuf.getBufStart();
		for ( int32_t i = 0 ; i < nd ; i++ ) {
			DepositRec *dr = &drs[i];
			if ( dr->m_transactionId > s_lastTransId )
				s_lastTransId = dr->m_transactionId;
		}
		// advance over that
		s_lastTransId++;
	}

	int32_t save = s_lastTransId;
	s_lastTransId++;
	return save;
}
*/

// . returns false if blocked, true otherwise
// . sets g_errno on error and returns true
// . sets su->m_authenticated to true or false
bool Proxy::hitCreditCard ( StateUser *su ) {

	// assume not authentic
	su->m_transactionSuccessful = false;
	su->m_attemptedTransaction = true;
	//HttpRequest *hr = &su->m_hr;

	// ensure plenty room
	int32_t need = sizeof(DepositRec) * 10;
	// on error, set the m_authNetMsg
	if ( ! m_depositBuf.reserve ( need ) ) 
		return gotDepositDoc(su);

	// get a unique transaction id
	//su->m_transactionId = getNextTransactionId();

	// connect to authorize.net
	SafeBuf url;
	bool testMode = false;


	if ( testMode )
		url.safePrintf("https://test.authorize.net/gateway/"
			       "transact.dll?");
	else
		url.safePrintf( "https://secure.authorize.net/gateway/"
				"transact.dll?");
	

	//UserInfo *ui = getUserInfoFromId ( su->m_userId32 );

	// make the POST
	// see  http://developer.authorize.net/guides/AIM/wwhelp/wwhimpl/js/html/wwhelp.htm 
	// see (List of API Fields at end)

	url.safePrintf("x_card_num=%s"
		       //"&x_cust_id=%" INT32 ""
		       "&x_customer_ip=%s"
		       "&x_delim_data=1" // must be 1 for AIM
		       "&x_description=gigablast.com+search+engine+services"
		       //"&x_invoice_num=%" INT32 ""
		       "&x_email_customer=1" // %" INT32 ""
		       "&x_version=3.0"
		       "&x_currency_code=USD"
		       "&x_recurring_billing=0"
		       "&x_relay_response=0" // we are using AIM
		       , su->m_ccNum
		       //, ui->m_userId32
		       , iptoa(su->m_socket->m_ip)
		       //, su->m_invoiceNum // also store in DepositRec
		       //, ui->m_sendEmails // send email to user
		       );

	// sanity check
	//if ( su->m_invoiceNum < 0 ) { char *xx=NULL;*xx=0; }


	// a negative amount will be a withdraw
	if ( su->m_refund != 0.0 ) {
		// we'll record this in deposit buf
		su->m_refundFee = .05 * su->m_refund;
		// this is kinda bogus...
		url.safePrintf("&x_type=CREDIT"
			       // this is kinda bogus since we do not see it
			       // has refunding original transactions but 
			       // rather getting a withdrawal
			       "&x_trans_id=%" INT64 ""

			       // do we need x_split_tender_id ???
			       // use that INSTEAD of x_trans_id...
			       //"&x_split_tender_id=%" INT64 ""

			       "&x_amount=%.02f"
			       , su->m_refundTransId
			       , su->m_refund - su->m_refundFee );
	}
	else
		url.safePrintf("&x_type=AUTH_CAPTURE"
			       "&x_amount=%.02f"
			       , su->m_deposit );


	// user's email address:
	url.safePrintf("&x_email="); 
	url.urlEncode ( su->m_email );
	url.safePrintf("&x_exp_date=");
	url.urlEncode ( su->m_exp );
	url.safePrintf("&x_phone=");
	url.urlEncode ( su->m_phone );
	//url.safePrintf("&x_card_num=");
	//url.urlEncode( su->m_ccNum );
	//
	// INSERT YOUR secret transaction/api key for authorize.net
	//

	url.safePrintf("&x_tran_key=%s",g_secret_tran_key);
	url.safePrintf("&x_login=%s",g_secret_api_key);

	//url.safePrintf("&x_tran_key=xxxxxxxxxxxxxxx");
	//url.safePrintf("&x_login=yyyyyyyyyy");

	// european requires stuff
	/*
	url.safePrintf("&x_first_name=");
	url.urlEncode ( ui->m_firstName );
	url.safePrintf("&x_last_name=");
	url.urlEncode ( ui->m_lastName );
	url.safePrintf("&x_address=");
	url.urlEncode ( ui->m_address );
	url.safePrintf("&x_city=");
	url.urlEncode ( ui->m_city );
	url.safePrintf("&x_state=");
	url.urlEncode ( ui->m_state );
	url.safePrintf("&x_zip=");
	url.urlEncode( ui->m_zip );
	*/

	log("proxy: contacting authorize.net");

	// show it
	log("proxy: authorize.netrequest=%s",url.getBufStart());

	// returns false if blocked, true otherwise
	if ( ! g_httpServer.getDoc (  url.getBufStart() ,
				      0 , // ip, 0 means unknown
				      0 , // offset
				      -1 , // size (use GET request)
				      0 , // ifmodifiedsince
				      su , // state
				      gotDepositDocWrapper ,
				      60 * 1000 , // 60 sec timeout
				      0 , // proxyip
				      0 , // proxyport
				      1000000, // 1MB maxtextdoclen
				      1000000, // 1MB maxotherdoclen
				      "Gigabot/1.0" , // useragent
				      "HTTP/1.0", // proto
				      true , // do post? YES!!!
				      NULL )) // cookie
		// return false if it blocked
		return false;

	// this never blocks?
	if ( ! gotDepositDoc ( su ) )
		log("proxy: got error in gotDepositDoc: %s",
		    mstrerror(g_errno));

	// never blocks
	return true;
}

int64_t rand63 ( ) {
	uint32_t r1 = rand();
	uint32_t r2 = rand();
	uint64_t r = r1;
	r <<= 32;
	r |= r2;
	r &= 0x7fffffffffffffffLL;
	return r;
}

// . parse a field out of an authorize.net reply	
// . first fieldNum is 0
char *getField ( char *docHTML , int32_t fieldNum , int32_t *replyMsgLen ) {

	char *p = docHTML;
	int32_t numCommas = 0;
	for ( ; *p ; p++ ) {
		if ( *p == ',' ) numCommas++;
		if ( numCommas == fieldNum ) { p++; break; }
	}
	// return NULL if not there
	if ( ! *p ) return NULL;
	// find next comma
	char *next = strchr ( p , ',' );
	// not there?
	int32_t plen;
	if ( ! next ) plen = gbstrlen(p);
	else          plen = next - p;
	// use that
	*replyMsgLen = plen;
	return p;
}


// . returns false and sets g_errno on error, true otherwise
// . does not block i guess
bool Proxy::gotDepositDoc ( StateUser *su ) {

	// another error? ETCPTIMDOUT?
	if ( g_errno ) {
		log("proxy: bad authorize.net reply");
		su->m_error = 1;
		su->m_authNetMsg.safePrintf("Error communicating with "
					    "authorize.net to charge "
					    "credit card. %s",
					    mstrerror(g_errno));
		// there was an error - redisplay the new user form
		if ( su->m_submittingNewUser ) return printEditForm ( su );
		// otherwise, we were making another deposit
		return printAccountingInfoPage ( su );
	}

	// parse reply
	TcpSocket *ts = su->m_docSocket;
	char *reply = ts->m_readBuf;
	int32_t replySize = ts->m_readOffset;
	HttpMime mime;
	Url redirUrl;
	mime.set ( reply , replySize , &redirUrl );

	// log it
	log("proxy: authorize.net reply: %s",reply);

	int32_t status = mime.getHttpStatus();

	if ( status != 200 ) {
		log("proxy: bad authorize.net mime stats = %" INT32 "",status);
		su->m_error = 1;
		su->m_authNetMsg.safePrintf("Error communicating with "
					    "authorize.net to charge "
					    "credit card. HTTP status "
					    "%" INT32 "",status);
		// there was an error - redisplay the new user form
		if ( su->m_submittingNewUser ) return printEditForm ( su );
		// otherwise, we were making another deposit
		return printAccountingInfoPage ( su );
	}

	// get msg
	char *docHTML = reply + mime.getMimeLen();
	//int32_t docLen = replySize - mime.getMimeLen();

	// a,b,c,ERRMSG,...
	// see http://developer.authorize.net/guides/AIM/wwhelp/wwhimpl/js/html/wwhelp.htm
	// the first number:
	// 1: approved
	// 2: declined
	// 3: error
	// 4: held for review
	int32_t returnCode = atol(docHTML);

	// show it has msg
	int32_t replyMsgLen = 0;
	char *replyMsg = getField ( docHTML , 3 , &replyMsgLen );


	if ( replyMsgLen ) 
		su->m_authNetMsg.safePrintf("<br><br>");

	// then the reply msg
	su->m_authNetMsg.safeMemcpy ( replyMsg , replyMsgLen );

	// get the reason code
	int32_t scLen; char *scPtr = getField(docHTML,2,&scLen);
	int32_t rc = 0; if ( scPtr ) rc = atol2(scPtr,scLen);
	if ( returnCode != 1 && su->m_refund != 0.0 && rc == 54 ) 
		// if this happens consider issuing a void and we can
		// save the credit card processing fees!!!
		su->m_authNetMsg.safePrintf(" You need to wait 25 hours for "
					    "the charge to settle before a "
					    "refund can be issued. ");


	su->m_authNetMsg.pushChar('\0');

	// error? this means it has NOT been approved
	if ( returnCode != 1 ) {
		su->m_error = 1;
		// there was an error - redisplay the new user form
		if ( su->m_submittingNewUser ) return printEditForm ( su );
		// otherwise, we were making another deposit
		return printAccountingInfoPage ( su );
	}

	// ensure userid32 unique!
	if ( su->m_submittingNewUser ) {
		int32_t numUsers = m_userInfoBuf.length() / sizeof(UserInfo) ;
		su->m_userId32 = numUsers + 1;
	}


	float amount;

	// make it negative for storing in deposit rec
	if ( su->m_refund != 0.0 ) amount = -1.0 * su->m_refund;
	else                       amount =        su->m_deposit;

	// log it for posterity
	log("proxy: successfully charged $%.02f to userid %" INT32 ""
	    ,amount
	    ,su->m_userId32
	    );


	int32_t need = sizeof(DepositRec) * 2;
	if ( ! m_depositBuf.reserve (need) ) {
		su->m_error = 1;
		su->m_authNetMsg.safePrintf("Error adding deposit to buf. "
					    "userid32=%" UINT32 " amt=$%.02f"
					    ,su->m_userId32
					    ,amount);
		char *xx=NULL;*xx=0; 
	}

	// good, now add it to buffer
	DepositRec dr;
	memset ( &dr , 0 , sizeof(DepositRec) );
	int32_t now = getTimeLocal();
	if ( su->m_userId32 <= 0 ) { char *xx=NULL;*xx=0; }
	dr.m_userId32 = su->m_userId32;
	dr.m_depositDate   = now;

	//
	// . we need this for doing CREDITs/withdrawals back to the user
	// . i've seen this # > 5B so use a int64_t
	//
	int32_t tlen;
	char *tid = getField(docHTML,6,&tlen);
	if ( tid ) {
		char c = tid[tlen];
		tid[tlen] = '\0';
		int64_t transId = atoll(tid);
		tid[tlen] = c;
		// the authorize.net transaction id
		dr.m_authorizeNetTransactionId = transId;
		log("proxy: got transactionid=%" INT64 "",transId);
	}
	
	// if depositing...
	if ( su->m_deposit != 0.0 ) {
		dr.m_depositAmount = su->m_deposit;
		dr.m_flags = DRF_DEPOSIT;
		m_depositBuf.safeMemcpy ( &dr , sizeof(DepositRec));
	}
	// withdraw fee? add that into this table too
	else {
		if ( su->m_refundFee <= 0.0 ) { char *xx=NULL;*xx=0; }
		dr.m_depositAmount = -1 * su->m_refundFee;
		dr.m_flags = DRF_WITHDRAW_FEE;
		m_depositBuf.safeMemcpy ( &dr , sizeof(DepositRec) );
		// amount is negative, withdraw fee is positive
		dr.m_depositAmount = -1*(su->m_deposit - su->m_refundFee);
		dr.m_flags = DRF_WITHDRAW;
		m_depositBuf.safeMemcpy ( &dr , sizeof(DepositRec) );
	}


	// success!
	su->m_transactionSuccessful = true;

	// if not submitting a new user, just add the amount to our
	// account balance and save, then print the accounting info page
	if ( ! su->m_submittingNewUser ) {
		// get user rec
		UserInfo *ui = g_proxy.getUserInfoFromId ( su->m_userId32 );
		// add it. will be negative for a refund.
		ui->m_accountBalance += amount;
		// save the deposit to disk
		saveUserBufs();
		// print the accounting info page with success msg
		su->m_authNetMsg.safePrintf("Despoit of $%.02f was "
					    "successful",amount);
		//return printAccountingInfoPage ( su );
		// shit actually, to avoid re-submits more so, make it
		// redirect i guess
		log("proxy: sending redirect 3");
		return sendRedirect ( su );		
	}



	/////////
	//
	// add the new user here!!
	//
	/////////
	UserInfo u2;
	memset ( &u2 , 0 , sizeof(UserInfo) );
	strncpy(u2.m_login, su->m_user,30);
	strncpy(u2.m_password, su->m_pwd,30);
	strncpy(u2.m_xmlFeedCode, su->m_fc,14);
	strncpy(u2.m_creditCardNum, su->m_ccNum,62);
	strncpy(u2.m_cvv, su->m_cvv,4);
	strncpy(u2.m_creditCardExpires, su->m_exp,5);
	strncpy(u2.m_creditCardType, su->m_ccType,30); // visa
	u2.m_pending = 0;
	u2.m_flags = 0;
	u2.m_accountBalance = su->m_deposit;
	//strncpy(u2.m_creditCardAddress,ccAddr,250);
	u2.m_signUpDate = getTimeLocal();
	u2.m_userId32 = su->m_userId32;
	u2.m_lastSessionId64 = rand63();
	u2.m_lastActionTime = now;
	u2.m_lastLoginIP = su->m_socket->m_ip;

	// note this as well
	log ("proxy: successfully added userid %" INT32 " to userbuf",su->m_userId32);

	// store it
	if ( ! m_userInfoBuf.safeMemcpy ( &u2 , sizeof(UserInfo) ) )
		return false;

	// save it. this blocks for now...
	if ( ! saveUserBufs() ) {
		log("proxy: error saving user bufs: %s",mstrerror(g_errno));
		return false;
	}

	// we are auto-logged in
	//su->m_userId32 = userId32;
	su->m_sessionId64 = u2.m_lastSessionId64;

	// now print the accounting page. they are an official user now.
	// no! because if they try to reload that page or go back to that
	// page it asks to confirm form resubmission! then it will just
	// say that that username already exists! 
	//return printAccountingInfoPage ( su );
	// so instead, let's redirect them to /account and set the cookie!
	// make a cookie in case they just logged in through this page!
	// otherwise the login doesn't "stick"
	return sendRedirect ( su );
}

// returns false and sets g_errno on error, true otherwise. does not block
// i guess...
bool Proxy::printEditForm ( StateUser *su ) {

	HttpRequest *hr = &su->m_hr;
	TcpSocket *s = su->m_socket;

	// print form to add new user? or submitting new user info?
	int32_t new1 = hr->getLong("new",0);

	// print form to edit user? or did they submit their edits?
	int32_t edit = hr->getLong("edit",0);

	// are we dealing with a form submission?
	int32_t submit = hr->getLong("submitted",0);

	// get the user we are editing into "ui"
	UserInfo *ui = NULL;
	SafeBuf errmsg;
	if ( edit ) ui = getUserInfoFromId ( su->m_userId32 );

	char *msg = "";

	// wtf?
	if ( edit && !ui ) {
		msg = "Could not find user to edit.";
		//hadError7:
		mdelete(su,sizeof(StateUser),"usprox");
		delete(su);
		g_httpServer.sendErrorReply(s,500,msg);
		return true;
	}

	SafeBuf sb;

	// show the user info form
	sb.safePrintf( "<html>"
		       "<title>Gigablast - Account Information</title>"
		       "<body>"
		       "<center>"
		       "<a href=/>"
		       "<img src=http://www.gigablast.com/logo-med.jpg "
		       "height=122 width=500>"
		       "</a>"
		       "</center>"
		       "<br>"

		      "<form method=post name=fff action=/account>"

		       "<table width=100%% cellpadding=5 cellspacing=0 "
		       "border=0>"

		       "<tr bgcolor=#0340fd>"
		       "<th colspan=2>"
		       "<font color=33dcff>"
		       "New User Information</font>"
		       "</th>"
		       "</tr>"
		       "<tr><td><br>"
		       );


	/*
	if ( new1 && submit && ! su->m_deposit ) {
		sb.safePrintf("<br><font color=red size=-1><b>"
			      "Unable to charge "
			      "Credit Card. Please check credit card info "
			      "below and verify it is accurate before "
			      "resubmitting.");
		sb.cat ( su->m_authNetMsg );
		sb.safePrintf("</font><br>");
	}
	*/
		


	sb.safePrintf( "<center>"
		       "<table width=400px cellpadding=6>"
		       );


	if ( su->m_attemptedTransaction && ! su->m_transactionSuccessful ) {
		sb.safePrintf("<tr><td colspan=10><b><font color=red>"
			      "Failed to deposit $%.02f. %s"
			      //" Please ensure your credit card info is "
			      //"correct."
			      "</font></td></tr>"
			      , su->m_deposit
			      , su->m_authNetMsg.getBufStart()
			      );
	}


	// username
	if ( new1 ) {	
		sb.safePrintf("<tr>"
			      "<td valign=top>Username</td>"
			      "<td><input type=text name=user value=\"%s\">"
			      , su->m_user 
			      );
	}
	else {
		sb.safePrintf("<tr>"
			      "<td valign=top>Username</td>"
			      "<td>%s"
			      "<input type=hidden name=user value=\"%s\">"
			      , su->m_user 
			      , su->m_user 
			      );
	}
	if ( su->m_userError[0] )
		sb.safePrintf("<br><font size=-1 "
			      "color=red>%s</font>",su->m_userError);
	sb.safePrintf("</td></tr>");


	// password
	sb.safePrintf("<tr>"
		      "<td valign=top>Password</td>"
		      "<td><input type=password size=12 maxlength=12 "
		      "name=pwd value=\"%s\">"
		      , su->m_pwd );
	if ( su->m_pwdError[0] )
		sb.safePrintf("<br><font size=-1 "
			      "color=red>%s</font>",su->m_pwdError);
	sb.safePrintf("</td></tr>");

	// password2
	sb.safePrintf("<tr>"
		      "<td valign=top>Repeat Password</td>"
		      "<td><input type=password size=12 maxlength=12 "
		      "name=pwd2 value=\"%s\">"
		      , su->m_pwd2 );
	if ( su->m_pwd2Error[0] )
		sb.safePrintf("<br><font size=-1 "
			      "color=red>%s</font>",su->m_pwd2Error);
	sb.safePrintf("</td></tr>");


	// email
	sb.safePrintf("<tr>"
		      "<td valign=top>Email Address</td>"
		      "<td><input type=text name=email value=\"%s\">"
		      , su->m_email );
	if ( su->m_emailError[0] )
		sb.safePrintf("<br><font size=-1 "
			      "color=red>%s</font>",su->m_emailError);
	else
		sb.safePrintf("<br><font size=-1 color=gray>"
			      "Required for emailing you receipts, or a "
			      "new password should you forget it."
			      "</font>");
	sb.safePrintf("</td></tr>");

	// phone #
	sb.safePrintf("<tr>"
		      "<td valign=top>Phone Number</td>"
		      "<td><input type=text name=phone value=\"%s\">"
		      , su->m_phone );
	if ( su->m_phoneError[0] )
		sb.safePrintf("<br><font size=-1 "
			      "color=red>%s</font>",su->m_phoneError);
	sb.safePrintf("</td></tr>");



	// creidt card #
	sb.safePrintf("<tr>"
		      "<td valign=top>Credit Card Number</td>"
		      "<td><input type=text name=cc value=\"");
	// don't show the entire credit card # for security!
	if ( edit && gbstrlen(su->m_ccNum)>12  )
		sb.safePrintf("************%s\">", su->m_ccNum + 12);
	else
		sb.safePrintf("%s\">", su->m_ccNum );
	if ( su->m_ccNumError[0] )
              sb.safePrintf("<br><font size=-1 "
			    "color=red>%s</font>",su->m_ccNumError);
	sb.safePrintf("</td></tr>");


	// credit card type
	char *s1 = "";
	char *s2 = "";
	char *s3 = "";
	char *s4 = "";
	if ( !strcmp(su->m_ccType,"Visa") ) s1 = " selected";
	if ( !strcmp(su->m_ccType,"MasterCard") ) s2 = " selected";
	if ( !strcmp(su->m_ccType,"Discovery") ) s3 = " selected";
	if ( !strcmp(su->m_ccType,"AmericanExpress") ) s4 = " selected";
	sb.safePrintf("<tr>"
		      "<td valign=top>Credit Card Type</td>"
		      "<td><select name=cctype>"
		      "<option value=Visa%s>Visa</option>"
		      "<option value=MasterCard%s>MasterCard</option>"
		      "<option value=Discover%s>Discover</option>"
		      "<option value=AmericanExpress%s>American Express"
		      "</option>"
		      "</select>"
		      , s1,s2,s3,s4 );
	if ( su->m_ccTypeError[0] )
              sb.safePrintf("<br><font size=-1 "
			    "color=red>%s</font>",su->m_ccTypeError);
	sb.safePrintf("</td></tr>");

	// ccv
	sb.safePrintf("<tr>"
		      "<td valign=top>3-digit CVV</td>"
		      "<td><input type=text name=cvv size=3 "
		      "maxlength=3 value=\"%s\">&nbsp;&nbsp;"
		      "<font size=-1 color=gray>(on back "
		      "of card)</font>"
		      , su->m_cvv );
	if ( su->m_cvvError[0] )
              sb.safePrintf("<br><font size=-1 "
			    "color=red>%s</font>",su->m_cvvError);
	sb.safePrintf("</td></tr>");


	// expiration
	sb.safePrintf("<tr>"
		      "<td valign=top>"
		      "<nobr>Credit Card Expiration</nobr></td>"
		      "<td><input type=text maxlength=5 "
		      "size=5 name=exp value=\"%s\">&nbsp;&nbsp;"
		      "<font size=-1 color=gray>"
		      "(MM/YY)"
		      "</font>"
		      , su->m_exp );
	if ( su->m_expError[0] )
              sb.safePrintf("<br><font size=-1 "
			    "color=red>%s</font>",su->m_expError);
	sb.safePrintf("</td></tr>");

	// deposit amount
	// TODO: add su->m_depositError!
	if ( new1 ) {
		float deposit = su->m_deposit;
		if ( ! submit ) deposit = MINCHARGE;//100.00;
		sb.safePrintf("<tr>"
			      "<td valign=top><nobr>"
			      "Initial Deposit Amount</nobr></td>"
			      "<td>"
			      "<input type=text name=deposit value=\"%.02f\">"
			      "<br>"
			      "<font size=-1 color=gray><i>Minimum deposit "
			      "is $%.02f"
			      "</i></font>"
			      ,deposit
			      ,MINCHARGE
			      );
		if ( su->m_depositError[0] )
			sb.safePrintf("<br><font size=-1 "
				      "color=red>%s</font>",
				      su->m_depositError);
		sb.safePrintf("</td></tr>");
	}

	// search feed code
	sb.safePrintf("<tr>"
		      "<td valign=top>XML Feed Code</td>"
		      "<td><input type=text name=fc size=14 "
		      "maxlength=14 value=\"%s\">"
		      , su->m_fc );
	if ( su->m_fcError[0] )
              sb.safePrintf("<br><font size=-1 "
			    "color=red>%s</font>",su->m_fcError);
	else
		sb.safePrintf("<br><font size=-1 color=gray>"
			      "A secret code used along with your userid "
			      "to access the XML feeds."
			      "</font>");

	sb.safePrintf("</td></tr>");


	if ( new1 ) {
		char *cs = "";
		if ( su->m_terms ) cs = " checked";
		sb.safePrintf("<tr><td colspan=10>"
			      "<input type=checkbox name=terms value=1%s> "
			      "I have read and "
			      "agree to these <a href=/terms.html>terms</a>."
			      , cs);
		if ( su->m_termsError[0] )
			sb.safePrintf("<br><font size=-1 "
				      "color=red>%s</font>",su->m_termsError);
		sb.safePrintf("</td></tr>");
	}
	
	sb.safePrintf("</table>");


	// so sendPageAccount() knows when it receives a submit
	sb.safePrintf("<input type=hidden name=submitted value=1>");

	if ( new1 )
		sb.safePrintf("<input type=hidden name=new value=1>");
	if ( edit )
		sb.safePrintf("<input type=hidden name=edit value=1>");



	sb.safePrintf("<br>"
		      "<a href=/account>Cancel</a> &nbsp; "
		      "<input type=submit name=submit value=OK>");

	sb.safePrintf("</center>");

	sb.safePrintf("</form>"
		      "</body>"
		      "</html>" );

	g_httpServer.sendDynamicPage ( s ,
				       sb.getBufStart(), 
				       sb.length(),
				       0 , // cachetime in secs
				       false , // postreply?
				       "text/html", // content type
				       -1, // http status -1->200
				       NULL, // cookiePtr,
				       "utf-8" );
	return true;
}

bool Proxy::saveUserBufs() {
	log("proxy: saving user bufs");
	if ( m_sumBuf.saveToFile ( g_hostdb.m_dir , "usersum.dat" ) < 0 )
		return false;
	if ( m_userInfoBuf.saveToFile ( g_hostdb.m_dir , "userinfo.dat" ) < 0 )
		return false;
	if ( m_depositBuf.saveToFile ( g_hostdb.m_dir,"userdeposit.dat" ) < 0 )
		return false;
	return true;
}

// and make summary rec table, "m_srht"
bool Proxy::loadUserBufs ( ) {

	// these return 0 if file does not exist, which may be the case
	if ( m_sumBuf.fillFromFile ( g_hostdb.m_dir , "usersum.dat" ) < 0 )
		return false;
	if ( m_userInfoBuf.fillFromFile(g_hostdb.m_dir , "userinfo.dat")<0)
		return false;
	if ( m_depositBuf.fillFromFile(g_hostdb.m_dir,"userdeposit.dat")< 0)
		return false;
	// scan users and zero out m_pending, we might have shutdown before
	// the operation could complete so do not charge for it
	UserInfo *uis = (UserInfo *)m_userInfoBuf.getBufStart();
	int32_t ni = m_userInfoBuf.length() / sizeof(UserInfo) ;
	int32_t i; for ( i = 0 ; i < ni ; i++ ) {
		// int16_tcut
		UserInfo *ui = &uis[i];
		if ( ui->m_pending == 0.0 ) continue;
		log("proxy: erasing pending=%.02f for uid=%" INT32 "",
		    ui->m_pending,ui->m_userId32);
		ui->m_pending = 0.0;
	}
	
	//
	// make the hashtable for summaryrecs
	//
	m_srht.reset();
	int32_t ns = m_sumBuf.length() / sizeof(SummaryRec);
	SummaryRec *ss = (SummaryRec *)m_sumBuf.getBufStart();
	int32_t needSlots = ns * 2;
	if ( ! m_srht.set(8,4,needSlots,NULL,0,false,0,"srectbl") ) {
		log("proxy: failed to alloc srht");
		return false;
	}
	for ( int32_t i = 0 ; i < ns ; i++ ) {
		// int16_tcut
		SummaryRec *sr = &ss[i];
		// get the offset
		int32_t sumOff = ((char *)sr) - m_sumBuf.getBufStart();
		// make key
		uint64_t h64 = (uint32_t)sr->m_userId32;
		uint32_t t = sr->m_year;
		t <<= 16;
		t |= sr->m_month; // 1..12
		t <<= 8;
		t |= sr->m_day; // 1..31
		t <<= 8;
		t |= sr->m_accessType;
		h64 <<= 32;
		h64 |= t;
		// add it
		if ( ! m_srht.addKey ( &h64 , &sumOff ) ) { // sr ) ) {
			log("proxy: failed to load user bufs");
			return false;
		}
	}
	// if first time, then initialize with records for our old clients
	// like existing clients
	int32_t nr = m_userInfoBuf.length() / sizeof(UserInfo);
	if ( nr >= 5 ) return true;

	// clear it all
	m_userInfoBuf.reset();

	int32_t userId = 1;

	// matt wells, admin login
	UserInfo ui;
	memset(&ui,0,sizeof(UserInfo));
	strcpy(ui.m_login,"admin");
	strcpy(ui.m_password,"admin123");
	strcpy(ui.m_xmlFeedCode,"admincode");
	strcpy(ui.m_email,"admin@admin.com");
	ui.m_flags = UIF_ADMIN;
	ui.m_accountBalance = 0.0;
	ui.m_userId32 = userId++;
	m_userInfoBuf.safeMemcpy(&ui,sizeof(UserInfo));

	// success!
	return true;
}


bool Proxy::printAccountingInfoPage ( StateUser *su , SafeBuf *errmsg ) {

	HttpRequest *hr = &su->m_hr;
	TcpSocket *socket = su->m_socket;

	// get the userinfo for logged in user
	//SafeBuf errmsg;
	// this will be NULL if userid32 is -1 (invalid, not logged in, etc.)
	UserInfo *ui = getUserInfoFromId ( su->m_userId32 );

	// print html page into this safebuf
	SafeBuf *sb = &su->m_sb;

	// ok, they are authenticated, show their account stats
	sb->safePrintf("<html>"
		       "<title>Gigablast - Account Information</title>"
		       "<body>"
		       );
	//insertLoginBarDirective ( sb );
	sb->safePrintf(

		       "<style><!--"
		       "a:link,a:visited{color:#0000ff;text-decoration:none;}"
		       "-->"
		       "</style>"
		       "<center>"
		       "<a href=/>"
		       "<img src=http://www.gigablast.com/logo-med.jpg "
		       "height=122 width=500>"
		       "</a>"
		       "</center>"
		       "<br>"
		       "<table width=100%% cellpadding=5 cellspacing=0 "
		       "border=0>"

		       "<tr bgcolor=#0340fd>"
		       "<th colspan=2>"
		       "<font color=33dcff>"
		       "Account Information</font>"
		       "</th>"
		       "</tr>"
		       "</table>"
		       "\n"
		       );


	if ( su->m_transactionSuccessful && su->m_deposit > 0.0 ) {
		sb->safePrintf("<tr><td colspan=10><b><font color=green>"
			       "%s "
			      "Successfully deposited $%.02f. "
			      "</font></b><br><br><hr><br>\n",
			       su->m_authNetMsg.getBufStart(),
			       su->m_deposit );
	}
	else if ( su->m_attemptedTransaction && su->m_refund > 0.0 ) {
		sb->safePrintf("<tr><td colspan=10><b><font color=red>"
			       "%s "
			      "Failed to refund $%.02f. "
			      "</font></b><br><br><hr><br>\n",
			       su->m_authNetMsg.getBufStart(),
			       su->m_refund );
	}
	else if ( su->m_attemptedTransaction ) {
		sb->safePrintf("<tr><td colspan=10><b><font color=red>"
			       "%s "
			       "Failed to charge $%.02f. "
			       "Please verify your credit card number, "
			       "credit card type, "
			       "3-digit CVV and expiration date."
			      "</font></b><br><br><hr><br>\n",
			       su->m_authNetMsg.getBufStart(),
			       su->m_deposit );
	}

	// likewise, put refund errors here too!
	if ( su->m_refundError[0] )
		sb->safePrintf("<tr><td colspan=10><b><font color=red>"
			       "<br><br>"
			       "%s "
			       "Failed to refund $%.02f. "
			       "</font></b><br><br><hr><br>\n",
			       su->m_refundError ,
			       su->m_refund );


	char *ccnum = ui->m_creditCardNum;
	if ( gbstrlen ( ccnum) > 12 ) ccnum = ccnum + 12;

	sb->safePrintf( 
		       "<br>"
			"<table cellpadding=6>"

			"<tr><td colspan=10>"
			//"<center>"
			"<b>User Information</b> [<a href=/account?edit=1>"
		       "edit</a>]"
			//"</center>"
			"</td></tr>"

		       "<tr>"
		       "<td>Username</td>"
		       "<td>%s</td>"
		       //"<td><a href=/account?logout=1><b>LOGOUT</b></a></td>"
		       "</tr>"

		       "<tr>"
		       "<td>Password</td>"
		       "<td>%s</td>"
		       "</tr>"

		       "<tr>"
		       "<td>Email</td>"
		       "<td>%s</td>"
		       "</tr>"

		       "<tr>"
		       "<td>Phone</td>"
		       "<td>%s</td>"
		       "</tr>"

		       "<tr>"
		       "<td>Credit Card #</td>"
		       "<td>************%s</td>"
		       "<td></td>"
		       "</tr>"


		       "<tr>"
		       "<td><nobr>User ID</nobr></td>"
		       "<td>%" INT32 "</td>"
		       "<td>See the <a href=/searchfeed.html>XML Search "
		       "Feed</a> page or the <a href=/seoapi.html>SEO API</a> "
		       "page for details on using this.</td>"
		       "</tr>"

		       "<tr>"
		       "<td><nobr>XML Feed Code</nobr></td>"
		       "<td>%s</td>"
		       "<td>See the <a href=/searchfeed.html>XML Search "
		       "Feed</a> page or the <a href=/seoapi.html>SEO API</a> "
		       "page for details on using this.</td>"
		       "</tr>"

			"</td>"
		       "</tr>"

		       "</table>\n"

			, ui->m_login
			, "*******"
			, ui->m_email
			, ui->m_phone
			// only display last 4 digits
			, ccnum
			, ui->m_userId32
			, ui->m_xmlFeedCode
		       //, ui->m_accountBalance
		       );

	if ( ui->m_userId32 == 1 && (ui->m_flags & UIF_ADMIN) ) {
		sb->safePrintf ( "<br>");
		sb->safePrintf ( "<hr>");
		sb->safePrintf ( "<br>");
		printUsers ( sb ) ;
	}


	///////////////
	//
	// DEPOSIT GUI
	//
	///////////////
	sb->safePrintf ( "<br>");
	sb->safePrintf ( "<hr>");
	sb->safePrintf ( "<br>");

	sb->safePrintf("Account Balance: &nbsp <b>$%.02f</b><br><br>"
		       , ui->m_accountBalance );

	if ( ui->m_pending != 0.0 )
		sb->safePrintf("Pending Actions: &nbsp <b>$%.02f</b><br><br>"
			       , ui->m_pending);


	sb->safePrintf(
		       "<table cellpadding=6>"
		       "<tr><td>"
		       "<form action=/account method=POST>"
		       "Make "
		       //"<select name=transtype>"
		       //"<option value=deposit selected>deposit</option>"
		       //"<option value=withdrawal>withdrawal</option>"
		       //"</select>"
		       "deposit of "
		       "<input type=text name=deposit value=\"%.02f\">"
		       ,MINCHARGE);


	// the admin can credit the account if he receives a wire or a check
	// from a user...
	/*
	if ( su->m_isMasterAdmin )
		sb->safePrintf("<br>"
			       "<font color=red>"
			       "Record Wire of "
			       "<input type=text name=wire value=\"\"> "
			       " Desc: <input type=text name=wiredesc>"
			       "<br>"
			       "Record Check of "
			       "<input type=text name=check value=\"\"> "
			       " Desc: <input type=text name=checkdesc>"
			       );
	*/


	sb->safePrintf(//"<input type=hidden name=makedeposit value=1>"
		       "<input type=submit "
		       "onclick=\""
		       "document.getElementById('gears')."
		       "style.display='';"
		       //"innerHTML="
		       //"'<img width=50 height=50 "
		       //"src=/gears.gif>';"
		       "\" "
		       "name=dotrans value=OK>"
		       );

	if ( su->m_depositError[0] )
		sb->safePrintf("<br><font size=-1 color=red>%s</font>",
			       su->m_depositError);

	sb->safePrintf("</td><td>"
		       "<div style=display:none; id=gears>"
		       "<img width=50 height=50 src=/gears.gif></div>"
		       "</form>\n"
		       "</td></tr></table>\n"
		       );
	//sb->safePrintf("<br>");


	////////////////
	//
	// DEPOSIT TABLE
	//
	// . all the deposits and withdrawls they've ever made
	//
	////////////////
	sb->safePrintf ( "<br>");
	sb->safePrintf ( "<hr>");
	sb->safePrintf ( "<br>");
	printDepositTable ( sb , ui->m_userId32 );


	int32_t cutoff;
	bool printedSomething;
	SummaryRec *srs = (SummaryRec *)m_sumBuf.getBufStart();
	int32_t nsr = m_sumBuf.length() / sizeof(SummaryRec);


	///////////////
	//
	// DAILY BREAKDOWN
	//
	// print daily query stats for selected month
	//
	//////////////
	sb->safePrintf ( "<br>");
	sb->safePrintf ( "<hr>");
	sb->safePrintf ( "<br>");
	int32_t now = getTimeLocal();
	struct tm *timeStruct = gmtime ( (time_t *)&now );
	int32_t currentMonth = timeStruct->tm_mon+1; // 1 to 12
	int32_t currentYear  = timeStruct->tm_year+1900;
	int32_t currentDay   = timeStruct->tm_mday;
	static char *s_mnames[12] = {
		"January",
		"February",
		"March",
		"April",
		"May",
		"June",
		"July",
		"August",
		"September",
		"October",
		"November",
		"December"
	};
	int32_t month = hr->getLong("month",currentMonth);
	int32_t year  = hr->getLong("year",currentYear);
	int32_t day   = hr->getLong("day",currentDay);
	if ( month < 1 ) month = 1;
	if ( month > 12 ) month = 12;
	if ( day < 1 ) day = 1;
	if ( day > 31 ) day = 31;
	char *monthName = s_mnames[month-1];
	sb->safePrintf( "<table width=600px cellpadding=6 border=0>"
			"<tr>"
		       "<td colspan=2>"
			"<b>"
			//"<center>"
		       "Daily Breakdown for %s %" INT32 ""
			//"</center>"
			"</b>"
		       "</td>"
		       "</tr>"
		       // the first column in this pane splitter table
		       , monthName
		       , year
		       );
	cutoff = sb->length();
	// transaction table in left pane
	sb->safePrintf ( "<tr>"
			"<td>DAY</td>"
			"<td>Total Cost</td>"
			"<td>Total Accesses</td>"
			"<td>Description</td>"
			"</tr>"
			);
	// scan daily transactions for that month and this user
	//SummaryRec *srs = (SummaryRec *)m_sumBuf.getBufStart();
	//int32_t nsr = m_sumBuf.length() / sizeof(SummaryRec);
	printedSomething = false;
	for ( int32_t i = 0 ; i < nsr ; i++ ) {
		SummaryRec *sr = &srs[i];
		if ( sr->m_userId32 != ui->m_userId32 ) continue;
		//if ( sr->m_day   != day   ) continue;
		if ( sr->m_month != month ) continue;
		if ( sr->m_year  != year  ) continue;
		char *as = getAccessTypeString(sr->m_accessType);
		// they should be in order by date...
		printedSomething = true;
		sb->safePrintf("<tr>"
			       //"<td>%02" INT32 "/%02" INT32 "</td>"
			       "<td>%02" INT32 "</td>"
			       "<td>$%.02f</td>"
			       "<td>%" INT32 "</td>"
			       "<td>%s</td>" // "new search feed", etc.
			       "</tr>\n"
			       //, (int32_t)sr->m_month
			       , (int32_t)sr->m_day
			       , sr->m_totalCost
			       , sr->m_numAccesses
			       , as
			       );
	}
	if ( ! printedSomething ) {
		sb->setLength ( cutoff );
		sb->safePrintf("<tr><td colspan=3><font size=-1>"
			       "<i>There is no data for "
			       "this because you have not accessed the feed "
			       "yet using your XML Feed Code</i>"
			       "</font></td></tr>");
	}
	sb->safePrintf("</table>\n");



	/////////
	//
	// print feed access MONTHLY SUMMARY table
	//
	////////
	sb->safePrintf ( "<br>");
	sb->safePrintf ( "<hr>");
	sb->safePrintf ( "<br>");
	sb->safePrintf ( "<table width=800px cellpadding=6>"

			 "<tr><td colspan=10>"
			 //"<center>"
			 "<b>Monthly Summary</b>"
			 //"</center>"
			 "</td></tr>"
			 );
	cutoff = sb->length();
	sb->safePrintf( "<tr>"
			"<td>Date</td>"
			"<td>Description</td>"
			"<td># Accesses</td>"
			"<td>Total Cost</td>"
			"<td>Cost Per Access</td>"
			"<td>Avg. Process Time</td>"
			"</tr>"
			);
	// . add up SummaryRec stats for each month and transaction type
	//   for this user
	// . scan daily transactions for that month and this user
	// . go out 20 years for each access type
	int32_t numCursors = (MAX_ACCESS_TYPE+1) * 20 * 12;
	int32_t need = numCursors * sizeof(SummaryRec);
	char tmp[need];
	SafeBuf ss(tmp,need);
	// reset to all zeros
	ss.zeroOut();
	// pointers
	SummaryRec *cursors = (SummaryRec *)ss.getBufStart();
	// . maybe just make a hash key of each summary rec based on
	//   it's month and year and access type
	for ( int32_t i = 0 ; i < nsr ; i++ ) {
		SummaryRec *sr = &srs[i];
		if ( sr->m_userId32 != ui->m_userId32 ) continue;
		// we go out 20 years
		int32_t index = (int32_t)sr->m_accessType * (12*20);
		index += (sr->m_year - 2013) * 12;
		// we use 1..12 for month range, so subtract 1
		index += sr->m_month - 1;
		// now get it
		SummaryRec *cursor = &cursors[index];
		// just in case it is our first time hitting "cursor"
		cursor->m_accessType   = sr->m_accessType;
		cursor->m_month        = sr->m_month;
		cursor->m_year         = sr->m_year;
		cursor->m_day          = 0; // we are accumulating all days!
		// accumulate access stats for that day
		cursor->m_numAccesses      += sr->m_numAccesses;
		cursor->m_totalCost        += sr->m_totalCost;
		cursor->m_totalProcessTime += sr->m_totalProcessTime;
	}
	printedSomething = false;
	// now print out the cursors for each month in reverse!
	for ( int32_t i = numCursors - 1 ; i >= 0 ; i-- ) {
		// int16_tcut
		SummaryRec *cursor = &cursors[i];
		// 0 is not a valid access type
		if ( cursor->m_accessType == 0 ) continue;
		// print it otherwise
		char *as = getAccessTypeString(cursor->m_accessType);
		printedSomething = true;
		sb->safePrintf("<tr>"
			       "<td>%02" INT32 "/%04" INT32 "</td>"
			       "<td><nobr>%s</nobr></td>"
			       "<td>%" INT32 "</td>"
			       "<td>$%.02f</td>"
			       "<td>$%.05f</td>"// price per access
			       "<td>%" INT32 "ms</td>"
			       "</td>"
			       , (int32_t)cursor->m_month
			       , (int32_t)cursor->m_year
			       , as
			       , cursor->m_numAccesses
			       , cursor->m_totalCost
			       , cursor->m_totalCost / 
			       (float)cursor->m_numAccesses
			       , (int32_t)(cursor->m_totalProcessTime /
					(float)cursor->m_numAccesses)
			       );
	}
	if ( ! printedSomething ) {
		sb->setLength ( cutoff );
		sb->safePrintf("<tr><td colspan=3><font size=-1>"
			       "<i>There is no data for "
			       "this because you have not accessed the feed "
			       "yet using your XML Feed Code</i>"
			       "</font></td></tr>");
	}

	// end monthly summary table
	sb->safePrintf("</table>");




	// needs this for cookie setting
	su->m_socket    = socket;


	////////////
	//
	// pretty graph in right pane of query latency and shit i guess
	//
	////////////
	//
	// convert to timestamps in seconds
	struct tm timeStruct2 ;
	timeStruct2.tm_mday = day; // 1 to 31
	timeStruct2.tm_year = year - 1900;
	timeStruct2.tm_mon  = month - 1; // 0 to 11
	timeStruct2.tm_hour = 0;
	timeStruct2.tm_sec  = 0;
	time_t t1 = mktime ( &timeStruct2 ); // utc i guess
	int32_t nextYear = year;
	int32_t nextMonth = month;
	if ( nextMonth >= 13 ) {
		nextMonth = 1;
		nextYear++;
	}
	timeStruct2.tm_mon  = nextMonth - 1; // 0 to 11
	timeStruct2.tm_year = nextYear  - 1900;
	time_t t2 = mktime ( &timeStruct2 ); // utc i guess

	if ( ! g_statsdb.makeGIF ( t2 , // end
				   t1 , // start
				   10 , // samples in moving avg...
				   &su->m_sb2,
				   su,
				   gotGifWrapper  ,
				   su->m_userId32 ) )
		return false;

	return gotGif ( su );
}



bool Proxy::gotGif ( StateUser *su ) {

	SafeBuf *sb = &su->m_sb;
	HttpRequest *hr = &su->m_hr;

	// cat the gif on
	sb->cat ( su->m_sb2 );

	sb->safePrintf("<br><br>"
		       "<center>"
		       "Copyright &copy; 2013. All rights reserved."
		       "</center>"
		       "</body>"
		       "</html>"
		       );

	// sanity!
	if ( su->m_sessionId64 < 0 ) { char *xx=NULL;*xx=0; }

	// make a cookie in case they just logged in through this page!
	// otherwise the login doesn't "stick"
	SafeBuf cb;
	cb.safePrintf("Set-Cookie: sessionid=%" INT64 ";\r\n"
		      "Set-Cookie: userid=%" INT32 ";\r\n"
		      ,su->m_sessionId64
		      ,(int32_t)su->m_userId32);

	// if admin logs in as another user by clicking on their login name
	// in the users table, we still need to know that it is the admin,
	// so use this special cookie
	UserInfo *ui = getUserInfoFromId ( su->m_userId32 );
	if ( ui && (ui->m_flags & UIF_ADMIN) )
		cb.safePrintf("Set-Cookie: adminsessid=%" INT64 ";\r\n"
			      ,su->m_sessionId64);

	char *cookiePtr = NULL;
	if ( cb.length() ) cookiePtr = cb.getBufStart();
	
	// . send this page
	// . encapsulates in html header and tail
	// . make a Mime
	g_httpServer.sendDynamicPage ( su->m_socket, 
				       sb->getBufStart(), 
				       sb->length(),
				       0 , // cachetime in secs
				       false , // postreply?
				       "text/html", // content type
				       -1, // http status -1->200
				       cookiePtr,
				       "utf-8" ,
				       hr );

	// i guess it copies the safebuf contents..
	mdelete(su,sizeof(StateUser),"usprox");
	delete(su);
	return true;
}


bool Proxy::printDepositTable ( SafeBuf *sb , int32_t userId32 ) {
	int32_t nd = m_depositBuf.length() / sizeof(DepositRec);
	DepositRec *drs = (DepositRec *)m_depositBuf.getBufStart();

	UserInfo *ui = getUserInfoFromId ( userId32 );	

	sb->safePrintf("<table cellpadding=6>"
		       "<tr><td colspan=3>"
		       "<b>Transaction Table</b>"
		       "</td></tr>"
		       
		       "<tr>"
		       "<td>Time</td>"
		       //"<td>TransId</td>"
		       "<td>Amount</td>"
		       "<td>Description</td>"
		       "<td>Transaction ID</td>"
		       
		       "</tr>"
		       );
	for ( int32_t i = nd - 1 ; i >= 0 ; i-- ) {
		DepositRec *dr = &drs[i];
		if ( dr->m_userId32 != userId32 ) continue;
		// convert timestamp to month/day/year in utc
		time_t tt = dr->m_depositDate;//getTimeLocal();
		struct tm *timeStruct = gmtime ( &tt );
		sb->safePrintf("<tr><td>"
			       "%" INT32 "/%" INT32 "/%" INT32 " %" INT32 ":%02" INT32 ""
			       "</td>"
			       , (int32_t)(timeStruct->tm_mon + 1) // month 0..11
			       , (int32_t)timeStruct->tm_mday //day of month 1..31
			       , (int32_t)(timeStruct->tm_year+1900) // year
			       , (int32_t)(timeStruct->tm_hour)
			       , (int32_t)(timeStruct->tm_min)
			       );
		//sb->safePrintf("<td>%" INT32 "</td>",dr->m_transactionId);
		char *bs1 = "<font color=green>";
		char *bs2 = "</font>";
		// is it a withdrawl?
		if ( dr->m_depositAmount < 0.0 ) {
			bs1 = "<font color=red>";
			bs2 = "</font>";
		}
		char *desc = "unknown";
		if ( dr->m_flags == DRF_DEPOSIT  ) desc = "deposit";
		if ( dr->m_flags == DRF_WITHDRAW ) desc = "withdraw";
		if ( dr->m_flags == DRF_WITHDRAW_FEE ) desc = "withdraw fee";
		sb->safePrintf("<td>%s%.02f%s</td>"
			       "<td>%s</td>"
			       "<td>%" INT64 "</td>"
			       , bs1 
			       , dr->m_depositAmount 
			       , bs2 
			       , desc
			       , dr->m_authorizeNetTransactionId
			       );
		float refundAmt = dr->m_depositAmount;
		if ( refundAmt > ui->m_accountBalance )
			// subtract a penny because account balance
			// might be off by a fraction of a penny
			// and it will say that you are trying to
			// refund more than the account balance
			refundAmt = ui->m_accountBalance - .01 ;
		// there is a 5% refund charge
		//refundAmt *= 0.95;
		sb->safePrintf("<td><a href=/account?refund=%.02f&"
			       "transid=%" INT64 ">refund"
			       "</a></td>"
			       , refundAmt 
			       , dr->m_authorizeNetTransactionId
			       );
		sb->safePrintf("</tr>\n");
	}
	sb->safePrintf("</table>\n");
	return true;
}


#define BARSIZE 250

/*
// put a 100 byte directive into th src
bool Proxy::insertLoginBarDirective ( SafeBuf *sb ) {
	if ( ! sb->reserve(BARSIZE) ) return false;
	int32_t len = sb->length();
	//sb.safePrintf("<div style=display:none>%%login%%</div>");
	char *dir = "%%login%%";
	int32_t dlen = gbstrlen(dir);
	sb->safeMemcpy(dir,dlen);
	int32_t inserted = sb->length() - len;
	// make directive exactly 180 bytes...
	int32_t remain = BARSIZE - inserted;
	memset (sb->getBuf() , ' ' , remain );
	sb->incrementLength ( remain );
	return true;
}
*/

char *Proxy::storeLoginBar ( char *reply , 
			     int32_t replySize , 
			     int32_t replyAllocSize,
			     int32_t mimeLen,
			     //int32_t userId32 ,
			     int32_t *newReplySize ,
			     HttpRequest *hr ) {

	// assume new reply identical to old reply
	char *newReply = reply;
	*newReplySize = replySize;

	// did mime have error in reply?
	// if so, do not insert login bar
	if ( strcmp(reply,"HTTP/1.0 ") == 0 ) {
		int32_t httpStatus = atol2(reply+9,3);
		if ( httpStatus != 200 ) return reply;
	}

	// userid is in cookie
	int32_t userId32 = hr->getLongFromCookie("userid",0);
	UserInfo *ui = getUserInfoFromId ( userId32 );
	int64_t sessionId64 = hr->getLongLongFromCookie("sessionid",0);
	if ( ui && ui->m_lastSessionId64 != sessionId64 ) ui = NULL;


	SafeBuf cu;
	char *currentUrl = "";
	if ( hr ) {
		hr->getCurrentUrl ( cu );
		currentUrl = cu.getBufStart();
	}


	//return reply;

	// if too small, just return the original reply
	char *content = reply + mimeLen;
	char *contentEnd = reply + replySize;
	int32_t contentLen = contentEnd - content;
	if ( contentLen < 20 ) return newReply;

	// find <body tag in the reply
	char *p = content;
	int32_t maxLen = 3000;
	if ( contentLen < maxLen ) maxLen = contentLen;
	char *pend = content + maxLen;
	// not necessarily \0 terminated, so do not over-scan
	pend -= 10;
	for ( ; p < pend ; p++ ) {
		if ( p[0] != '<' ) continue;
		if ( p[1] != 'b' ) continue;
		if ( p[2] != 'o' ) continue;
		if ( p[3] != 'd' ) continue;
		if ( p[4] != 'y' ) continue;
		// get it!
		break;
	}
	// return if did not find directive
	if ( p >= pend ) return newReply;

	// scan to end of body tag
	for ( ; p < pend && *p != '>' ; p++ );

	// no end of body tag?
	if ( p >= pend ) return newReply;

	// skip that
	p++;

	// make the insertion
	SafeBuf ib;

	// if not logged in... print login link
	if ( ! ui ) {
		ib.safePrintf ( "<table width=100%%><tr><td align=right>"
				"[<a style=decoration:none; href=/account?"
				"follow="
				);
		ib.urlEncode ( currentUrl );
		ib.safePrintf( ">"
			       "login</a>]</td></tr></table>");
	}

	else {
		// if logged in print user name (38 bytes)
		ib.safePrintf("<table width=100%%><tr><td align=right>"
			      "logged in as <b>"
			      "<a href=/account>");
		ib.safeStrcpy ( ui->m_login);
		ib.safePrintf("</a></b> [<a style=decoration:none; "
			      "href=/account?logout=1&follow=");
		ib.urlEncode ( currentUrl );
		ib.safePrintf( ">logout</a>]</td></tr></table>");
	}

	// how much do we need?
	int32_t need = replySize + ib.length();
	// a \0 terminating i guess
	//need++;

	// make a new one
	SafeBuf sb;
	if ( ! sb.reserve ( need ) ) {
		log("proxy: failed to store login bar");
		return newReply;
	}

	// copy up to end of body tag
	sb.safeMemcpy ( reply ,  p - reply );
	// then the insertion
	sb.safeMemcpy ( &ib );
	// then the rest
	sb.safeMemcpy ( p , contentEnd - p );
	// then the \0 i guess
	//sb.addSilentNull();
	//sb.reserve ( 1 );

	// all done. +1 for the \0
	mfree ( reply , replyAllocSize , "prrepl");

	// extricate
	newReply = sb.getBufStart();
	*newReplySize = sb.length();
	sb.detachBuf();

	// fix overreading!
	// fix the fucking content-length in the mime...
	char *mp = strnstr(newReply,"Content-Length:",*newReplySize);
	if ( ! mp ) mp = strnstr(newReply,"Content-length:",*newReplySize);
	if ( ! mp ) {
		log("proxy: fuck, no content-length: in mime");
		int32_t len = *newReplySize;
		if ( len > 300 ) len = 300;
		len--;
		if ( len <= 0 ) {
			log("proxy: fuck, 0 length reply");
			return newReply;
		}
		char c = newReply[len];
		newReply[len] = '\0';
		log("proxy: fuck: reply=%s",newReply);
		newReply[len] = c;
		return newReply;
	}

	// temp fix take it out because it is not working right
	mp[0] = 'x';
	return newReply;

	// point to first digit in there
	mp += 16;
	// store our new content length as ascii into test buf
	char test[64];

	int32_t len =sprintf(test,"%" INT32 "",(int32_t)(*newReplySize-mimeLen));
	// find end
	char *end = mp;
	while ( *end && is_digit(*end) ) end++;
	// copy backwards then
	char *src = test + len - 1;
	for ( ; src >= test ; src-- , end-- )
		*end = *src;
	// that should be it!!
	return newReply;
}

void Proxy::printUsers ( SafeBuf *sb ) {

	sb->safePrintf( "<table width=600px cellpadding=6 border=0>"
			"<tr>"
			"<td colspan=2>"
			"<b>"
			//"<center>"
			"User Table"
			//"</center>"
			"</b>"
			"</td>"
			"</tr>"
			);

	UserInfo *uis = (UserInfo *)m_userInfoBuf.getBufStart();
	int32_t ni = m_userInfoBuf.length() / sizeof(UserInfo) ;
	int32_t i; for ( i = 0 ; i < ni ; i++ ) {
		// int16_tcut
		UserInfo *ui = &uis[i];
		// begin new row?
		if ( i % 5 == 0 ) {
			if ( i > 0 ) sb->safePrintf("</tr>");
			sb->safePrintf("<tr>");
		}
		// if admin clicks this link then he logs in as that user,
		// but if admin we should still have set our cookie
		// adminsessid to our current session id so we know we are
		// also the admin!
		sb->safePrintf("<td><nobr>%" INT32 ". "
			       "<a href=/account?login=%s&password=%s>"
			       "%s</a></nobr></td>"
			       ,i
			       ,ui->m_login
			       ,ui->m_password 
			       ,ui->m_login
			       //,ui->m_userId32
			       );
	}
	sb->safePrintf("</tr>\n");
	sb->safePrintf("</table>\n");
}