ftp/Win32.cpp

// Win32.cpp : Defines the entry point for the console application.
//
// Summary: sfFTPLib c++ demonstration
//
// Technical support: support@smartftp.com
//
// Copyright (c) SmartSoft Ltd.

#include "stdafx.h"
#include "Win32.h"

#include <string_view>
#include <memory>

[[nodiscard]] ATL::CComSafeArray<byte> GetKey(const std::wstring_view password)
{
	// TODO: Hash password (e.g. SHA1) and set data
	byte data[128 / 8]{};

	ATL::CComSafeArray<byte> key;
	ATLENSURE_SUCCEEDED(key.Create(sizeof(data)));
	const auto begin{ reinterpret_cast<const byte*>(data) };
	const auto end{ reinterpret_cast<const byte*>(data) + sizeof(data) };
	auto it{ begin };
	for (long Index = 0; it != end; Index++, ++it)
	{
		key.SetAt(Index, *it);
	}
	return key;
}

// mode: 1 = Read, 2 = Write
[[nodiscard]] ATL::CComPtr<sfFTPLib::IStreamFilter> CreateStreamFilter(const std::wstring_view password, int nBits, int mode)
{
	ATL::CComPtr<sfFTPLib::IStreamFilter> stream;

	if (mode == 1)
	{
		ATL::CComPtr<sfFTPLib::IAES128CTRReadStream> readStream;
		ATLENSURE_SUCCEEDED(readStream.CoCreateInstance(__uuidof(sfFTPLib::AES128CTRReadStream)));

		ATLENSURE_SUCCEEDED(readStream->SetKey(const_cast<byte*>(reinterpret_cast<const byte*>(password.data())), password.size()));
		stream = readStream;
	}
	else if (mode == 2)
	{
		ATL::CComPtr<sfFTPLib::IAES128CTRWriteStream> writeStream;
		ATLENSURE_SUCCEEDED(writeStream.CoCreateInstance(__uuidof(sfFTPLib::AES128CTRWriteStream)));
		ATLENSURE_SUCCEEDED(writeStream->SetKey(const_cast<byte*>(reinterpret_cast<const byte*>(password.data())), password.size()));
		stream = writeStream;
	}

	return stream;
}

void DownloadUsingStream(_In_ sfFTPLib::IFTPConnection* connection)
{
	// create a storage
	ATL::CComPtr<IStorage> storage;
	ATLENSURE_SUCCEEDED(::StgCreateDocfile(L"Storage", STGM_NOSCRATCH | STGM_TRANSACTED | STGM_CREATE | STGM_WRITE | STGM_SHARE_EXCLUSIVE, 0, &storage));

	ATL::CComPtr<IStream> stream;
	ATLENSURE_SUCCEEDED(storage->CreateStream(L"CONTENTS", STGM_CREATE | STGM_WRITE | STGM_SHARE_EXCLUSIVE, 0, 0, &stream));

	ATLENSURE_SUCCEEDED(connection->DownloadFileEx(ATL::CComBSTR(L"This is a DEMO server.txt"), ATL::CComVariant(stream.p), 0, MAXULONGLONG, nullptr));
	::wprintf(L"DownloadFileEx() successful.\n");

	stream->Commit(STGC_OVERWRITE | STGC_DANGEROUSLYCOMMITMERELYTODISKCACHE);
	storage->Commit(STGC_OVERWRITE | STGC_DANGEROUSLYCOMMITMERELYTODISKCACHE);
}

void ReadDirectory(_In_ sfFTPLib::IFTPConnection* connection, _In_z_ PCWSTR directory)
{
	ATLENSURE_SUCCEEDED(connection->ChangeDirectory(ATL::CComBSTR(directory)));

	// read listing
	ATL::CComPtr<sfFTPLib::IFTPItems> directoryItems;
	ATLENSURE_SUCCEEDED(connection->ReadDirectory(&directoryItems));

	long count;
	ATLENSURE_SUCCEEDED(directoryItems->get_Count(&count));
	::wprintf(L"Count = %d\n", count);

	// Enum
	if (count > 0)
	{
		ATL::CComPtr<IUnknown> unkEnum;
		directoryItems->get__NewEnum(&unkEnum);
		ATL::CComQIPtr<IEnumVARIANT> pEnum(unkEnum);

		const auto pArrVariant{ std::make_unique<VARIANT[]>(count) };
		ULONG fetched;
		if (SUCCEEDED(pEnum->Next(count, pArrVariant.get(), &fetched)))
		{
			for (ULONG i = 0; i < fetched; i++)
			{
				if (pArrVariant[i].vt == VT_DISPATCH)
				{
					ATL::CComQIPtr<sfFTPLib::IFTPItem> item{ pArrVariant[i].pdispVal };
					
					sfFTPLib::ItemType itemType;
					ATLENSURE_SUCCEEDED(item->get_Type(&itemType));
					ATL::CComBSTR itemName;
					ATLENSURE_SUCCEEDED(item->get_Name(&itemName));
					ULONGLONG itemSize;
					ATLENSURE_SUCCEEDED(item->get_Size(&itemSize));

					::wprintf(L"Type=0x%x; Name=%s; Size=%I64u\n", itemType, (PCWSTR) itemName, itemSize);

					if (itemType == sfFTPLib::ftpItemTypeSymbolicLink)
					{
						ATL::CComBSTR linkPoint;
						item->get_LinkPoint(&linkPoint);
						::wprintf(L"LinkPoint=%s", (PCWSTR) linkPoint);
					}
				}
				::VariantClear(&pArrVariant[i]);
			}
		}
	}
}

