/*
 * Copyright (c) 2002, 2003, 2004, Scott Nicol <esniper@users.sf.net>
 * All rights reserved
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * - Redistributions of source code must retain the above copyright notice,
 *   this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/* for strcasestr  prototype in string.h */
#define _GNU_SOURCE

#include "util.h"
#include "auction.h"
#include "buffer.h"
#include "http.h"
#include "html.h"
#include "history.h"
#include <ctype.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <time.h>
#if defined(WIN32)
#	define strcasecmp(s1, s2) stricmp((s1), (s2))
#	define sleep(t) _sleep((t) * 1000)
#	define strncasecmp(s1, s2, len) strnicmp((s1), (s2), (len))
#else
#	include <unistd.h>
#endif

#define newRemain(aip) (aip->endTime - time(NULL) - aip->latency - options.bidtime)

#define TOKEN_FOUND_UIID (1 << 0)
#define TOKEN_FOUND_STOK (1 << 1)
#define TOKEN_FOUND_SRT (1 << 2)

#define TOKEN_FOUND_ALL (TOKEN_FOUND_UIID | TOKEN_FOUND_STOK | TOKEN_FOUND_SRT)

typedef struct _headerattr
{
  char* name;
  int   occurence;
  int   direction;
  int   mandatory;
  char* value;
} headerAttr_t, headerVal_t;

typedef enum searchType { st_attribute, st_value } searchType_t;

static time_t loginTime = 0;	/* Time of last login */
static time_t defaultLoginInterval = 12 * 60 * 60;	/* ebay login interval */

static int acceptBid(const char *pagename, auctionInfo *aip);
static int bid(auctionInfo *aip);
static int ebayLogin(auctionInfo *aip, time_t interval);
static int findAttr(char* src, size_t srcLen, headerAttr_t* attr);
static int forceEbayLogin(auctionInfo *aip);
static char *getIdInternal(char *s, size_t len);
static int getInfoTiming(auctionInfo *aip, time_t *timeToFirstByte);
static int getQuantity(int want, int available);
static int getVals(char* src, size_t srcLen, headerVal_t* vals);
static int makeBidError(const pageInfo_t *pageInfo, auctionInfo *aip);
static int match(memBuf_t *mp, const char *str);
static int parseBid(memBuf_t *mp, auctionInfo *aip);
static int preBid(auctionInfo *aip);
static int parsePreBid(memBuf_t *mp, auctionInfo *aip);
static int printMyItemsRow(char **row, int printNewline);
static int signinFormSearch(char* src, size_t srcLen, headerAttr_t* searchdef, searchType_t searchfor);
static int watch(auctionInfo *aip);

/*
 * attempt to match some input, neglecting case, ignoring \r and \n.
 * returns 0 on success, -1 on failure
 */
static int
match(memBuf_t *mp, const char *str)
{
	const char *cursor;
	int c;

	log(("\n\nmatch(\"%s\")\n\n", str));

	cursor = str;
	while ((c = memGetc(mp)) != EOF) {
		if (options.debug)
			logChar(c);
		if (tolower(c) == (int)*cursor) {
			if (*++cursor == '\0') {
				if (options.debug)
					logChar(EOF);
				return 0;
			}
		} else if (c != '\n' && c != '\r')
			cursor = str;
	}
	if (options.debug)
		logChar(EOF);
	return -1;
}

static const char PAGEID[] = "Page id: ";
static const char SRCID[] = "srcId: ";

/*
 * Get page info, including pagename variable, page id and srcid comments.
 */
pageInfo_t *
getPageInfo(memBuf_t *mp)
{
	const char *line;
	pageInfo_t p = {NULL, NULL, NULL}, *pp;
	int needPageName = 1;
	int needPageId = 1;
	int needSrcId = 1;
	int needMore = 3;
	char *title = NULL;

	log(("getPageInfo():\n"));
	memReset(mp);
	while (needMore && (line = getTag(mp))) {
		char *tmp;

		if (!strcasecmp(line, "title") ||
                    !strcasecmp(line, "h1 class=\"page-title__main\"")) {  /* Version newer than 2.33.0 */
		    line = getNonTag(mp);
		    if (line) title = myStrdup(line);
		    continue;
		}
		if (strncmp(line, "!--", 3))
			continue;
		if (needPageName && (tmp = strstr(line, PAGENAME))) {
			if ((tmp = getPageNameInternal(tmp))) {
				--needMore;
				--needPageName;
				p.pageName = myStrdup(tmp);
			}
		} else if (needPageId && (tmp = strstr(line, PAGEID))) {
			if ((tmp = getIdInternal(tmp, sizeof(PAGEID)))) {
				--needMore;
				--needPageId;
				p.pageId = myStrdup(tmp);
			}
		} else if (needSrcId && (tmp = strstr(line, SRCID))) {
			if ((tmp = getIdInternal(tmp, sizeof(SRCID)))) {
				--needMore;
				--needSrcId;
				p.srcId = myStrdup(tmp);
			}
		}
	}
	if (needPageName && title) {
	   log(("using title as page name: %s", title));
	   p.pageName = title;
	   --needPageName;
	   --needMore;
	   title = NULL;
	}
	if (title) free(title);
	log(("getPageInfo(): pageName = %s, pageId = %s, srcId = %s\n", nullStr(p.pageName), nullStr(p.pageId), nullStr(p.srcId)));
	memReset(mp);
	if (needMore == 3)
		return NULL;
	pp = (pageInfo_t *)myMalloc(sizeof(pageInfo_t));
	pp->pageName = p.pageName;
	pp->pageId = p.pageId;
	pp->srcId = p.srcId;
	return pp;
}

static char *
getIdInternal(char *s, size_t len)
{
	char *id = s + len - 1;
	char *dash = strchr(id, '-');

	if (!*dash) {
		log(("getIdInternal(): Cannot find trailing dash: %s\n", id));
		return NULL;
	}
	*dash = '\0';
	log(("getIdInternal(): id = %s\n", id));
	return id;
}

