
// Curlinet.cpp : implementation file
//
#include "stdafx.h"
#include "DsLavSplit.h"
#include "Curlinet.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

////////////////////////////////////////////////////////////////////////
// Class CCurlinet
CCurlinet::CCurlinet()
{
	// Curl in Thread members.
	m_hwndMain = NULL;
	m_hwndDlg = NULL;
    m_pEvent = NULL;
//	m_inThread = FALSE;
	m_mbDownloaded = -1;

	// 9/25/2021 - test fix 
	// Test fix runaway .m3u download
	m_nBytesDownloaded = 0;
	m_nMaxDownloadBytes = 0;
}
CCurlinet::~CCurlinet()
{
}

// Not used by DsLav
// Fetches reponse from strUrl - stores in default results.txt file.
BOOL CCurlinet::GetInternetData(CString strUrl, CString & strErr)
{
	TRACE(_T("Entering CCurlinet::GetInternetData - default file path\n"));

	// Get current directory.
	CString curDir = ((CDsLavApp*)AfxGetApp())->m_curDirectory;;
	CString filePath = curDir;

	// Add file name.
	filePath += _T("\\results.txt");
	return GetInternetData(strUrl, filePath, strErr);
}

// Not used by DsLav
// Works with _MBCS defined, no go with _UNICODE.
// Fetches reponse from strUrl - stores in filePath file.
BOOL CCurlinet::GetInternetData(CString strUrl, CString filePath, CString & strErr)
{
	TRACE(_T("Entering CCurlinet::GetInternetData\n"));

	// Get current directory.
	CString curDir = ((CDsLavApp*)AfxGetApp())->m_curDirectory;
	CString certPathCA;
	CString certPathPEM;
	BOOL ret = TRUE;

	// Open disc file.
	CFileException e;
	if(!m_file.Open(filePath, 
				    CFile::modeCreate | CFile::modeWrite,
				    &e)) 
	{
		TRACE(_T("CCurlinet::GetInternetData - can not open file %s, error = %u\n"), 
					filePath, e.m_cause );
		return FALSE;
	}
    CURL * curl;
    CURLcode result;

    // In windows, this will init the winsock stuff.
	// todo - do once in constructor?
	//  See: https://curl.se/libcurl/c/curl_global_init.html
	//  See: https://github.com/curl/curl/issues/3990
	// Note: This function is not thread safe.
    curl_global_init(CURL_GLOBAL_ALL);

 	TRACE(_T("CCurlinet::GetInternetData calling curl_easy_init\n"));

	// Get a CURL easy handle.
    curl = curl_easy_init();
 
    if(curl)
    {
		// Provide the URL to use in the request.
		//  See: https://curl.se/libcurl/c/CURLOPT_URL.html 
		curl_easy_setopt(curl, CURLOPT_URL, strUrl); 

		// Pass a pointer to a linked list of HTTP headers to pass to 
		// the server - NULL
		//  See: https://curl.se/libcurl/c/CURLOPT_HTTPHEADER.html.
		curl_easy_setopt(curl, CURLOPT_HEADER, 0);

		// Pass a pointer to class static callback function.
		//  See: https://curl.se/libcurl/c/CURLOPT_WRITEFUNCTION.html
		curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CCurlinet::CurlCallback);

		// Must tell the library to follow any redirect location. If not 
		// set, callback func may not be called, as curl only does what 
		// you tell it to do. Avoids a 301 reponse code.
		//  See: https://curl.se/libcurl/c/CURLOPT_FOLLOWLOCATION.html
		curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, TRUE);

		// Provide certificates stored in:
		//  \DsLavSplit\Curl_Local\bin\xxxcert
		//  See!: https://stackoverflow.com/questions/25041021/ssl-peer-certificate-or-
		//         ssh-remote-key-was-not-ok-c
		//
		// Set cert path for curl-ca-bundle.crt:
		certPathCA = curDir;
		certPathCA += _T("\\Curl_Local\\bin\\curl-ca-bundle.crt");
		curl_easy_setopt(curl, CURLOPT_CAPATH, certPathCA);

		// Set cert path for cacert.pem
		certPathPEM = curDir;
		certPathPEM += _T("\\Curl_Local\\bin\\cacert.pem");
		curl_easy_setopt(curl, CURLOPT_CAINFO, certPathPEM); 

		// Ask for an HTTP GET request (use a GET to fetch this).
		//  See: https://curl.se/libcurl/c/CURLOPT_HTTPGET.html
		curl_easy_setopt(curl, CURLOPT_HTTPGET, 1);

		// A data pointer to pass to the write callback. If you use 
		// the CURLOPT_WRITEFUNCTION option, this is the pointer you'll 
		// get in that callback's 4th argument.
		//  See: https://curl.se/libcurl/c/CURLOPT_WRITEDATA.html
		//  See: Curl FAQ: 5.14 Using C++ non-static functions for callbacks?
		curl_easy_setopt(curl, CURLOPT_WRITEDATA, this);

		TRACE(_T("CCurlinet::GetInternetData calling curl_easy_perform\n"));

		// Perform the HTTP get.
		result = curl_easy_perform(curl);
		
		TRACE(_T("CCurlinet::GetInternetData - curl_easy_perform returned %d\n"), result);

		// Debug - check results.
		char*  raw_url;
		long   response_code;
		double elapsed;
		double uploaded_bytes;
		double downloaded_bytes;

		curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);	// 301
		curl_easy_getinfo(curl, CURLINFO_TOTAL_TIME, &elapsed);				// 0.547
		curl_easy_getinfo(curl, CURLINFO_EFFECTIVE_URL, &raw_url);			// https://microsoft.com
		curl_easy_getinfo(curl, CURLINFO_SIZE_DOWNLOAD, &downloaded_bytes);	// 0
		curl_easy_getinfo(curl, CURLINFO_SIZE_UPLOAD, &uploaded_bytes);		// 0
		TRACE(_T("CCurlinet::GetInternetData - response code %d\n"), response_code);
		TRACE(_T("CCurlinet::GetInternetData - elapsed time %f\n"), elapsed);
		TRACE(_T("CCurlinet::GetInternetData - raw url %s\n"), raw_url);
		TRACE(_T("CCurlinet::GetInternetData - up bytes %f\n"), uploaded_bytes);
		TRACE(_T("CCurlinet::GetInternetData - down bytes %f\n"), downloaded_bytes);

		// Must cleanup handle.
		curl_easy_cleanup(curl);

		// Success.
		if(result == CURLE_OK) // = 0
		{
			//AfxMessageBox(_T("Download complete."));
		}
		// Error - get info. 
		//  See http://curl.haxx.se/libcurl/c/libcurl-errors.html
		else
		{
			CString msg;
			CString str = curl_easy_strerror(result); 
			TRACE(_T("CCurlinet::GetInternetData - ERROR %s\n"), str);
			ret = FALSE;

			msg = _T("Download Failed: Error ");
			msg += str;

			// Happens with no conection.
			if(str == _T("Couldn't resolve host name"))
				msg += _T("\nAre you connected to the internet?");

			// Set param.
			strErr = msg;

			AfxMessageBox(msg);
		}
    }
	// Clean global.
	// todo - in destructor?
    curl_global_cleanup();

	// Close our disc file.
	m_file.Close();

	return ret;
}