int wmain(int argc, wchar_t* argv[], wchar_t* envp[])
{
	ATLENSURE_SUCCEEDED(::CoInitializeEx(nullptr, COINIT_MULTITHREADED));

	ATL::CComPtr<sfFTPLib::IGlobal> global;
	ATLENSURE_SUCCEEDED(global.CoCreateInstance(__uuidof(sfFTPLib::Global)));

	// If LoadLicense is not called a trial license is automatically obtained from the activation server. The FTP Library uses WinHTTP to access
	// the activation server at www.smartftp.com (TLS, port 443). Ensure that your application is not blocked by any firewall.
	// TODO: insert the provided serial after the purchase of a license
	//ATLENSURE_SUCCEEDED(global->LoadLicense(ATL::CComBSTR(L"XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX")));

	ATL::CComPtr<sfFTPLib::IFTPConnection> ftp;
	ATLENSURE_SUCCEEDED(ftp.CoCreateInstance(__uuidof(sfFTPLib::FTPConnectionMTA)));

	ATL::CComPtr<sfFTPLib::IFileLogger> fileLogger;
	ATLENSURE_SUCCEEDED(fileLogger.CoCreateInstance(__uuidof(sfFTPLib::FileLogger)));
	ATLENSURE_SUCCEEDED(fileLogger->put_File(ATL::CComBSTR(L"Win32Demo.log")));

	ATL::CComQIPtr<sfFTPLib::ILogger> logger{ fileLogger };
	ATLENSURE_SUCCEEDED(ftp->put_Logger(logger));

	// AUTH TLS
	ATLENSURE_SUCCEEDED(ftp->put_Protocol(sfFTPLib::ftpProtocolSSLExplicit));
	ATLENSURE_SUCCEEDED(ftp->put_Host(ATL::CComBSTR(L"smartftp.com")));
	ATLENSURE_SUCCEEDED(ftp->put_Port(21));
	ATLENSURE_SUCCEEDED(ftp->put_Username(ATL::CComBSTR(L"anonymous")));
	ATLENSURE_SUCCEEDED(ftp->put_Password(ATL::CComBSTR(L"bla@bla.com")));
	ATLENSURE_SUCCEEDED(ftp->put_Passive(VARIANT_TRUE));

	// No Proxy
	// ATL::CComPtr<IProxy> proxy;
	// ATLENSURE_SUCCEEDED(ftp->get_Proxy(&proxy));
	//proxy->put_Type(sfFTPLib::ftpProxyTypeNone);
	//proxy->put_Host(ATL::CComBSTR(L"192.168.1.10");
	//proxy->put_Port(1080;
	//proxy->put_Authentication(VARIANT_TRUE);
	//proxy->put_Username(ATL::CComBSTR(L"user");
	//proxy->put_Password(ATL::CComBSTR(L"pass");

	ATLENSURE_SUCCEEDED(ftp->Connect());

	ReadDirectory(ftp, ATL::CComBSTR(L"/"));

	// download file
	ATLENSURE_SUCCEEDED(ftp->DownloadFileEx(ATL::CComBSTR(L"This is a DEMO server.txt"), ATL::CComVariant(L"This is a DEMO server.txt"), 0, MAXULONGLONG, nullptr));
	::wprintf(L"DownloadFile() successful.\n");
	ULONGLONG lastTransferBytes;
	ATLENSURE_SUCCEEDED(ftp->get_LastTransferBytes(&lastTransferBytes));
	::wprintf(L"LastTransferBytes = %I64u B\n", lastTransferBytes);
	long lastTransferTime;
	ATLENSURE_SUCCEEDED(ftp->get_LastTransferTime(&lastTransferTime));
	::wprintf(L"LastTransferTime = %d s\n", lastTransferTime);
	long lastTransferSpeed;
	ATLENSURE_SUCCEEDED(ftp->get_LastTransferSpeed(&lastTransferSpeed));
	::wprintf(L"LastTransferSpeed = %d B/s\n", lastTransferSpeed);

	// DownloadFileEx using IStream
	//DownloadUsingStream(ftp);

	// Upload file
	//ATLENSURE_SUCCEEDED(ftp->UploadFileEx(ATL::CComVariant(L"c:\\test.dat"), ATL::CComBSTR(L"test.dat"), 0, nullptr));

	::CoUninitialize();

	return 0;
}