/*
 * Free a pageInfo_t and it's internal members.
 */
void
freePageInfo(pageInfo_t *pp)
{
	if (pp) {
		free(pp->pageName);
		free(pp->pageId);
		free(pp->srcId);
		free(pp);
	}
}

/*
 * Calculate quantity to bid on.  If it is a dutch auction, never
 * bid on more than 1 less item than what is available.
 */
static int
getQuantity(int want, int available)
{
	if (want == 1 || available == 1)
		return 1;
	if (available > want)
		return want;
	return available - 1;
}

static const char HISTORY_URL[] = "http://%s/ws/eBayISAPI.dll?ViewBids&item=%s";

/*
 * getInfo(): Get info on auction from bid history page.
 *
 * returns:
 *	0 OK
 *	1 error (badly formatted page, etc) set auctionError
 */
int
getInfo(auctionInfo *aip)
{
	return getInfoTiming(aip, NULL);
}

/*
 * getInfoTiming(): Get info on auction from bid history page.
 *
 * returns:
 *	0 OK
 *	1 error (badly formatted page, etc) set auctionError
 */
static int
getInfoTiming(auctionInfo *aip, time_t *timeToFirstByte)
{
	int i, ret;
	time_t start;

	log(("\n\n*** getInfo auction %s price %s user %s\n", aip->auction, aip->bidPriceStr, options.username));
	if (ebayLogin(aip, 0))
		return 1;

	for (i = 0; i < 3; ++i) {
		memBuf_t *mp = NULL;

		if (!aip->query) {
			size_t urlLen = sizeof(HISTORY_URL) + strlen(options.historyHost) + strlen(aip->auction) - (2*2);

			aip->query = (char *)myMalloc(urlLen);
			sprintf(aip->query, HISTORY_URL, options.historyHost, aip->auction);
		}
		start = time(NULL);
		if (!(mp = httpGet(aip->query, NULL))) {
			freeMembuf(mp);
			return httpError(aip);
		}
		ret = parseBidHistory(mp, aip, start, timeToFirstByte, 0);
		freeMembuf(mp);
		if (i == 0 && ret == 1 && aip->auctionError == ae_mustsignin) {
			if (forceEbayLogin(aip))
				break;
		} else if (aip->auctionError == ae_notime)
			/* Blank time remaining -- give it another chance */
			sleep(2);
		else
			break;
	}
	return ret;
}

/*
 * Note: quant=1 is just to dupe eBay into allowing the pre-bid to get
 *	 through.  Actual quantity will be sent with bid.
 */
static const char PRE_BID_URL[] = "http://%s/ws/eBayISAPI.dll?MfcISAPICommand=MakeBid&fb=2&co_partner_id=&item=%s&maxbid=%s&quant=%s";

/*
 * Get bid key
 *
 * returns 0 on success, 1 on failure.
 */
static int
preBid(auctionInfo *aip)
{
	memBuf_t *mp = NULL;
	int quantity = getQuantity(options.quantity, aip->quantity);
	char quantityStr[12];	/* must hold an int */
	size_t urlLen;
	char *url;
	int ret = 0;

	if (ebayLogin(aip, 0))
		return 1;
	sprintf(quantityStr, "%d", quantity);
	urlLen = sizeof(PRE_BID_URL) + strlen(options.prebidHost) + strlen(aip->auction) + strlen(aip->bidPriceStr) + strlen(quantityStr) - (4*2);
	url = (char *)myMalloc(urlLen);
	sprintf(url, PRE_BID_URL, options.prebidHost, aip->auction, aip->bidPriceStr, quantityStr);
	log(("\n\n*** preBid(): url is %s\n", url));
	mp = httpGet(url, NULL);
	free(url);
	if (!mp)
		return httpError(aip);

	ret = parsePreBid(mp, aip);
	freeMembuf(mp);
	return ret;
}

static int
parsePreBid(memBuf_t *mp, auctionInfo *aip)
{
	int ret = 0;
	int found = 0; //Used as binary store - 1=uiid,10=stok,100=srt

	memReset(mp);
	while (!match(mp, "name=\"uiid\"")) {
		char *start, *value, *end;

		for (start = mp->readptr; start >= mp->memory && *start != '<'; --start)
			;
		value = strcasestr(start, "value=\"");
		end = strchr(start, '>');

		if (!value || !end || value > end)
			continue;
		free(aip->biduiid);
		mp->readptr = value + 7;
		aip->biduiid = myStrdup(getUntil(mp, '\"'));
		log(("preBid(): biduiid is \"%s\"", aip->biduiid));
		found |= TOKEN_FOUND_UIID;
		break;
	}

	memReset(mp);
	while (!match(mp, "name=\"stok\"")) {
		char *start, *value, *end;

		for (start = mp->readptr; start >= mp->memory && *start != '<'; --start)
			;
		value = strcasestr(start, "value=\"");
		end = strchr(start, '>');

		if (!value || !end || value > end)
			continue;
		free(aip->bidstok);
		mp->readptr = value + 7;
		aip->bidstok = myStrdup(getUntil(mp, '\"'));
		log(("preBid(): bidstok is \"%s\"", aip->bidstok));
		found |= TOKEN_FOUND_STOK;
		break;
	}

	memReset(mp);
	while (!match(mp, "name=\"srt\"")) {
		char *start, *value, *end;

		for (start = mp->readptr; start >= mp->memory && *start != '<'; --start)
			;
		value = strcasestr(start, "value=\"");
		end = strchr(start, '>');

		if (!value || !end || value > end)
			continue;
		free(aip->bidsrt);
		mp->readptr = value + 7;
		aip->bidsrt = myStrdup(getUntil(mp, '\"'));
		log(("preBid(): bidsrt is \"%s\"", aip->bidsrt));
		found |= TOKEN_FOUND_SRT;
		break;
	}

	if ((found & TOKEN_FOUND_ALL) != TOKEN_FOUND_ALL) {
		pageInfo_t *pageInfo = getPageInfo(mp);

		ret = makeBidError(pageInfo, aip);
		if (ret < 0) {
			ret = auctionError(aip, ae_bidtokens, NULL);
			bugReport("preBid", __FILE__, __LINE__, aip, mp, optiontab, "cannot find bid token (found=%d)", found);
		}
		freePageInfo(pageInfo);
	}
	return ret;
}