// Works with _UNICODE defined - see StdAfx.
// Curl and MFC _UNICODE don't seem to mix.
// Curl Curl 7.74.0 does say it's built with unicode support. 
// Converting CString params to ASCII with WideCharToMultiByte() fixes.
//
// Fetches reponse from strUrl - stores in filePath file.
BOOL CCurlinet::GetInternetDataUnicode(CString strUrl, CString filePath, 
									   CString & strErr, UINT maxBytes /*= 0*/)
{
	TRACE(_T("Entering CCurlinet::GetInternetData\n"));

	// 9/25/2021
	// Set maximum bytes to download per maxBytes param. 
	// Used in CCurlinet::CurlResults to cancel if download too big. 
	// See CDsLavDoc::ReadM3u for info.
	m_nMaxDownloadBytes = 0;
	m_nBytesDownloaded = 0;
	if(maxBytes)
		m_nMaxDownloadBytes = maxBytes;

	// Get current directory.
	CString curDir = ((CDsLavApp*)AfxGetApp())->m_curDirectory;

	CString certPathCA;
	CString certPathPEM;
	BOOL ret = TRUE;

	// Open disc file.
	CFileException e;
	if(!m_file.Open(filePath, 
				    CFile::modeCreate | CFile::modeWrite,
				    &e)) 
	{
		TRACE(_T("CCurlinet::GetInternetDataUnicode - can not open file %s, error = %u\n"), 
					filePath, e.m_cause );
		return FALSE;
	}

	// Build ASCII strings.
	char * szUrl = NULL;
	char * szCertPathCA = NULL;
	char * szCertPathPEM = NULL;

	// szUrl
	szUrl = GetAsciiStringFromCString(strUrl);

	certPathCA = curDir;
	certPathCA += _T("\\Curl_Local\\bin\\curl-ca-bundle.crt");
	szCertPathCA = GetAsciiStringFromCString(certPathCA);
	CString temp = szCertPathCA;
	TRACE(_T("CCurlinet::GetInternetDataUnicode - szCertPathCA = %s\n"), temp);

	// szCertPathPEM
	certPathPEM = curDir;
	certPathPEM += _T("\\Curl_Local\\bin\\cacert.pem");
	szCertPathPEM = GetAsciiStringFromCString(certPathPEM);
	temp = szCertPathPEM;
	TRACE(_T("CCurlinet::GetInternetDataUnicode - szCertPathPEM = %s\n"), temp);

	// Ensure conversions else CURL will fail.
	if(!szUrl || !szCertPathCA || !szCertPathPEM)
	{
		if(szUrl)
			delete [] szUrl;
		if(szCertPathCA)
			delete [] szCertPathCA;
		if(szCertPathPEM)
			delete [] szCertPathPEM;
		m_file.Close();
 		TRACE(_T("ERROR CCurlinet::GetInternetDataUnicode GetAsciiStringFromCString failed.\n"));
		return FALSE;
	}
	// CURL.
    CURL * curl = NULL;
    CURLcode result;

    // In windows, this will init the winsock stuff.
	// todo - do once in constructor?
	//  See: https://curl.se/libcurl/c/curl_global_init.html
	//  See: https://github.com/curl/curl/issues/3990
	// Note: This function is not thread safe.
	curl_global_init(CURL_GLOBAL_ALL); 

 	TRACE(_T("CCurlinet::GetInternetDataUnicode calling curl_easy_init\n"));

	// Get a CURL easy handle.
    curl = curl_easy_init();	// cleaned if success
 
    if(curl)
    {
		// Provide the URL to use in the request.
		//  See: https://curl.se/libcurl/c/CURLOPT_URL.html 
		curl_easy_setopt(curl, CURLOPT_URL, szUrl); 

		// Pass a pointer to a linked list of HTTP headers to pass to 
		// the server - NULL
		//  See: https://curl.se/libcurl/c/CURLOPT_HTTPHEADER.html.
		curl_easy_setopt(curl, CURLOPT_HEADER, 0);

		// Pass a pointer to class static callback function.
		//  See: https://curl.se/libcurl/c/CURLOPT_WRITEFUNCTION.html
		curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CCurlinet::CurlCallback);

		// Must tell the library to follow any redirect location. If not 
		// set, callback func may not be called, as curl only does what 
		// you tell it to do. Avoids a 301 reponse code.
		//  See: https://curl.se/libcurl/c/CURLOPT_FOLLOWLOCATION.html
		curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, TRUE);

		// Provide certificates stored in:
		//  \DsLavSplit\Curl_Local\bin\xxxcert
		//  See!: https://stackoverflow.com/questions/25041021/ssl-peer-certificate-or-
		//         ssh-remote-key-was-not-ok-c
		//
		// Set cert path for curl-ca-bundle.crt:
		//  See: https://curl.se/libcurl/c/CURLOPT_CAPATH.html
		curl_easy_setopt(curl, CURLOPT_CAPATH, szCertPathCA);

		// Set cert path for cacert.pem
		//  See: https://curl.se/libcurl/c/CURLOPT_CAINFO.html
		curl_easy_setopt(curl, CURLOPT_CAINFO, szCertPathPEM); 

		// Ask for an HTTP GET request (use a GET to fetch this).
		//  See: https://curl.se/libcurl/c/CURLOPT_HTTPGET.html
		curl_easy_setopt(curl, CURLOPT_HTTPGET, 1);

		// A data pointer to pass to the write callback. If you use 
		// the CURLOPT_WRITEFUNCTION option, this is the pointer you'll 
		// get in that callback's 4th argument.
		//  See: https://curl.se/libcurl/c/CURLOPT_WRITEDATA.html
		//  See: Curl FAQ: 5.14 Using C++ non-static functions for callbacks?
		curl_easy_setopt(curl, CURLOPT_WRITEDATA, this);

		// Test get debug info.
		curl_easy_setopt(curl, CURLOPT_VERBOSE, true); // no change
		//curl_easy_setopt(curl, CURLOPT_STDERR, $out);

		TRACE(_T("CCurlinet::GetInternetDataUnicode calling curl_easy_perform\n"));

		// Perform the HTTP get.
		result = curl_easy_perform(curl);
		
		TRACE(_T("CCurlinet::GetInternetDataUnicode - curl_easy_perform returned %d\n"), result);

		// Debug - check results.
		char *  raw_url = NULL;
		long   response_code;
		double elapsed;
		double uploaded_bytes;
		double downloaded_bytes;

		// curl_version
		//CURLINFO_SSL_ENGINES
		CString vers = curl_version();
		TRACE(_T("CCurlinet::GetInternetDataUnicode - curl version = %s\n"), vers);

		curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);	// 301
		curl_easy_getinfo(curl, CURLINFO_TOTAL_TIME, &elapsed);				// 0.547
		curl_easy_getinfo(curl, CURLINFO_EFFECTIVE_URL, &raw_url);			// https://microsoft.com
		curl_easy_getinfo(curl, CURLINFO_SIZE_DOWNLOAD, &downloaded_bytes);	// 0
		curl_easy_getinfo(curl, CURLINFO_SIZE_UPLOAD, &uploaded_bytes);		// 0
		TRACE(_T("CCurlinet::GetInternetDataUnicode - response code %d\n"), response_code);
		TRACE(_T("CCurlinet::GetInternetDataUnicode - elapsed time %f\n"), elapsed);
		TRACE(_T("CCurlinet::GetInternetDataUnicode - raw url %s\n"), (LPCTSTR)raw_url);	// Q125796
		TRACE(_T("CCurlinet::GetInternetDataUnicode - up bytes %f\n"), uploaded_bytes);
		TRACE(_T("CCurlinet::GetInternetDataUnicode - down bytes %f\n"), downloaded_bytes);

		// Must cleanup handle.
		curl_easy_cleanup(curl);

		// Success.
		if(result == CURLE_OK) // = 0
		{
		//	AfxMessageBox(_T("Download complete."));
		}
		// Error - get info. 
		//  See http://curl.haxx.se/libcurl/c/libcurl-errors.html
		else
		{
			// Note: curl_easy_strerror is fine with CString in UNICODE.
			CString msg;
			CString str = curl_easy_strerror(result); 
			TRACE(_T("CCurlinet::GetInternetDataUnicode - ERROR %s\n"), str);
			ret = FALSE;

			msg = _T("Download Failed: Error ");
			msg += str;

			// Happens with no conection.
			if(str == _T("Couldn't resolve host name"))
				msg += _T("\nAre you connected to the internet?");

			// Set param.
			strErr = msg;

		//	AfxMessageBox(msg);
		}
    }
	else
	{
		DWORD err = GetLastError();
		CString msg;
		msg.Format(_T("EasyInit failed with %d"), err);
		AfxMessageBox(msg);
	}

	// Clean strings.
	delete [] szUrl;
	delete [] szCertPathCA;
	delete [] szCertPathPEM;

	// Clean global.
	// todo - in destructor?
	curl_global_cleanup();

	// Close our disc file.
	m_file.Close();

	return ret;
}

