#include "precomp.h"

#include <iostream>
#include <string>

#include "download.h"
#include "uniconv.h"
#include "except.h"

#ifdef _WIN32
#include <winhttp.h>
#include <malloc.h>
#else
#include <curl/curl.h>
#endif

#ifndef _WIN32
	class CurlWriteBuffer
	{
	public:
		CurlWriteBuffer(void * buffer, size_t size)
		{
			this->buffer = (char *)buffer;
			this->size = size;
			offset = 0;
		}

		int Append(void * buffer, size_t size)
		{
			if (this->offset + size > this->size)
				size = this->size - offset;

			if (size > 0)
			{
				memcpy(this->buffer + offset, buffer, size);
				offset += size;
			}

			return size;
		}

		static size_t
		Callback(void *contents, size_t size, size_t nmemb, void *userp)
		{
			return ((CurlWriteBuffer *)userp)->Append(contents, size * nmemb);
		}

		size_t offset;
		size_t size;
		char * buffer;
	};

#endif

void UrlDownloadToString(const char * url, char * buffer, int * len)
{
#ifndef LICENSING_NO_NETWORKING
#ifdef _WIN32
	HRESULT result = ERROR_SUCCESS;

	URL_COMPONENTS urlComponents;

	do {
		ZeroMemory(&urlComponents, sizeof(urlComponents));
		urlComponents.dwStructSize = sizeof(urlComponents);

		urlComponents.dwHostNameLength  = (DWORD)-1;
		urlComponents.dwUrlPathLength = (DWORD)-1;

		auto urlw = s2w(url);

		if (!WinHttpCrackUrl(urlw.c_str(), urlw.size(), 0L, &urlComponents))
		{
			result = GetLastError();
			break;
		}

		wstring hostName(urlComponents.lpszHostName, urlComponents.dwHostNameLength);

		unique_ptr<VOID, decltype(&WinHttpCloseHandle)> internet_ptr(WinHttpOpen(L"ANSCENTER Licensing SDK", WINHTTP_ACCESS_TYPE_NO_PROXY, NULL, NULL, 0L), WinHttpCloseHandle);
		if (!internet_ptr)
		{
			result = GetLastError();
			break;
		}

		unique_ptr<VOID, decltype(&WinHttpCloseHandle)> connection_ptr(WinHttpConnect(internet_ptr.get(), hostName.c_str(), urlComponents.nPort, 0L), WinHttpCloseHandle);
		if (connection_ptr == NULL)
		{
			result = GetLastError();
			break;
		}

		wstring objectPath(urlComponents.lpszUrlPath, urlComponents.dwUrlPathLength);

		DWORD requestFlags = WINHTTP_FLAG_BYPASS_PROXY_CACHE;
		if (urlComponents.nScheme == INTERNET_SCHEME_HTTPS)
			requestFlags |= WINHTTP_FLAG_SECURE;

		unique_ptr<VOID, decltype(&WinHttpCloseHandle)> request_ptr(WinHttpOpenRequest(connection_ptr.get(), L"GET", objectPath.c_str(), NULL, NULL, NULL, requestFlags), WinHttpCloseHandle);
		if (!request_ptr)
		{
			result = GetLastError();
			break;
		}

		if (!WinHttpSendRequest(request_ptr.get(), NULL, 0L, NULL, 0, 0L, NULL))
		{
			result = GetLastError();
			break;
		}

		if (!WinHttpReceiveResponse(request_ptr.get(), NULL))
		{
			result = GetLastError();
			break;
		}

		DWORD status;
		DWORD statusSize = sizeof(DWORD);

		if (!WinHttpQueryHeaders(request_ptr.get(), WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, WINHTTP_HEADER_NAME_BY_INDEX, &status, &statusSize, WINHTTP_NO_HEADER_INDEX))
		{
			result = GetLastError();
			break;
		}

		if (status != HTTP_STATUS_OK)
		{
			WCHAR buffer[1024];
			DWORD bufferSize = 1024;

			if (!WinHttpQueryHeaders(request_ptr.get(), WINHTTP_QUERY_STATUS_TEXT, WINHTTP_HEADER_NAME_BY_INDEX, buffer, &bufferSize, WINHTTP_NO_HEADER_INDEX))
			{
				result = GetLastError();
				break;
			}

			result = ERROR_WINHTTP_INVALID_URL;
			break;
		}

		if (status != HTTP_STATUS_OK)
		{
			result = GetLastError();
			break;
		}

		DWORD offset = 0, count;
		char lbuf[0x100];

		do {
			if (WinHttpReadData(request_ptr.get(), lbuf, 0x100, &count))
			{
				if (count == 0)
				{
					*len = offset;
					break;
				}

				if (offset + count > *len)
				{
					result = ERROR_INSUFFICIENT_BUFFER;
					break;
				}

				memcpy(buffer + offset, lbuf, count);
				offset += count;
			} else
			{
				result = GetLastError();
				break;
			}
		} while (true);
	} while (false);

	if (result != ERROR_SUCCESS)
		throw new LicensingException(result, "networking error while downloading url contents");
#else
	CURL *curl;
	CURLcode res;
 
	if ((curl = curl_easy_init()) == NULL)
		throw new LicensingException(STATUS_NET_ERROR, "cURL initalization failed");

	curl_easy_setopt(curl, CURLOPT_URL, url);
			
	curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);

	CurlWriteBuffer curlBuffer(buffer, *len);

	curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteBuffer::Callback);
 
	curl_easy_setopt(curl, CURLOPT_WRITEDATA, &curlBuffer);

	res = curl_easy_perform(curl);
			
	if(res != CURLE_OK)
	{
		curl_easy_cleanup(curl);
		throw new LicensingException(STATUS_NET_ERROR, curl_easy_strerror(res));
	}
 
	*len = curlBuffer.offset;

	curl_easy_cleanup(curl);
#endif
#else // !LICENSING_NO_NETWORKING
	throw new LicensingException(STATUS_GENERIC_ERROR, "networking support not built in");
#endif
}