static const char LOGIN_1_URL[] = "https://%s/ws/eBayISAPI.dll?SignIn";
static const char LOGIN_2_URL[] = "https://%s/ws/eBayISAPI.dll?co_partnerId=2&siteid=0&UsingSSL=1";
static const char LOGIN_DATA[] = "rqid=%s&lkdhjebhsjdhejdshdjchquwekguid=%s&refId=&regUrl=%s&MfcISAPICommand=SignInWelcome&bhid=DEF_CI&UsingSSL=1&inputversion=2&lse=false&lsv=&mid=%s&kgver=1&kgupg=1&kgstate=&omid=&hmid=&rhr=f&srt=%s&siteid=0&co_partnerId=2&ru=&pp=&pa1=&pa2=&pa3=&i1=-1&pageType=-1&rtmData=&usid=%s&afbpmName=sess1&kgct=&userid_otp=&sgnBt=Continue&otp=&keepMeSignInOption3=1&userid=%s&%s=%s&runId2=%s&%s=%s&pass=%s&keepMeSignInOption2=1&keepMeSignInOption=1&htmid=&kdata=";

static const char* id="id=\"";
static const char* id2="value=\"";

static const int USER_NUM=0;
static const int PASS_NUM=1;

static const int RQID=0;
static const int GUID=1;
static const int REGURL=2;
static const int MID=3;
static const int SRT=4;
static const int USID=5;
static const int RUNID2=6;

static headerAttr_t headerAttrs[] = {"<label for=\"userid\"", 1, 1, 1, NULL,
                            "\"password\"", 1, -1, 1, NULL};

static headerVal_t headerVals[] = {"rqid", 1, 1, 1, NULL,
                           "lkdhjebhsjdhejdshdjchquwekguid", 1, 1, 1, NULL,
                           "regUrl", 1, 1, 0, NULL,
                           "mid", 1, 1, 1, NULL,
                           "srt", 1, 1, 1, NULL,
                           "usid", 1, 1, 1, NULL,
                           "runId2", 1, 1, 0, NULL};

static int
signinFormSearch(char* src, size_t srcLen, headerAttr_t* searchdef, searchType_t searchfor)
{
	char* start = src;
	char* end = src + srcLen;
	char* search = NULL;
	char pattern[128];
	char res[4096];
	int  i;

	if(searchfor == st_attribute)
		strcpy(pattern, searchdef->name);
	else
		sprintf(pattern, "name=\"%s\"", searchdef->name);

	for(i = 0; i < searchdef->occurence; i++) {
		search = strstr(start, pattern);
		if( search == NULL )
		{
			searchdef->value = (char *)myMalloc(1);
			strncpy(searchdef->value, "\0", 1);
			return searchdef->mandatory;
		}
		start = search;
		start += strlen(pattern);
	}

	while(src != search && end != search ) {
                search += (searchdef->direction);

		if(!strncmp(search, (searchfor == st_attribute ? id : id2), 
                                       (searchfor == st_attribute ? strlen(id) : strlen(id2))) ) {
			search += (searchfor == st_attribute ? strlen(id) : strlen(id2));
			memset(res, '\0', sizeof(res));
			for(i = 0; (*search) != '"' && i < sizeof(res); res[i++] = *search++);
			searchdef->value = (char *)myMalloc(strlen(res) + 1);
			strncpy(searchdef->value, (char*) &res, strlen(res) + 1);
			if (options.debug)
				dlog("%s(): %s=%s", (searchfor == st_attribute ? "findAttr" : "searchvalue"), 
					searchdef->name, searchdef->value);
			return 0;
		}
	}

	if( searchdef->value == NULL )
        {
            searchdef->value = (char *)myMalloc(1);
            strncpy(searchdef->value, "\0", 1);
            return searchdef->mandatory;
        }
	return searchdef->mandatory;
}

static int
findAttr(char* src, size_t srcLen, headerAttr_t* attr)
{
	return signinFormSearch(src, srcLen, attr, st_attribute);
}

static int
getVals(char* src, size_t srcLen, headerVal_t* vals)
{
	return signinFormSearch(src, srcLen, vals, st_value);
}

/*
 * Force an ebay login.
 *
 * Returns 0 on success, 1 on failure.
 */
static int
forceEbayLogin(auctionInfo *aip)
{
	loginTime = 0;
	return ebayLogin(aip, 0);
}

/*
 * Ebay login.  Make sure loging has been done with the given interval.
 *
 * Returns 0 on success, 1 on failure.
 */