// Get ASCII string from CString.
// Char ptr returned must be deleted (delete [] szStr).
char * CCurlinet::GetAsciiStringFromCString(CString str)
{
	// From MSDN Q139759
	// WideCharToMultiByte(CP_ACP,0,strSQL,-1, szAscii, strSQL.GetLength() + 1, NULL, NULL);

	char * szStr = NULL;
	int len = str.GetLength();
	szStr = new char[len + 1];
	WideCharToMultiByte(CP_ACP, 0, str, -1, szStr, len + 1, NULL, NULL);
	return szStr;

	// CP_ACP  - ANSI code page 
	// CP_UTF8 - Translate using UTF-8
}

// Static callback:
// Called by Curl from GetInternetData().
//  Note error C2724 if declared static in implementation.
//  See: https://curl.se/libcurl/c/CURLOPT_WRITEFUNCTION.html
//  See: Curl FAQ: 5.14 Using C++ non-static functions for callbacks?
//
// This callback function gets called by libcurl as soon as there is data 
// received that needs to be saved. For most transfers, this callback gets 
// called many times and each invoke delivers another chunk of data. ptr 
// points to the delivered data, and the size of that data is 
// nmemb; size is always 1.
size_t CCurlinet::CurlCallback(char *data, size_t size, size_t nmemb, void * f)
{
	// C++ review: Static member functions have no *this pointer. 
	//
	// CCurlinet's this ptr was passed to curl with:
	//   curl_easy_setopt(curl, CURLOPT_WRITEDATA, this)
	// in GetInternetData().
	//
	// That class instance ptr is now in the void * f param 
	// and can be used to access class members.
	//
	// So here we go to CCurlinet::CurlResults()...
	TRACE(_T("In CCurlinet::CurlCallback, forwarding to CurlResults.\n"));
	return static_cast<CCurlinet*>(f)->CurlResults(data, size, nmemb);
}