static int
ebayLogin(auctionInfo *aip, time_t interval)
{
	memBuf_t *mp = NULL;
	size_t urlLen;
	char *url, *data, *logdata;
	pageInfo_t *pp;
	int ret = 0;
	char *password;
	int i;	

	/* negative value forces login */
	if (loginTime > 0) {
		if (interval == 0)
			interval = defaultLoginInterval;	/* default: 12 hours */
		if ((time(NULL) - loginTime) <= interval)
			return 0;
	}

	cleanupCurlStuff();
	if (initCurlStuff())
		return auctionError(aip, ae_unknown, NULL);

        urlLen = sizeof(LOGIN_2_URL) + strlen(options.loginHost) - (1*2);
        url = (char *)myMalloc(urlLen);
        sprintf(url, LOGIN_2_URL, options.loginHost);
        mp = httpPost(url, NULL, NULL); /* Send fake login in order to get wanted html data */
        free(url);

	if (!mp)
		return httpError(aip);

	// Get all attributes and values needed
	for(i = 0; i < sizeof(headerAttrs)/sizeof(headerAttr_t); i++)
		if(findAttr(mp->memory, mp->size, &headerAttrs[i]))
			bugReport("ebayLogin", __FILE__, __LINE__, aip, mp, optiontab,
				"findAttr cannot find %s", headerAttrs[i].name);
	for(i = 0; i < sizeof(headerVals)/sizeof(headerVal_t); i++)
		if(getVals(mp->memory, mp->size, &headerVals[i]))
			bugReport("ebayLogin", __FILE__, __LINE__, aip, mp, optiontab,
				"getVals cannot find %s", headerVals[i].name);

	freeMembuf(mp);
	mp = NULL;

	urlLen = sizeof(LOGIN_2_URL) + strlen(options.loginHost) - (1*2);
	password = getPassword();
	url = (char *)myMalloc(urlLen);
	sprintf(url, LOGIN_2_URL, options.loginHost);
	data = (char *)myMalloc(	sizeof(LOGIN_DATA)
                                      + strlen(headerAttrs[USER_NUM].value)
                                      + strlen(headerAttrs[PASS_NUM].value)
                                      + strlen(options.usernameEscape) * 2
                                      + strlen(password) * 2
                                      + strlen(headerVals[RQID].value)
                                      + strlen(headerVals[GUID].value)
                                      + strlen(headerVals[REGURL].value)
                                      + strlen(headerVals[MID].value)
                                      + strlen(headerVals[SRT].value)
                                      + strlen(headerVals[USID].value)
                                      + strlen(headerVals[RUNID2].value)
				      - (11*2)
                                      );
	logdata = (char *)myMalloc(	sizeof(LOGIN_DATA)
                                      + strlen(headerAttrs[USER_NUM].value)
                                      + strlen(headerAttrs[PASS_NUM].value) 
                                      + strlen(options.usernameEscape) * 2
                                      + 5 * 2
                                      + strlen(headerVals[RQID].value)
                                      + strlen(headerVals[GUID].value)
                                      + strlen(headerVals[REGURL].value)
                                      + strlen(headerVals[MID].value)
                                      + strlen(headerVals[SRT].value)
                                      + strlen(headerVals[USID].value)
                                      + strlen(headerVals[RUNID2].value)
				      - (11*2)
                                      );
	sprintf(data, LOGIN_DATA,	headerVals[RQID].value,
					headerVals[GUID].value,
					headerVals[REGURL].value,
					headerVals[MID].value,
					headerVals[SRT].value,
					headerVals[USID].value,
					options.usernameEscape,
					headerAttrs[USER_NUM].value,
					options.usernameEscape,
					headerVals[RUNID2].value,
                                        headerAttrs[PASS_NUM].value,
					password,
					password
					);
	freePassword(password);
	sprintf(logdata, LOGIN_DATA,	headerVals[RQID].value,
					headerVals[GUID].value,
					headerVals[REGURL].value,
					headerVals[MID].value,
					headerVals[SRT].value,
					headerVals[USID].value,
					options.usernameEscape,
					headerAttrs[USER_NUM].value,
					options.usernameEscape,
					headerVals[RUNID2].value,
                                        headerAttrs[PASS_NUM].value,
					"*****",
					"*****"
					);

	// Using POST method instead of GET
	mp = httpPost(url, data, logdata);

	// Free memory
	for(i=0; i < sizeof(headerAttrs)/sizeof(headerAttr_t); free(headerAttrs[i++].value));
	for(i=0; i < sizeof(headerVals)/sizeof(headerVal_t); free(headerVals[i++].value));
	free(url);
	free(data);
	free(logdata);

	if (!mp)
		return httpError(aip);

	if ((pp = getPageInfo(mp))) {
		log(("ebayLogin(): pagename = \"%s\", pageid = \"%s\", srcid = \"%s\"", nullStr(pp->pageName), nullStr(pp->pageId), nullStr(pp->srcId)));
		/*
		 * Pagename is usually MyeBaySummary, but it seems as though
		 * it can be any MyeBay page, and eBay is not consistent with
		 * naming of MyeBay pages (MyeBay, MyEbay, myebay, ...) so
		 * esniper must use strncasecmp().
		 */
		if ((pp->srcId && !strcmp(pp->srcId, "SignInAlertSupressor"))||
		    (pp->pageName &&
			(!strncasecmp(pp->pageName, "MyeBay", 6) ||
			 !strncasecmp(pp->pageName, "My eBay", 7) ||
			 !strncasecmp(pp->pageName, "Watch list", 10) ||
			 !strncasecmp(pp->pageName, "Purchase History", 16) ||
			 !strncasecmp(pp->pageName, " Black Friday", 13) ||
			 !strncasecmp(pp->pageName, "Black Friday", 12) ||
			 !strncasecmp(pp->pageName, "Electronics", 11) ||
                         !strncasecmp(pp->pageName, "eBay: Update your contact info", 30)) 
		    ))
			loginTime = time(NULL);
		else if (pp->pageName &&
				(!strcmp(pp->pageName, "Welcome to eBay") ||
				 !strcmp(pp->pageName, "Welcome to eBay - Sign in - Error") ||
				 !strcmp(pp->pageName, "Welcome to eBay - Error") ||
				 !strcmp(pp->pageName, "Sign in or Register | eBay - Error")))
			ret = auctionError(aip, ae_badpass, NULL);
		else if (pp->pageName && !strcmp(pp->pageName, "PageSignIn"))
			ret = auctionError(aip, ae_login, NULL);
		else if (pp->pageName && !strcmp(pp->pageName, "Reset your password"))
			ret = auctionError(aip, ae_manualaction, NULL);
		else if (pp->srcId && !strcmp(pp->srcId, "Captcha.xsl"))
			ret = auctionError(aip, ae_captcha, NULL);
		else {
			ret = auctionError(aip, ae_login, NULL);
			bugReport("ebayLogin", __FILE__, __LINE__, aip, mp, optiontab, "unknown pageinfo");
		}
	} else {
		log(("ebayLogin(): pageinfo is NULL\n"));
		ret = auctionError(aip, ae_login, NULL);
		bugReport("ebayLogin", __FILE__, __LINE__, aip, mp, optiontab, "pageinfo is NULL");
	}
	freeMembuf(mp);
	freePageInfo(pp);
	return ret;
}

/*
 * acceptBid: handle all known AcceptBid pages.
 *
 * Returns -1 if page not recognized, 0 if bid accepted, 1 if bid not accepted.
 */
static int
acceptBid(const char *pagename, auctionInfo *aip)
{
	static const char ACCEPTBID[] = "AcceptBid_";
	static const char HIGHBID[] = "HighBidder";
	static const char OUTBID[] = "Outbid";
	static const char RESERVENOTMET[] = "ReserveNotMet";

	if (!strcmp(pagename, "Bid confirmation"))
		return aip->bidResult = 0;

	if (!pagename ||
	    strncmp(pagename, ACCEPTBID, sizeof(ACCEPTBID) - 1))
		return -1;
	pagename += sizeof(ACCEPTBID) - 1;

	/*
	 * valid pagenames include AcceptBid_HighBidder,
	 * AcceptBid_HighBidder_rebid, possibly others.
	 */
	if (!strncmp(pagename, HIGHBID, sizeof(HIGHBID) - 1))
		return aip->bidResult = 0;
	/*
	 * valid pagenames include AcceptBid_Outbid, AcceptBid_Outbid_rebid,
	 * possibly others.
	 */
	if (!strncmp(pagename, OUTBID, sizeof(OUTBID) - 1))
		return aip->bidResult = auctionError(aip, ae_outbid, NULL);
	/*
	 * valid pagenames include AcceptBid_ReserveNotMet,
	 * AcceptBid_ReserveNotMet_rebid, possibly others.
	 */
	if (!strncmp(pagename, RESERVENOTMET, sizeof(RESERVENOTMET) - 1))
		return aip->bidResult = auctionError(aip, ae_reservenotmet, NULL);
	/* unknown AcceptBid page */
	return -1;
}

/*
 * makeBidError: handle all known MakeBidError pages.
 *
 * Returns -1 if page not recognized, 0 if bid accepted, 1 if bid not accepted.
 */
static int
makeBidError(const pageInfo_t *pageInfo, auctionInfo *aip)
{
	static const char MAKEBIDERROR[] = "MakeBidError";
	const char *pagename = pageInfo->pageName;

	if (!pagename) {
		const char *srcId = pageInfo->srcId;

		if (srcId && !strcasecmp(srcId, "ViewItem"))
			return aip->bidResult = auctionError(aip, ae_ended, NULL);
		else
			return -1;
	}
	if (!strcasecmp(pagename, "Place bid"))
		return aip->bidResult = auctionError(aip, ae_outbid, NULL);
	if (!strcasecmp(pagename, "eBay Alerts"))
		return aip->bidResult = auctionError(aip, ae_alert, NULL);
	if (!strcasecmp(pagename, "Buyer Requirements"))
		return aip->bidResult = auctionError(aip, ae_buyerrequirements, NULL);

	if (!strcasecmp(pagename, "PageSignIn"))
		return aip->bidResult = auctionError(aip, ae_mustsignin, NULL);
	if (!strncasecmp(pagename, "BidManager", 10) ||
	    !strncasecmp(pagename, "BidAssistant", 12))
		return aip->bidResult = auctionError(aip, ae_bidassistant, NULL);

	if (strncasecmp(pagename, MAKEBIDERROR, sizeof(MAKEBIDERROR) - 1))
		return -1;
	pagename += sizeof(MAKEBIDERROR) - 1;
	if (!*pagename ||
	    !strcasecmp(pagename, "AuctionEnded"))
		return aip->bidResult = auctionError(aip, ae_ended, NULL);
	if (!strcasecmp(pagename, "AuctionEnded_BINblock") ||
	    !strcasecmp(pagename, "AuctionEnded_BINblock "))
		return aip->bidResult = auctionError(aip, ae_cancelled, NULL);
	if (!strcasecmp(pagename, "Password"))
		return aip->bidResult = auctionError(aip, ae_badpass, NULL);
	if (!strcasecmp(pagename, "MinBid"))
		return aip->bidResult = auctionError(aip, ae_bidprice, NULL);
	if (!strcasecmp(pagename, "BuyerBlockPref"))
		return aip->bidResult = auctionError(aip, ae_buyerblockpref, NULL);
	if (!strcasecmp(pagename, "BuyerBlockPrefDoesNotShipToLocation"))
		return aip->bidResult = auctionError(aip, ae_buyerblockprefdoesnotshiptolocation, NULL);
	if (!strcasecmp(pagename, "BuyerBlockPrefNoLinkedPaypalAccount"))
		return aip->bidResult = auctionError(aip, ae_buyerblockprefnolinkedpaypalaccount, NULL);
	if (!strcasecmp(pagename, "HighBidder"))
		return aip->bidResult = auctionError(aip, ae_highbidder, NULL);
	if (!strcasecmp(pagename, "CannotBidOnItem"))
		return aip->bidResult = auctionError(aip, ae_cannotbid, NULL);
	if (!strcasecmp(pagename, "DutchSameBidQuantity"))
		return aip->bidResult = auctionError(aip, ae_dutchsamebidquantity, NULL);
	if (!strcasecmp(pagename, "BuyerBlockPrefItemCountLimitExceeded"))
		return aip->bidResult = auctionError(aip, ae_buyerblockprefitemcountlimitexceeded, NULL);
	if (!strcasecmp(pagename, "BidGreaterThanBin_BINblock"))
		return aip->bidResult = auctionError(aip, ae_bidgreaterthanbin_binblock, NULL);
	/* unknown MakeBidError page */
	return -1;
}