// Process callbacks - called only from static CurlCallback().
size_t CCurlinet::CurlResults(char *data, size_t size, size_t nmemb)
{
	// Size in bytes.
	UINT nBytes = size * nmemb;

	TRACE(_T("In CCurlinet::CurlResults, writing %d bytes.\n"), nBytes);

	// 9/25/2021 
	// Limit download size.
	if(m_nMaxDownloadBytes)
	{
		m_nBytesDownloaded += nBytes;
		if(m_nBytesDownloaded > m_nMaxDownloadBytes)
		{
			TRACE(_T("In CCurlinet::CurlResults, max bytes %d exceeded.\n"), m_nMaxDownloadBytes);
			TRACE(_T("Cancelling download.\n"));
			AfxMessageBox(_T("Cancelling download - maximum file size exceeded."));
			return 0;
		}
	}

	// Write *data to disc file.
	TRY
	{
	   m_file.Write(data, nBytes);
	}
	// Failed write.
	CATCH( CFileException, e )
	{
		TCHAR   szCause[255];
		CString strFormatted;
  
		e->GetErrorMessage(szCause, 255);   
		strFormatted = _T("Unknown error: ");
		strFormatted += szCause;  
		AfxMessageBox(strFormatted);
		/*
		WCHAR szErr[1024];
		CString strEr;
        if(!e->GetErrorMessage(szErr, 1024))
			wcscpy(szErr, _T("Unknown error."));
			//StringCbCopy(szErr, 15 * sizeof(TCHAR), _T("Unknown error."));
		TRACE(_T("In CCurlinet::CurlResults File write failed!! - %s\n"), szErr);
		e->Delete();*/

		// This should cause curl to quit?
		nBytes = 0;
	}
	END_CATCH

	// Return bytes processed (written to file).
	return nBytes;
}