/*
 * Parse bid result.
 *
 * Returns:
 * 0: OK
 * 1: error
 */
static int
parseBid(memBuf_t *mp, auctionInfo *aip)
{
	/*
	 * The following sometimes have more characters after them, for
	 * example AcceptBid_HighBidder_rebid (you were already the high
	 * bidder and placed another bid).
	 */
	pageInfo_t *pageInfo = getPageInfo(mp);
	int ret;

	aip->bidResult = -1;
	log(("parseBid(): pagename = %s\n", pageInfo->pageName));
	if ((ret = acceptBid(pageInfo->pageName, aip)) >= 0 ||
	    (ret = makeBidError(pageInfo, aip)) >= 0) {
		;
	} else {
		bugReport("parseBid", __FILE__, __LINE__, aip, mp, optiontab, "unknown pagename");
		printLog(stdout, "Cannot determine result of bid\n");
		ret = 0;	/* prevent another bid */
	}
	freePageInfo(pageInfo);
	return ret;
} /* parseBid() */

static const char BID_URL[] = "http://%s/ws/eBayISAPI.dll?MfcISAPICommand=MakeBid&maxbid=%s&quant=%s&mode=1&uiid=%s&co_partnerid=2&user=%s&fb=2&item=%s&stok=%s&srt=%s";

/*
 * Place bid.
 *
 * Returns:
 * 0: OK
 * 1: error
 */
static int
bid(auctionInfo *aip)
{
	memBuf_t *mp = NULL;
	size_t urlLen;
	char *url, *logUrl, *tmpUsername, *tmpUiid, *tmpStok, *tmpSrt;
	int ret;
	int quantity = getQuantity(options.quantity, aip->quantity);
	char quantityStr[12];	/* must hold an int */

	if (!aip->biduiid || !aip->bidstok || !aip->bidsrt)
		return auctionError(aip, ae_bidtokens, NULL);

	if (ebayLogin(aip, 0))
		return 1;
	sprintf(quantityStr, "%d", quantity);

	/* create url */
	urlLen = sizeof(BID_URL) + strlen(options.bidHost) + strlen(aip->bidPriceStr) + strlen(quantityStr) + strlen(aip->biduiid) + strlen(options.usernameEscape) + strlen(aip->auction) + strlen(aip->bidstok) + strlen(aip->bidsrt) - (8*2);
	url = (char *)myMalloc(urlLen);
	sprintf(url, BID_URL, options.bidHost, aip->bidPriceStr, quantityStr, aip->biduiid, options.usernameEscape, aip->auction, aip->bidstok, aip->bidsrt);

	logUrl = (char *)myMalloc(urlLen);
	tmpUsername = stars(strlen(options.usernameEscape));
	tmpUiid = stars(strlen(aip->biduiid));
	tmpStok = stars(strlen(aip->bidstok));
	tmpSrt = stars(strlen(aip->bidsrt));
	sprintf(logUrl, BID_URL, options.bidHost, aip->bidPriceStr, quantityStr, tmpUiid, tmpUsername, aip->auction, tmpStok, tmpSrt);
	free(tmpUsername);
	free(tmpUiid);
    free(tmpStok);
    free(tmpSrt);

	if (!options.bid) {
		printLog(stdout, "Bidding disabled\n");
		log(("\n\nbid(): query url:\n%s\n", logUrl));
		ret = aip->bidResult = 0;
	} else if (!(mp = httpGet(url, logUrl))) {
		ret = httpError(aip);
	} else {
		ret = parseBid(mp, aip);
	}
	free(url);
	free(logUrl);
	freeMembuf(mp);
	return ret;
} /* bid() */

/*
 * watch(): watch auction until it is time to bid
 *
 * returns:
 *	0 OK
 *	1 Error
 */