// Threaded version of GetInternetDataUnicode.
//  Provide a progress callback.
//  Post messages to thread owner and a CDialog.
//  Use a CEvent to teminate download
BOOL CCurlinet::GetInternetDataUnicodeThread(CString strUrl, CString tempFilePath, CString & strErr,
											 HWND hwndMain, HWND hwndDlg, CEvent * pEvent)
{
	TRACE(_T("Entering CCurlinet::GetInternetDataUnicodeThread.\n"));

	// Ensure.
	if(!hwndMain || !hwndDlg || !pEvent)
	{
		strErr = _T("CCurlinet::GetInternetDataUnicodeThread param errors.\n");
		TRACE(strErr);
		return FALSE;
	}
	// Set members.
	m_hwndMain = hwndMain;
	m_hwndDlg = hwndDlg;
    m_pEvent = pEvent;
	m_mbDownloaded = -1; // progess count

	// Get current directory for certs.
	CString curDir = ((CDsLavApp*)AfxGetApp())->m_curDirectory;

	CString userAgent;
	CString certPathCA;
	CString certPathPEM;
	BOOL ret = TRUE;

	// Open temp disc file for writing.
	CFileException e;
	if(!m_file.Open(tempFilePath, 
				    CFile::modeCreate | CFile::modeWrite,
				    &e)) 
	{
		TRACE(_T("CCurlinet::GetInternetDataUnicodeThread - can not open file %s, error = %u\n"), 
					tempFilePath, e.m_cause );
		return FALSE;
	}

	// Build ASCII strings.
	char * szUrl = NULL;
	char * szUserAgent = NULL;
	char * szCertPathCA = NULL;
	char * szCertPathPEM = NULL;

	// szUrl
	szUrl = GetAsciiStringFromCString(strUrl);

	// szUserAgent
	userAgent = _T("DsLav application");
	szUserAgent = GetAsciiStringFromCString(userAgent);

	// szCertPathCA
	certPathCA = curDir;
	certPathCA += _T("\\Curl_Local\\bin\\curl-ca-bundle.crt");
	szCertPathCA = GetAsciiStringFromCString(certPathCA);
	CString temp = szCertPathCA;
	TRACE(_T("CCurlinet::GetInternetDataUnicodeThread - szCertPathCA = %s\n"), temp);

	// szCertPathPEM
	certPathPEM = curDir;
	certPathPEM += _T("\\Curl_Local\\bin\\cacert.pem");
	szCertPathPEM = GetAsciiStringFromCString(certPathPEM);
	temp = szCertPathPEM;
	TRACE(_T("CCurlinet::GetInternetDataUnicodeThread - szCertPathPEM = %s\n"), temp);

	// Ensure conversions else CURL will fail.
	if(!szUrl || !szUserAgent || !szCertPathCA || !szCertPathPEM)
	{
		if(szUrl)
			delete [] szUrl;
		if(szUserAgent)
			delete [] szUserAgent;
		if(szCertPathCA)
			delete [] szCertPathCA;
		if(szCertPathPEM)
			delete [] szCertPathPEM;
		m_file.Close();
 		TRACE(_T("ERROR CCurlinet::GetInternetDataUnicodeThread GetAsciiStringFromCString failed.\n"));
		return FALSE;
	}
	// Dlg msg.
	CString dlgMsg;

	// Check if User has aborted update.
	if(::WaitForSingleObject(m_pEvent->m_hObject, 0) == WAIT_OBJECT_0) 
	{
		// Clean up.
		m_file.Close();
		// Post Status msg.
		dlgMsg = _T("Canceling download.");
		ShowDialogStatus(dlgMsg);		
 		TRACE(_T("CCurlinet::GetInternetDataUnicodeThread user canceled update.\n"));
		// Done.
		return FALSE;
	}
	// Info user making connection.
	ShowDialogStatus(_T("Connecting to Radio Browser server."));
	
	// CURL.
    CURL * curl = NULL;
    CURLcode result;

    // In windows, this will init the winsock stuff.
	// todo - do once in constructor?
	//  See: https://curl.se/libcurl/c/curl_global_init.html
	//  See: https://github.com/curl/curl/issues/3990
	// Note: This function is not thread safe.
	curl_global_init(CURL_GLOBAL_ALL); 

 	TRACE(_T("CCurlinet::GetInternetDataUnicodeThread calling curl_easy_init\n"));

	// Get a CURL easy handle.
    curl = curl_easy_init();	// cleaned if success
 
    if(curl)
    {
		// Provide the URL to use in the request.
		//  See: https://curl.se/libcurl/c/CURLOPT_URL.html 
		curl_easy_setopt(curl, CURLOPT_URL, szUrl); 

		// User-Agent - RB wants it for stats.
		//  See: https://curl.se/libcurl/c/CURLOPT_USERAGENT.html
		curl_easy_setopt(curl, CURLOPT_USERAGENT, szUserAgent); 

		// Pass a pointer to a linked list of HTTP headers to pass to 
		// the server - NULL
		//  See: https://curl.se/libcurl/c/CURLOPT_HTTPHEADER.html.
		curl_easy_setopt(curl, CURLOPT_HEADER, 0);

		// Pass a pointer to class static callback function.
		//  See: https://curl.se/libcurl/c/CURLOPT_WRITEFUNCTION.html
		curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CCurlinet::CurlCallback);

		// Must tell the library to follow any redirect location. If not 
		// set, callback func may not be called, as curl only does what 
		// you tell it to do. Avoids a 301 reponse code.
		//  See: https://curl.se/libcurl/c/CURLOPT_FOLLOWLOCATION.html
		curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, TRUE);

		// Provide certificates stored in:
		//  \DsLavSplit\Curl_Local\bin\xxxcert
		//  See!: https://stackoverflow.com/questions/25041021/ssl-peer-certificate-or-
		//         ssh-remote-key-was-not-ok-c
		//
		// Set cert path for curl-ca-bundle.crt:
		//  See: https://curl.se/libcurl/c/CURLOPT_CAPATH.html
		curl_easy_setopt(curl, CURLOPT_CAPATH, szCertPathCA);

		// Set cert path for cacert.pem
		//  See: https://curl.se/libcurl/c/CURLOPT_CAINFO.html
		curl_easy_setopt(curl, CURLOPT_CAINFO, szCertPathPEM); 

		// Ask for an HTTP GET request (use a GET to fetch this).
		//  See: https://curl.se/libcurl/c/CURLOPT_HTTPGET.html
		curl_easy_setopt(curl, CURLOPT_HTTPGET, 1);

		// A data pointer to pass to the write callback. If you use 
		// the CURLOPT_WRITEFUNCTION option, this is the pointer you'll 
		// get in that callback's 4th argument.
		//  See: https://curl.se/libcurl/c/CURLOPT_WRITEDATA.html
		//  See: Curl FAQ: 5.14 Using C++ non-static functions for callbacks?
		curl_easy_setopt(curl, CURLOPT_WRITEDATA, this);

		// Test get debug info.
		curl_easy_setopt(curl, CURLOPT_VERBOSE, true); // no change
		//curl_easy_setopt(curl, CURLOPT_STDERR, $out);

		// Set progress callback function.
		//
		// Enable progress meter.
		// If onoff is to 1, it tells the library to shut off the progress meter completely.
		//  See: https://curl.se/libcurl/c/CURLOPT_NOPROGRESS.html
		curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L); // Zero param = on

		// Callback function to progress meter.
		// If your callback function returns CURL_PROGRESSFUNC_CONTINUE it will cause libcurl 
		// to continue executing the default progress function.
		// Returning any other non-zero value from this callback will cause libcurl to abort 
		// the transfer and return CURLE_ABORTED_BY_CALLBACK. 
		//  See: https://curl.se/libcurl/c/CURLOPT_XFERINFOFUNCTION.html
		curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, CurlProgressCallback);

		// Custom pointer passed to the progress callback.
		//  See: https://curl.se/libcurl/c/CURLOPT_XFERINFODATA.html
		curl_easy_setopt(curl, CURLOPT_XFERINFODATA, this);

		TRACE(_T("CCurlinet::GetInternetDataUnicodeThread calling curl_easy_perform\n"));

		// Perform the HTTP get.
		result = curl_easy_perform(curl);
		
		TRACE(_T("CCurlinet::GetInternetDataUnicodeThread - curl_easy_perform returned %d\n"), result);

		// Debug - check results.
		char *  raw_url = NULL;
		long   response_code;
		double elapsed;
		double uploaded_bytes;
		double downloaded_bytes;

		// curl_version - CURLINFO_SSL_ENGINES
		CString vers = curl_version();
		TRACE(_T("CCurlinet::GetInternetDataUnicodeThread - curl version = %s\n"), vers);

		curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);	// 301
		curl_easy_getinfo(curl, CURLINFO_TOTAL_TIME, &elapsed);				// 0.547
		curl_easy_getinfo(curl, CURLINFO_EFFECTIVE_URL, &raw_url);			// https://microsoft.com
		curl_easy_getinfo(curl, CURLINFO_SIZE_DOWNLOAD, &downloaded_bytes);	// 0
		curl_easy_getinfo(curl, CURLINFO_SIZE_UPLOAD, &uploaded_bytes);		// 0
		TRACE(_T("CCurlinet::GetInternetDataUnicode - response code %d\n"), response_code);
		TRACE(_T("CCurlinet::GetInternetDataUnicode - elapsed time %f\n"), elapsed);
		TRACE(_T("CCurlinet::GetInternetDataUnicode - raw url %s\n"), raw_url);
		TRACE(_T("CCurlinet::GetInternetDataUnicode - up bytes %f\n"), uploaded_bytes);
		TRACE(_T("CCurlinet::GetInternetDataUnicode - down bytes %f\n"), downloaded_bytes);

		// Must cleanup handle.
		curl_easy_cleanup(curl);

		// Success.
		if(result == CURLE_OK) // = 0
		{
			// Info user.
			dlgMsg = _T("Download complete.");
			ShowDialogStatus(dlgMsg);
		}

		// Error - get info. 
		//  See http://curl.haxx.se/libcurl/c/libcurl-errors.html
		else
		{
			// Note: curl_easy_strerror is fine with CString in UNICODE.
			CString str = curl_easy_strerror(result); 
			TRACE(_T("CCurlinet::GetInternetDataUnicodeThread - ERROR %s\n"), str);
			ret = FALSE;

			dlgMsg = _T("Download Failed: Error ");
			dlgMsg += str;

			// Happens with no conection.
			if(str == _T("Couldn't resolve host name"))
				dlgMsg += _T("\r\nAre you connected to the internet?");

			// User canceled: 
			//  curl_easy_perform returned 42
			else if(str == _T("Operation was aborted by an application callback"))
				dlgMsg = _T("Download canceled by user.");

			// Set param.
			strErr = dlgMsg;

			// Info user.
			ShowDialogStatus(dlgMsg);
		}
    }
	// Curl fail.
	else
	{
		DWORD err = GetLastError();
		dlgMsg.Format(_T("Connection failed with %d"), err);
		// Info user.
		ShowDialogStatus(dlgMsg);
	}

	// Clean strings.
	delete [] szUrl;
	delete [] szUserAgent;
	delete [] szCertPathCA;
	delete [] szCertPathPEM;

	// Clean global.
	// todo - in destructor?
	curl_global_cleanup();

	// Close our disc file.
	m_file.Close();

	return ret;
}

// Status String message to dialog during threaded opeation.
void CCurlinet::ShowDialogStatus(CString strStatus)
{
	// Set CApp string buff and Post message to dialog.
	if(m_hwndDlg)
	{
		((CDsLavApp*)AfxGetApp())->SetThreadMessage(strStatus);
		PostMessage(m_hwndDlg, WM_USER_THREAD_STATUS_STRING, 0, 0);

		// Let the UI thread get the string else jumbled output.
		::Sleep(250);
	}
}

// Progress control position message to dialog.
void CCurlinet::SetProgressPos(int pos)
{
	// Post to dialog.
	if(m_hwndDlg)
		PostMessage(m_hwndDlg, WM_USER_THREAD_PROGRESS_POS, 0, (LPARAM)pos);
}


// Static Progress callback
// Called by Curl from GetInternetDataUnicodeProgress().
//
size_t CCurlinet::CurlProgressCallback(void * f,   
									   curl_off_t dltotal,
									   curl_off_t dlnow,
									   curl_off_t ultotal,
									   curl_off_t ulnow)
{
	// Call CCurlinet::CurlProgress().
	TRACE(_T("In CCurlinet::CurlProgressCallback, forwarding to CurlProgress.\n"));
	return static_cast<CCurlinet*>(f)->CurlProgress(dltotal,
													dlnow,
													ultotal,
													ulnow);
}