static int
watch(auctionInfo *aip)
{
	int errorCount = 0;
	long remain = LONG_MIN;
	unsigned int sleepTime = 0;

	log(("*** WATCHING auction %s price-each %s quantity %d bidtime %ld\n", aip->auction, aip->bidPriceStr, options.quantity, options.bidtime));

	for (;;) {
		time_t tmpLatency;
		time_t start = time(NULL);
		time_t timeToFirstByte = 0;
		int ret = getInfoTiming(aip, &timeToFirstByte);
		time_t end = time(NULL);

		if (timeToFirstByte == 0)
			timeToFirstByte = end;
		tmpLatency = (timeToFirstByte - start);
		if ((tmpLatency >= 0) && (tmpLatency < 600))
			aip->latency = tmpLatency;
		printLog(stdout, "Latency: %d seconds\n", aip->latency);

		if (ret) {
			printAuctionError(aip, stderr);

			/*
			 * Fatal error?  We allow up to 50 errors, then quit.
			 * eBay "unavailable" doesn't count towards the total.
			 */
			if (aip->auctionError == ae_unavailable) {
				if (remain >= 0)
					remain = newRemain(aip);
				if (remain == LONG_MIN || remain > 86400) {
					/* typical eBay maintenance period
					 * is two hours.  Sleep for half that
					 * amount of time.
					 */
					printLog(stdout, "%s: Will try again, sleeping for an hour\n", timestamp());
					sleepTime = 3600;
					sleep(sleepTime);
					continue;
				}
			} else if (remain == LONG_MIN) {
				/* first time through?  Give it 3 chances then
				 * make the error fatal.
				 */
				int j;

				for (j = 0; ret && j < 3 && aip->auctionError == ae_notitle; ++j)
					ret = getInfo(aip);
				if (ret)
					return 1;
				remain = newRemain(aip);
			} else {
				/* non-fatal error */
				log(("ERROR %d!!!\n", ++errorCount));
				if (errorCount > 50)
					return auctionError(aip, ae_toomany, NULL);
				printLog(stdout, "Cannot find auction - internet or eBay problem?\nWill try again after sleep.\n");
				remain = newRemain(aip);
			}
		} else if (!isValidBidPrice(aip))
			return auctionError(aip, ae_bidprice, NULL);
		else
			remain = newRemain(aip);

		/*
		 * Check login when we are close to bidding.
		 */
		if (remain <= 300) {
			if (ebayLogin(aip, defaultLoginInterval - 600))
				return 1;
			remain = newRemain(aip);
		}

		/*
		 * if we're less than two minutes away, get bid key
		 */
		if (remain <= 150 && !aip->biduiid && aip->auctionError == ae_none) {
			int i;

			printf("\n");
			for (i = 0; i < 5; ++i) {
				/* ae_bidtokens is used when the page loaded
				 * but failed for some unknown reason.
				 * Do not try again in this situation.
				 */
				if (!preBid(aip) ||
				    aip->auctionError == ae_bidtokens)
					break;
				if (aip->auctionError == ae_mustsignin &&
				    forceEbayLogin(aip))
					break;
			}
			if (aip->auctionError != ae_none &&
			    aip->auctionError != ae_highbidder) {
				printLog(stderr, "Cannot get bid key\n");
				return 1;
			}
		}

		remain = newRemain(aip);

		/* it's time!!! */
		if (remain <= 0)
			break;

		/*
		 * Setup sleep schedule so we get updates once a day, then
		 * at 2 hours, 1 hour, 5 minutes, 2 minutes
		 */
		if (remain <= 150)	/* 2 minutes + 30 seconds (slop) */
			sleepTime = (unsigned int)remain;
		else if (remain < 720)	/* 5 minutes + 2 minutes (slop) */
			sleepTime = (unsigned int)remain - 120;
		else if (remain < 3900)	/* 1 hour + 5 minutes (slop) */
			sleepTime = (unsigned int)remain - 600;
		else if (remain < 10800)/* 2 hours + 1 hour (slop) */
			sleepTime = (unsigned int)remain - 3600;
		else if (remain < 97200)/* 1 day + 3 hours (slop) */
			sleepTime = (unsigned int)remain - 7200;
		else			/* knock off one day */
			sleepTime = 86400;

		printf("%s: ", timestamp());
		if (sleepTime >= 86400)
			printLog(stdout, "Sleeping for a day\n");
		else if (sleepTime >= 3600)
			printLog(stdout, "Sleeping for %d hours %d minutes\n",
				sleepTime/3600, (sleepTime % 3600) / 60);
		else if (sleepTime >= 60)
			printLog(stdout, "Sleeping for %d minutes %d seconds\n",
				sleepTime/60, sleepTime % 60);
		else
			printLog(stdout, "Sleeping for %ld seconds\n", sleepTime);
		sleep(sleepTime);
		printf("\n");

		if ((remain=newRemain(aip)) <= 0)
			break;
	}
	return 0;
} /* watch() */

/*
 * parameters:
 * aip	auction to bid on
 *
 * return number of items won
 */
int
snipeAuction(auctionInfo *aip)
{
	int won = 0;
	char *tmpUsername;

	if (!aip)
		return 0;

	if (options.debug)
		logOpen(aip, options.logdir);

	tmpUsername = stars(strlen(options.username));
	log(("auction %s price %s quantity %d user %s bidtime %ld\n",
	     aip->auction, aip->bidPriceStr,
	     options.quantity, tmpUsername, options.bidtime));
	free(tmpUsername);

	if (ebayLogin(aip, 0)) {
		printAuctionError(aip, stderr);
		return 0;
	}

	/* 0 means "now" */
	if ((options.bidtime == 0) ? preBid(aip) : watch(aip)) {
		printAuctionError(aip, stderr);
		if (aip->auctionError != ae_highbidder)
			return 0;
	}

	/* ran out of time! */
	if (aip->endTime <= time(NULL)) {
		(void)auctionError(aip, ae_ended, NULL);
		printAuctionError(aip, stderr);
		return 0;
	}

	if (aip->auctionError != ae_highbidder) {
		printLog(stdout, "\nAuction %s: Bidding...\n", aip->auction);
		for (;;) {
			if (bid(aip)) {
				/* failed bid */
				if (aip->auctionError == ae_mustsignin) {
					if (!forceEbayLogin(aip))
						continue;
				}
				printAuctionError(aip, stderr);
				return 0;
			}
			break;
		}
	}

	/* view auction after bid.
	 * Stick it in a loop in case our timing is a bit off (due
	 * to wild swings in latency, for instance).
	 */
	for (;;) {
		if (options.bidtime > 0 && options.bidtime < 60) {
			time_t seconds = aip->endTime - time(NULL);

			if (seconds < 0)
				seconds = 0;
			/* extra 2 seconds to make sure auction is over */
			seconds += 2;
			printLog(stdout, "Auction %s: Waiting %d seconds for auction to complete...\n", aip->auction, seconds);
			sleep((unsigned int)seconds);
		}

		printLog(stdout, "\nAuction %s: Post-bid info:\n",
			 aip->auction);
		if (getInfo(aip))
			printAuctionError(aip, stderr);
		if (aip->remain > 0 && aip->remain < 60 &&
		    options.bidtime > 0 && options.bidtime < 60)
			continue;
		break;
	}

	if (aip->won == -1) {
		won = options.quantity < aip->quantity ?
			options.quantity : aip->quantity;
		printLog(stdout, "\nunknown outcome, assume that you have won %d items\n", won);
	} else {
		won = aip->won;
		printLog(stdout, "\nwon %d item(s)\n", won);
	}
	options.quantity -= won;
	return won;
}

/* Max \td in the description table (is 8 on 02 of May 2010): */
#define MAX_TDS 8
#define MAX_TDS_LENGTH 6

/*
 * On first call, use printNewline to 0.  On subsequent calls, use return
 * value from previous call.
 */