// Member Progress callback - called only from static CurlProgressCallback().
//
// If your callback function returns CURL_PROGRESSFUNC_CONTINUE it will cause libcurl 
// to continue executing the default progress function.
// Returning any other non-zero value from this callback will cause libcurl to abort 
// the transfer and return CURLE_ABORTED_BY_CALLBACK. 
//  See: https://curl.se/libcurl/c/CURLOPT_XFERINFOFUNCTION.html
size_t CCurlinet::CurlProgress(curl_off_t dltotal,
							   curl_off_t dlnow,
							   curl_off_t ultotal,
							   curl_off_t ulnow)
{
	// Get param as mega bytes in int.
	int mbNow = (int)dlnow / 1048576; // 1 MB = 1,048,576 bytes

	// First time here.
	if(m_mbDownloaded == -1)
	{
		// Print msg.
		ShowDialogStatus(_T("Downloading Radio Browser data.\r\nPlease wait."));

		m_mbDownloaded = 0; // or mbNow

		// Update dlg progress ctrl.
		SetProgressPos(m_mbDownloaded);

		TRACE(_T("In CCurlinet::CurlProgress, First: dltotal = %d, dlnow = %d.\n"), 
					(int)dltotal, (int)dlnow);

	}
	// Message for each MB.
	if(mbNow > m_mbDownloaded)
	{
		m_mbDownloaded = mbNow;

		// Update dlg progress ctrl.
		SetProgressPos(m_mbDownloaded);

		TRACE(_T("In CCurlinet::CurlProgress, dltotal = %d, dlnow = %d.\n"), 
					(int)dltotal, (int)dlnow);

	}
	// Check if User has aborted the update.
	if(::WaitForSingleObject(m_pEvent->m_hObject, 0) == WAIT_OBJECT_0) 
	{
 		TRACE(_T("CCurlinet::CurlProgress user canceled - dlnow = %d.\n"), (int)dlnow);
		// Message user.
		ShowDialogStatus(_T("Canceling download."));

		// Make curl abort.
		return 1;
	}
	// Continue downloading.
	return CURL_PROGRESSFUNC_CONTINUE; //268435457 ?
}