static int
printMyItemsRow(char **row, int printNewline)
{
	const char *myitems_description[MAX_TDS][MAX_TDS_LENGTH] = {
		{0, 0, 0, 0, 0, 0},
		{0, 0, 0, 0, 0, 0},
		{"Description:\t%s\n", 0, "Seller:\t\t%s", 0, 0," (%s)\n"},
		{ "Bids:\t\t%s\n", 0, 0, 0, 0, 0},
		{ "Price:\t\t%s\n", 0, 0, 0, 0, 0},
		{ "Shipping:\t%s\n", 0, 0, 0, 0, 0},
		{ "Time left:\t%s\n", 0, 0, 0, 0, 0},
		{ 0, 0, 0, 0, 0, 0},
	};
	int column = 0;
	int ret = printNewline;
	int item_nr=0;	/* count no_tag item */

	for (; row[column]; ++column) {
		memBuf_t buf;
		char *value = NULL;

		if (column == 0) { /* item nr on checkbox in 1st (-1) column */
			static const char search[] = "value=";
			char *tmp = strstr(row[column], search);

			if (tmp) {
				int i;

				tmp += sizeof(search) - 1;
				for (; !isdigit(*tmp); ++tmp)
					;
				for (i = 1; isdigit(tmp[i]); ++i)
					;
				value = myStrndup(tmp, (size_t)(i));
				printLog(stdout, "ItemNr:\t\t%s\n", value);
				free(value);
			}
		}
		strToMemBuf(row[column], &buf); /* load new row */
		for (item_nr = 0; item_nr < MAX_TDS_LENGTH; item_nr++) {
			value = getNonTag(&buf);

			/* there may be a "ENDING SOON" message */
			if ((column==2)&&(item_nr==0)&&strstr(value,"ENDING SOON"))
				value = getNonTag(&buf);
			/* when nothing interesting in row */
			if (column >= MAX_TDS || !myitems_description[column][item_nr])
				continue;
			/* print the entry */
			printLog(stdout, myitems_description[column][item_nr], value ? value : "");
		}
	}
	printf("\n");	/* for spacing */
	return ret;
}

static const char MYITEMS_URL[] = "http://%s/ws/eBayISAPI.dll?MyeBay&CurrentPage=MyeBayWatching";

/*
 * TODO: allow user configuration of myItems.
 */
int
printMyItems(void)
{
	memBuf_t *mp = NULL;
	const char *table;
	char **row;
	auctionInfo *dummy = newAuctionInfo("0", "0");
	char *url;
	size_t urlLen;

	if (ebayLogin(dummy, 0)) {
		printAuctionError(dummy, stderr);
		freeAuction(dummy);
		return 1;
	}
	urlLen = sizeof(MYITEMS_URL) + strlen(options.myeBayHost) - (1*2);
	url = (char *)myMalloc(urlLen);
	sprintf(url, MYITEMS_URL, options.myeBayHost);
	mp = httpGet(url, NULL);
	free(url);
	if (!mp) {
		httpError(dummy);
		printAuctionError(dummy, stderr);
		freeAuction(dummy);
		freeMembuf(mp);
		return 1;
	}
	while ((table = getTableStart(mp))) {
		int printNewline = 0;

		/* search for table containing my itmes */
		if (!strstr(table, "class=\"my_itl-iT\""))
			continue;
		/* skip first descriptive table row */
		if ((row = getTableRow(mp)))
			freeTableRow(row);
		else {
			freeAuction(dummy);
			return 0; /* error? */
		}
		while ((row = getTableRow(mp))) {
			printNewline = printMyItemsRow(row, printNewline);
			freeTableRow(row);
		}
	}
	freeAuction(dummy);
	freeMembuf(mp);
	return 0;
}

/* secret option - test parser */
void
testParser(int flag)
{
	memBuf_t *mp = readFile(stdin);

	switch (flag) {
	case 1:
	    {
		/* print pagename */
		char *line;

		/* dump non-tag data */
		while ((line = getNonTag(mp)))
			printf("\"%s\"\n", line);

		/* pagename? */
		memReset(mp);
		if ((line = getPageName(mp)))
			printf("\nPAGENAME is \"%s\"\n", line);
		else
			printf("\nPAGENAME is NULL\n");
		break;
	    }
	case 2:
	    {
		/* run through bid history parser */
		auctionInfo *aip = newAuctionInfo("1", "2");
		time_t start = time(NULL), end;
		int ret = parseBidHistory(mp, aip, start, &end, 1);

		printf("ret = %d\n", ret);
		printAuctionError(aip, stdout);
		break;
	    }
	case 3:
	    {
		/* run through bid result parser */
		auctionInfo *aip = newAuctionInfo("1", "2");
		int ret = parseBid(mp, aip);

		printf("ret = %d\n", ret);
		printAuctionError(aip, stdout);
		break;
	    }
	case 4:
	    {
		/* print bid history table */
		const char *table;
		char **row;
		char *cp;
		int rowNum = 0;

		while ((cp = getNonTag(mp))) {
			if (!strcmp(cp, "Time left:"))
				break;
			if (!strcmp(cp, "Time Ended:"))
				break;
		}
		if (!cp) {
			printf("time left not found!\n");
			break;
		}
		(void)getTableStart(mp); /* skip one table */
		table = getTableStart(mp);
		if (!table) {
			printf("no table found!\n");
			break;
		}

		printf("table: %s\n", table);
		while ((row = getTableRow(mp))) {
			int columnNum = 0;

			printf("\trow %d:\n", rowNum++);
			for (; row[columnNum]; ++columnNum) {
				memBuf_t buf;

				strToMemBuf(row[columnNum], &buf);
				printf("\t\tcolumn %d: %s\n", columnNum, getNonTag(mp));
				free(row[columnNum]);
			}
		}
		break;
	    }
	case 5:
		{
		/* run through prebid parser */
		auctionInfo *aip = newAuctionInfo("1", "2");
		int ret = parsePreBid(mp, aip);

		printf("ret = %d\n", ret);
		printf("uiid = %s\n", aip->biduiid);
		printAuctionError(aip, stdout);
		break;
		}
	}
}
