This commit is contained in:
Shauren
2021-06-27 20:20:51 +02:00
parent 0bbf3f7300
commit b70f34b696
24 changed files with 1856 additions and 400 deletions

View File

@@ -12,8 +12,10 @@ set(HEADER_FILES
src/common/IndexMap.h
src/common/ListFile.h
src/common/Map.h
src/common/Mime.h
src/common/Path.h
src/common/RootHandler.h
src/common/Sockets.h
src/jenkins/lookup.h
)
@@ -24,7 +26,9 @@ set(SRC_FILES
src/common/FileStream.cpp
src/common/FileTree.cpp
src/common/ListFile.cpp
src/common/Mime.cpp
src/common/RootHandler.cpp
src/common/Sockets.cpp
src/jenkins/lookup3.c
src/CascDecompress.cpp
src/CascDecrypt.cpp

View File

@@ -30,8 +30,10 @@
#include "common/Directory.h"
#include "common/ListFile.h"
#include "common/Csv.h"
#include "common/Mime.h"
#include "common/Path.h"
#include "common/RootHandler.h"
#include "common/Sockets.h"
// Headers from Alexander Peslyak's MD5 implementation
#include "md5/md5.h"
@@ -58,8 +60,7 @@
#define CASC_MAGIC_FIND 0x444E494643534143 // 'CASCFIND'
// For CASC_CDN_DOWNLOAD::Flags
#define CASC_CDN_FLAG_PORT1119 0x0001 // Use port 1119
#define CASC_CDN_FORCE_DOWNLOAD 0x0002 // Force downloading the file even if in the cache
#define CASC_CDN_FORCE_DOWNLOAD 0x0001 // Force downloading the file even if in the cache
//-----------------------------------------------------------------------------
// In-memory structures
@@ -101,7 +102,7 @@ typedef struct _CASC_INDEX
typedef struct _CASC_INDEX_HEADER
{
USHORT IndexVersion; // 5 for index v 1.0, 7 for index version 2.0
BYTE BucketIndex; // Should be the same as the first byte of the hex filename.
BYTE BucketIndex; // Should be the same as the first byte of the hex filename.
BYTE StorageOffsetLength; // Length, in bytes, of the StorageOffset field in the EKey entry
BYTE EncodedSizeLength; // Length, in bytes, of the EncodedSize in the EKey entry
BYTE EKeyLength; // Length, in bytes, of the (trimmed) EKey in the EKey entry
@@ -367,7 +368,7 @@ struct TCascFile
DWORD bVerifyIntegrity:1; // If true, then the data are validated more strictly when read
DWORD bDownloadFileIf:1; // If true, then the data will be downloaded from the online storage if missing
DWORD bCloseFileStream:1; // If true, file stream needs to be closed during CascCloseFile
DWORD bOvercomeEncrypted:1; // If true, then CascReadFile will fill the part that is encrypted (and key was not found) with zeros
DWORD bOvercomeEncrypted:1; // If true, then CascReadFile will fill the part that is encrypted (and key was not found) with zeros
DWORD bFreeCKeyEntries:1; // If true, dectructor will free the array of CKey entries
ULONGLONG FileCacheStart; // Starting offset of the file cached area

View File

@@ -530,4 +530,9 @@ void CascDumpStorage(HANDLE hStorage, const char * szDumpFile)
}
}
#else // _DEBUG
// so linker won't mind this .cpp file is empty in non-DEBUG builds
void unused_symbol() { }
#endif // _DEBUG

View File

@@ -13,6 +13,10 @@
#include "CascLib.h"
#include "CascCommon.h"
#ifdef _MSC_VER
#pragma comment(lib, "ws2_32.lib") // Internet functions for HTTP stream
#endif
//-----------------------------------------------------------------------------
// Local defines
@@ -56,6 +60,8 @@ static LPCTSTR DataDirs[] =
NULL,
};
static LPCTSTR bnet_region = _T("us");
//-----------------------------------------------------------------------------
// Local functions
@@ -93,12 +99,12 @@ static const char * CaptureDecimalInteger(const char * szDataPtr, const char * s
while (szDataPtr < szDataEnd && szDataPtr[0] != ' ')
{
// Must only contain decimal digits ('0' - '9')
if (!IsCharDigit(szDataPtr[0]))
if(!IsCharDigit(szDataPtr[0]))
break;
// Get the next value and verify overflow
AddValue = szDataPtr[0] - '0';
if ((TotalValue + AddValue) < TotalValue)
if((TotalValue + AddValue) < TotalValue)
return NULL;
TotalValue = (TotalValue * 10) + AddValue;
@@ -143,13 +149,13 @@ static const char * CaptureSingleHash(const char * szDataPtr, const char * szDat
// Count all hash characters
for (size_t i = 0; i < HashStringLength; i++)
{
if (szDataPtr >= szDataEnd || isxdigit(szDataPtr[0]) == 0)
if(szDataPtr >= szDataEnd || isxdigit(szDataPtr[0]) == 0)
return NULL;
szDataPtr++;
}
// There must be a separator or end-of-string
if (szDataPtr > szDataEnd || IsWhiteSpace(szDataPtr) == false)
if(szDataPtr > szDataEnd || IsWhiteSpace(szDataPtr) == false)
return NULL;
// Give the values
@@ -167,7 +173,7 @@ static const char * CaptureHashCount(const char * szDataPtr, const char * szData
{
// Check one hash
szDataPtr = CaptureSingleHash(szDataPtr, szDataEnd, HashValue, MD5_HASH_SIZE);
if (szDataPtr == NULL)
if(szDataPtr == NULL)
return NULL;
// Skip all whitespaces
@@ -227,11 +233,11 @@ static bool CheckConfigFileVariable(
// Capture the variable from the line
szLinePtr = CaptureSingleString(szLinePtr, szLineEnd, szVariableName, sizeof(szVariableName));
if (szLinePtr == NULL)
if(szLinePtr == NULL)
return false;
// Verify whether this is the variable
if (!CascCheckWildCard(szVariableName, szVarName))
if(!CascCheckWildCard(szVariableName, szVarName))
return false;
// Skip the spaces and '='
@@ -239,7 +245,7 @@ static bool CheckConfigFileVariable(
szLinePtr++;
// Call the parsing function only if there is some data
if (szLinePtr >= szLineEnd)
if(szLinePtr >= szLineEnd)
return false;
return (PfnParseProc(hs, szVariableName, szLinePtr, szLineEnd, pvParseParam) == ERROR_SUCCESS);
@@ -256,7 +262,7 @@ static DWORD LoadHashArray(
// Allocate the blob buffer
pBlob->cbData = (DWORD)(HashCount * MD5_HASH_SIZE);
pBlob->pbData = CASC_ALLOC<BYTE>(pBlob->cbData);
if (pBlob->pbData != NULL)
if(pBlob->pbData != NULL)
{
LPBYTE pbBuffer = pBlob->pbData;
@@ -264,7 +270,7 @@ static DWORD LoadHashArray(
{
// Capture the hash value
szLinePtr = CaptureSingleHash(szLinePtr, szLineEnd, pbBuffer, MD5_HASH_SIZE);
if (szLinePtr == NULL)
if(szLinePtr == NULL)
return ERROR_BAD_FORMAT;
// Move buffer
@@ -283,7 +289,7 @@ static DWORD LoadMultipleHashes(PQUERY_KEY pBlob, const char * szLineBegin, cons
DWORD dwErrCode = ERROR_SUCCESS;
// Retrieve the hash count
if (CaptureHashCount(szLineBegin, szLineEnd, &HashCount) == NULL)
if(CaptureHashCount(szLineBegin, szLineEnd, &HashCount) == NULL)
return ERROR_BAD_FORMAT;
// Do nothing if there is no data
@@ -398,10 +404,10 @@ static DWORD LoadVfsRootEntry(TCascStorage * hs, const char * szVariableName, co
DWORD VfsRootIndex = CASC_INVALID_INDEX;
// Skip the "vfs-" part
if (!strncmp(szVariableName, "vfs-", 4))
if(!strncmp(szVariableName, "vfs-", 4))
{
// Then, there must be a decimal number as index
if ((szVarPtr = CaptureDecimalInteger(szVarPtr + 4, szVarEnd, &VfsRootIndex)) != NULL)
if((szVarPtr = CaptureDecimalInteger(szVarPtr + 4, szVarEnd, &VfsRootIndex)) != NULL)
{
// We expect the array to be initialized
assert(pArray->IsInitialized());
@@ -484,9 +490,9 @@ static DWORD LoadBuildNumber(TCascStorage * hs, const char * /* szVariableName *
static int LoadQueryKey(const CASC_CSV_COLUMN & Column, QUERY_KEY & Key)
{
// Check the input data
if (Column.szValue == NULL)
if(Column.szValue == NULL)
return ERROR_BUFFER_OVERFLOW;
if (Column.nLength != MD5_STRING_SIZE)
if(Column.nLength != MD5_STRING_SIZE)
return ERROR_BAD_FORMAT;
return LoadHashArray(&Key, Column.szValue, Column.szValue + Column.nLength, 1);
@@ -538,6 +544,7 @@ static DWORD GetDefaultLocaleMask(TCascStorage * hs, const CASC_CSV_COLUMN & Col
static DWORD ParseFile_CDNS(TCascStorage * hs, CASC_CSV & Csv)
{
const char * szWantedRegion = hs->szRegion;
const char * szRegion;
size_t nLineCount;
// Fix the region
@@ -548,19 +555,23 @@ static DWORD ParseFile_CDNS(TCascStorage * hs, CASC_CSV & Csv)
nLineCount = Csv.GetLineCount();
// Find the active config
for (size_t i = 0; i < nLineCount; i++)
for(size_t i = 0; i < nLineCount; i++)
{
// Is it the version we are looking for?
if(!strcmp(Csv[i]["Name!STRING:0"].szValue, szWantedRegion))
// Retrieve the region
if((szRegion = Csv[i]["Name!STRING:0"].szValue) != NULL)
{
// Save the list of CDN servers
hs->szCdnServers = CascNewStrA2T(Csv[i]["Hosts!STRING:0"].szValue);
// Is it the version we are looking for?
if(!strcmp(Csv[i]["Name!STRING:0"].szValue, szWantedRegion))
{
// Save the list of CDN servers
hs->szCdnServers = CascNewStrA2T(Csv[i]["Hosts!STRING:0"].szValue);
// Save the CDN subpath
hs->szCdnPath = CascNewStrA2T(Csv[i]["Path!STRING:0"].szValue);
// Save the CDN subpath
hs->szCdnPath = CascNewStrA2T(Csv[i]["Path!STRING:0"].szValue);
// Check and return result
return (hs->szCdnServers && hs->szCdnPath) ? ERROR_SUCCESS : ERROR_BAD_FORMAT;
// Check and return result
return (hs->szCdnServers && hs->szCdnPath) ? ERROR_SUCCESS : ERROR_BAD_FORMAT;
}
}
}
@@ -657,12 +668,12 @@ static DWORD ParseFile_BuildInfo(TCascStorage * hs, CASC_CSV & Csv)
{
// Extract the CDN build key
dwErrCode = LoadQueryKey(Csv[nSelected]["Build Key!HEX:16"], hs->CdnBuildKey);
if (dwErrCode != ERROR_SUCCESS)
if(dwErrCode != ERROR_SUCCESS)
return dwErrCode;
// Extract the CDN config key
dwErrCode = LoadQueryKey(Csv[nSelected]["CDN Key!HEX:16"], hs->CdnConfigKey);
if (dwErrCode != ERROR_SUCCESS)
if(dwErrCode != ERROR_SUCCESS)
return dwErrCode;
// Get the CDN path
@@ -694,20 +705,20 @@ static DWORD ParseFile_VersionsDb(TCascStorage * hs, CASC_CSV & Csv)
for (size_t i = 0; i < nLineCount; i++)
{
// Either take the version required or take the first one
if (hs->szRegion == NULL || !strcmp(Csv[i]["Region!STRING:0"].szValue, hs->szRegion))
if(hs->szRegion == NULL || !strcmp(Csv[i]["Region!STRING:0"].szValue, hs->szRegion))
{
// Extract the CDN build key
dwErrCode = LoadQueryKey(Csv[i]["BuildConfig!HEX:16"], hs->CdnBuildKey);
if (dwErrCode != ERROR_SUCCESS)
if(dwErrCode != ERROR_SUCCESS)
return dwErrCode;
// Extract the CDN config key
dwErrCode = LoadQueryKey(Csv[i]["CDNConfig!HEX:16"], hs->CdnConfigKey);
if (dwErrCode != ERROR_SUCCESS)
if(dwErrCode != ERROR_SUCCESS)
return dwErrCode;
const CASC_CSV_COLUMN & VerColumn = Csv[i]["VersionsName!String:0"];
if (VerColumn.szValue && VerColumn.nLength)
if(VerColumn.szValue && VerColumn.nLength)
{
LoadBuildNumber(hs, NULL, VerColumn.szValue, VerColumn.szValue + VerColumn.nLength, NULL);
}
@@ -739,7 +750,7 @@ static DWORD ParseFile_BuildDb(TCascStorage * hs, CASC_CSV & Csv)
// Extract the CDN config key
dwErrCode = LoadQueryKey(Csv[CSV_ZERO][1], hs->CdnConfigKey);
if (dwErrCode != ERROR_SUCCESS)
if(dwErrCode != ERROR_SUCCESS)
return dwErrCode;
// Verify all variables
@@ -798,7 +809,7 @@ static DWORD ParseFile_CdnBuild(TCascStorage * hs, void * pvListFile)
// Initialize the empty VFS array
dwErrCode = hs->VfsRootList.Create<CASC_CKEY_ENTRY>(0x10);
if (dwErrCode != ERROR_SUCCESS)
if(dwErrCode != ERROR_SUCCESS)
return dwErrCode;
// Parse all variables
@@ -860,39 +871,45 @@ static DWORD ParseFile_CdnBuild(TCascStorage * hs, void * pvListFile)
static DWORD CheckDataDirectory(TCascStorage * hs, LPTSTR szDirectory)
{
LPTSTR szDataPath;
TCHAR szDataPath[MAX_PATH];
DWORD dwErrCode = ERROR_FILE_NOT_FOUND;
// Try all known subdirectories
for(size_t i = 0; DataDirs[i] != NULL; i++)
{
// Create the eventual data path
szDataPath = CombinePath(szDirectory, DataDirs[i]);
if(szDataPath != NULL)
{
// Does that directory exist?
if(DirectoryExists(szDataPath))
{
hs->szRootPath = CascNewStr(szDirectory);
hs->szDataPath = szDataPath;
return ERROR_SUCCESS;
}
CombinePath(szDataPath, _countof(szDataPath), szDirectory, DataDirs[i], NULL);
// Free the data path
CASC_FREE(szDataPath);
// Does that directory exist?
if(DirectoryExists(szDataPath))
{
hs->szRootPath = CascNewStr(szDirectory);
hs->szDataPath = CascNewStr(szDataPath);
return ERROR_SUCCESS;
}
}
return dwErrCode;
}
static DWORD LoadCsvFile(TCascStorage * hs, LPBYTE pbFileData, size_t cbFileData, PARSECSVFILE PfnParseProc, bool bHasHeader)
{
CASC_CSV Csv(0x40, bHasHeader);
DWORD dwErrCode;
// Load the external file to memory
if((dwErrCode = Csv.Load(pbFileData, cbFileData)) == ERROR_SUCCESS)
dwErrCode = PfnParseProc(hs, Csv);
return dwErrCode;
}
static DWORD LoadCsvFile(TCascStorage * hs, LPCTSTR szFileName, PARSECSVFILE PfnParseProc, bool bHasHeader)
{
CASC_CSV Csv(0x40, bHasHeader);
DWORD dwErrCode;
// Load the external file to memory
if ((dwErrCode = Csv.Load(szFileName)) == ERROR_SUCCESS)
if((dwErrCode = Csv.Load(szFileName)) == ERROR_SUCCESS)
dwErrCode = PfnParseProc(hs, Csv);
return dwErrCode;
}
@@ -940,7 +957,7 @@ static void CreateRemoteAndLocalPath(TCascStorage * hs, CASC_CDN_DOWNLOAD & Cdns
{
// The file is given by EKey. It's either a loose file, or it's stored in an archive.
// We check that using the EKey map
if ((pEKeyEntry = (PCASC_EKEY_ENTRY)hs->IndexMap.FindObject(CdnsInfo.pbEKey)) != NULL)
if((pEKeyEntry = (PCASC_EKEY_ENTRY)hs->IndexMap.FindObject(CdnsInfo.pbEKey)) != NULL)
{
// Change the path type to "data"
RemotePath.AppendString(_T("data"), true);
@@ -1073,15 +1090,15 @@ static DWORD DownloadFile(
// Open the remote stream
pRemStream = FileStream_OpenFile(szRemoteName, BASE_PROVIDER_HTTP | STREAM_PROVIDER_FLAT | dwPortFlags);
if (pRemStream != NULL)
if(pRemStream != NULL)
{
// Will we download the entire file or just a part of it?
if (PtrByteOffset == NULL)
if(PtrByteOffset == NULL)
{
ULONGLONG FileSize = 0;
// Retrieve the file size, but not longer than 1 GB
if (FileStream_GetSize(pRemStream, &FileSize) && 0 < FileSize && FileSize < 0x40000000)
if(FileStream_GetSize(pRemStream, &FileSize) && 0 < FileSize && FileSize < 0x40000000)
{
// Cut the file size down to 32 bits
cbReadSize = (DWORD)FileSize;
@@ -1089,15 +1106,15 @@ static DWORD DownloadFile(
}
// Shall we read something?
if ((cbReadSize != 0) && (pbFileData = CASC_ALLOC<BYTE>(cbReadSize)) != NULL)
if((cbReadSize != 0) && (pbFileData = CASC_ALLOC<BYTE>(cbReadSize)) != NULL)
{
// Read all required data from the remote file
if (FileStream_Read(pRemStream, PtrByteOffset, pbFileData, cbReadSize))
if(FileStream_Read(pRemStream, PtrByteOffset, pbFileData, cbReadSize))
{
pLocStream = FileStream_CreateFile(szLocalName, BASE_PROVIDER_FILE | STREAM_PROVIDER_FLAT);
if (pLocStream != NULL)
if(pLocStream != NULL)
{
if (FileStream_Write(pLocStream, NULL, pbFileData, cbReadSize))
if(FileStream_Write(pLocStream, NULL, pbFileData, cbReadSize))
dwErrCode = ERROR_SUCCESS;
FileStream_Close(pLocStream);
@@ -1119,11 +1136,63 @@ static DWORD DownloadFile(
return dwErrCode;
}
static DWORD RibbitDownloadFile(LPCTSTR szProduct, LPCTSTR szFileName, QUERY_KEY & FileData)
{
TFileStream * pStream;
ULONGLONG FileSize = 0;
TCHAR szRemoteUrl[256];
DWORD dwErrCode = ERROR_CAN_NOT_COMPLETE;
// Construct the full URL (https://wowdev.wiki/Ribbit)
// Old (HTTP) download: wget http://us.patch.battle.net:1119/wow_classic/cdns
CascStrPrintf(szRemoteUrl, _countof(szRemoteUrl), _T("ribbit://%s.version.battle.net/v1/products/%s/%s"), bnet_region, szProduct, szFileName);
// Open the file stream
if((pStream = FileStream_OpenFile(szRemoteUrl, 0)) != NULL)
{
if(FileStream_GetSize(pStream, &FileSize) && FileSize <= 0x04000000)
{
// Fill-in the file pointer and size
FileData.pbData = CASC_ALLOC<BYTE>((size_t)FileSize);
if(FileData.pbData != NULL)
{
if(FileStream_Read(pStream, NULL, FileData.pbData, (DWORD)FileSize))
{
FileData.cbData = (size_t)FileSize;
dwErrCode = ERROR_SUCCESS;
}
else
{
dwErrCode = GetCascError();
CASC_FREE(FileData.pbData);
FileData.pbData = NULL;
}
}
else
{
dwErrCode = ERROR_NOT_ENOUGH_MEMORY;
}
}
else
{
dwErrCode = GetCascError();
}
// Close the remote stream
FileStream_Close(pStream);
}
else
{
dwErrCode = GetCascError();
}
return dwErrCode;
}
static DWORD DownloadFileFromCDN2(TCascStorage * hs, CASC_CDN_DOWNLOAD & CdnsInfo)
{
CASC_PATH<TCHAR> RemotePath(URL_SEP_CHAR);
CASC_PATH<TCHAR> LocalPath(PATH_SEP_CHAR);
DWORD dwPortFlags = (CdnsInfo.Flags & CASC_CDN_FLAG_PORT1119) ? STREAM_FLAG_USE_PORT_1119 : 0;
DWORD dwErrCode;
// Assemble both the remote and local path
@@ -1139,7 +1208,7 @@ static DWORD DownloadFileFromCDN2(TCascStorage * hs, CASC_CDN_DOWNLOAD & CdnsInf
return dwErrCode;
// Attempt to download the file
dwErrCode = DownloadFile(RemotePath, LocalPath, NULL, 0, dwPortFlags);
dwErrCode = DownloadFile(RemotePath, LocalPath, NULL, 0, 0);
if(dwErrCode != ERROR_SUCCESS)
return dwErrCode;
}
@@ -1213,9 +1282,9 @@ static DWORD FetchAndLoadConfigFile(TCascStorage * hs, PQUERY_KEY pFileKey, PARS
// Load and verify the external listfile
pvListFile = ListFile_OpenExternal(szLocalPath);
if (pvListFile != NULL)
if(pvListFile != NULL)
{
if (ListFile_VerifyMD5(pvListFile, pFileKey->pbData))
if(ListFile_VerifyMD5(pvListFile, pFileKey->pbData))
{
dwErrCode = PfnParseProc(hs, pvListFile);
}
@@ -1283,34 +1352,30 @@ DWORD GetFileSpanInfo(PCASC_CKEY_ENTRY pCKeyEntry, PULONGLONG PtrContentSize, PU
DWORD CheckGameDirectory(TCascStorage * hs, LPTSTR szDirectory)
{
TFileStream * pStream;
LPTSTR szBuildFile;
TCHAR szBuildFile[MAX_PATH];
DWORD dwErrCode = ERROR_FILE_NOT_FOUND;
// Try to find any of the root files used in the history
for (size_t i = 0; BuildTypes[i].szFileName != NULL; i++)
{
// Create the full name of the .agent.db file
szBuildFile = CombinePath(szDirectory, BuildTypes[i].szFileName);
if (szBuildFile != NULL)
CombinePath(szBuildFile, _countof(szBuildFile), szDirectory, BuildTypes[i].szFileName, NULL);
// Attempt to open the file
pStream = FileStream_OpenFile(szBuildFile, STREAM_FLAG_READ_ONLY);
if(pStream != NULL)
{
// Attempt to open the file
pStream = FileStream_OpenFile(szBuildFile, STREAM_FLAG_READ_ONLY);
if (pStream != NULL)
// Free the stream
FileStream_Close(pStream);
// Check for the data directory
dwErrCode = CheckDataDirectory(hs, szDirectory);
if(dwErrCode == ERROR_SUCCESS)
{
// Free the stream
FileStream_Close(pStream);
// Check for the data directory
dwErrCode = CheckDataDirectory(hs, szDirectory);
if (dwErrCode == ERROR_SUCCESS)
{
hs->szBuildFile = szBuildFile;
hs->BuildFileType = BuildTypes[i].BuildFileType;
return ERROR_SUCCESS;
}
hs->szBuildFile = CascNewStr(szBuildFile);
hs->BuildFileType = BuildTypes[i].BuildFileType;
return ERROR_SUCCESS;
}
CASC_FREE(szBuildFile);
}
}
@@ -1323,35 +1388,6 @@ DWORD LoadBuildInfo(TCascStorage * hs)
DWORD dwErrCode;
bool bHasHeader = true;
// If the storage is online storage, we need to download "versions"
if(hs->dwFeatures & CASC_FEATURE_ONLINE)
{
CASC_CDN_DOWNLOAD CdnsInfo = {0};
TCHAR szLocalFile[MAX_PATH];
// Inform the user about loading the build.info/build.db/versions
if(InvokeProgressCallback(hs, "Downloading the \"versions\" file", NULL, 0, 0))
return ERROR_CANCELLED;
// Prepare the download structure for "us.patch.battle.net/%CODENAME%/versions" file
CdnsInfo.szCdnsHost = _T("us.patch.battle.net");
CdnsInfo.szCdnsPath = hs->szCodeName;
CdnsInfo.szPathType = _T("");
CdnsInfo.szFileName = _T("versions");
CdnsInfo.szLocalPath = szLocalFile;
CdnsInfo.ccLocalPath = _countof(szLocalFile);
CdnsInfo.Flags = CASC_CDN_FLAG_PORT1119 | CASC_CDN_FORCE_DOWNLOAD;
// Attempt to download the "versions" file
dwErrCode = DownloadFileFromCDN(hs, CdnsInfo);
if(dwErrCode != ERROR_SUCCESS)
return dwErrCode;
// Retrieve the name of the "versions" file
if((hs->szBuildFile = CascNewStr(szLocalFile)) == NULL)
return ERROR_NOT_ENOUGH_MEMORY;
}
// We support ".build.info", ".build.db" or "versions"
switch (hs->BuildFileType)
{
@@ -1372,14 +1408,35 @@ DWORD LoadBuildInfo(TCascStorage * hs)
return ERROR_NOT_SUPPORTED;
}
assert(hs->szBuildFile != NULL);
return LoadCsvFile(hs, hs->szBuildFile, PfnParseProc, bHasHeader);
// If the storage is online storage, we need to download "versions"
if(hs->dwFeatures & CASC_FEATURE_ONLINE)
{
QUERY_KEY FileData;
// Inform the user about loading the build.info/build.db/versions
if(InvokeProgressCallback(hs, "Downloading the \"versions\" file", NULL, 0, 0))
return ERROR_CANCELLED;
// Download the file using Ribbit protocol
dwErrCode = RibbitDownloadFile(hs->szCodeName, _T("versions"), FileData);
if(dwErrCode == ERROR_SUCCESS)
{
// Parse the downloaded file
dwErrCode = LoadCsvFile(hs, FileData.pbData, FileData.cbData, ParseFile_VersionsDb, true);
}
}
else
{
assert(hs->szBuildFile != NULL);
dwErrCode = LoadCsvFile(hs, hs->szBuildFile, PfnParseProc, bHasHeader);
}
return dwErrCode;
}
DWORD LoadCdnsFile(TCascStorage * hs)
{
CASC_CDN_DOWNLOAD CdnsInfo = {0};
TCHAR szLocalPath[MAX_PATH];
QUERY_KEY FileData;
DWORD dwErrCode = ERROR_SUCCESS;
// Sanity checks
@@ -1389,19 +1446,12 @@ DWORD LoadCdnsFile(TCascStorage * hs)
if(InvokeProgressCallback(hs, "Downloading the \"cdns\" file", NULL, 0, 0))
return ERROR_CANCELLED;
// Prepare the download structure
CdnsInfo.szCdnsHost = _T("us.patch.battle.net");
CdnsInfo.szCdnsPath = hs->szCodeName;
CdnsInfo.szPathType = _T("");
CdnsInfo.szFileName = _T("cdns");
CdnsInfo.szLocalPath = szLocalPath;
CdnsInfo.ccLocalPath = _countof(szLocalPath);
CdnsInfo.Flags = CASC_CDN_FLAG_PORT1119 | CASC_CDN_FORCE_DOWNLOAD;
// Download file and parse it
if((dwErrCode = DownloadFileFromCDN(hs, CdnsInfo)) == ERROR_SUCCESS)
// Download the file using Ribbit protocol
dwErrCode = RibbitDownloadFile(hs->szCodeName, _T("cdns"), FileData);
if(dwErrCode == ERROR_SUCCESS)
{
dwErrCode = LoadCsvFile(hs, szLocalPath, ParseFile_CDNS, true);
// Parse the downloaded file
dwErrCode = LoadCsvFile(hs, FileData.pbData, FileData.cbData, ParseFile_CDNS, true);
}
return dwErrCode;
@@ -1524,7 +1574,7 @@ LPBYTE LoadFileToMemory(LPCTSTR szFileName, DWORD * pcbFileData)
FileStream_GetSize(pStream, &FileSize);
cbFileData = (DWORD)FileSize;
// Do not load zero files or too larget files
// Do not load zero files or too large files
if(0 < FileSize && FileSize <= 0x2000000)
{
// Allocate file data buffer. Make it 1 byte longer
@@ -1532,7 +1582,12 @@ LPBYTE LoadFileToMemory(LPCTSTR szFileName, DWORD * pcbFileData)
pbFileData = CASC_ALLOC<BYTE>(cbFileData + 1);
if(pbFileData != NULL)
{
if(!FileStream_Read(pStream, NULL, pbFileData, cbFileData))
if(FileStream_Read(pStream, NULL, pbFileData, cbFileData))
{
// Terminate the data with zero so various string-based functions can process it
pbFileData[cbFileData] = 0;
}
else
{
CASC_FREE(pbFileData);
cbFileData = 0;

View File

@@ -118,6 +118,7 @@ static bool IndexDirectory_OnFileFound(
static LPTSTR CreateIndexFileName(TCascStorage * hs, DWORD IndexValue, DWORD IndexVersion)
{
TCHAR szFullName[MAX_PATH];
TCHAR szPlainName[0x40];
// Sanity checks
@@ -127,7 +128,10 @@ static LPTSTR CreateIndexFileName(TCascStorage * hs, DWORD IndexValue, DWORD Ind
// Create the full path
CascStrPrintf(szPlainName, _countof(szPlainName), hs->szIndexFormat, IndexValue, IndexVersion);
return CombinePath(hs->szIndexPath, szPlainName);
CombinePath(szFullName, _countof(szFullName), hs->szIndexPath, szPlainName, NULL);
// Return allocated path
return CascNewStr(szFullName);
}
static void SaveFileOffsetBitsAndEKeyLength(TCascStorage * hs, BYTE FileOffsetBits, BYTE EKeyLength)

View File

@@ -74,8 +74,8 @@ extern "C" {
//-----------------------------------------------------------------------------
// Defines
#define CASCLIB_VERSION 0x0200 // CascLib version - integral (1.50)
#define CASCLIB_VERSION_STRING "2.0" // CascLib version - string
#define CASCLIB_VERSION 0x0210 // CascLib version - integral (2.1)
#define CASCLIB_VERSION_STRING "2.1" // CascLib version - string
// Values for CascOpenFile
#define CASC_OPEN_BY_NAME 0x00000000 // Open the file by name. This is the default value

View File

@@ -135,6 +135,11 @@ TCascStorage * TCascStorage::Release()
// Need this to be atomic to make multi-threaded file opens work
if(CascInterlockedDecrement(&dwRefCount) == 0)
{
// Release all references in the socket cache
if(dwFeatures & CASC_FEATURE_ONLINE)
sockets_set_caching(false);
// Delete the object and return NULL
delete this;
return NULL;
}
@@ -162,16 +167,16 @@ void * ProbeOutputBuffer(void * pvBuffer, size_t cbLength, size_t cbMinLength, s
static LPTSTR CheckForIndexDirectory(TCascStorage * hs, LPCTSTR szSubDir)
{
LPTSTR szIndexPath;
TCHAR szIndexPath[MAX_PATH];
// Combine the index path
szIndexPath = CombinePath(hs->szDataPath, szSubDir);
if (!DirectoryExists(szIndexPath))
{
CASC_FREE(szIndexPath);
}
CombinePath(szIndexPath, _countof(szIndexPath), hs->szDataPath, szSubDir, NULL);
return szIndexPath;
// Check whether the path exists
if(!DirectoryExists(szIndexPath))
return NULL;
return CascNewStr(szIndexPath);
}
// Inserts an entry from the text build file
@@ -1168,9 +1173,14 @@ static DWORD LoadCascStorage(TCascStorage * hs, PCASC_OPEN_STORAGE_ARGS pArgs)
if(ExtractVersionedArgument(pArgs, FIELD_OFFSET(CASC_OPEN_STORAGE_ARGS, szBuildKey), &szBuildKey) && szBuildKey != NULL)
hs->szBuildKey = CascNewStrT2A(szBuildKey);
// For online storages, we need to load CDN servers
if ((dwErrCode == ERROR_SUCCESS) && (hs->dwFeatures & CASC_FEATURE_ONLINE))
// Special handling to online storages
if(hs->dwFeatures & CASC_FEATURE_ONLINE)
{
// Enable caching of the sockets. This will add references
// to all existing and all future sockets
sockets_set_caching(true);
// For online storages, we need to load CDN servers
dwErrCode = LoadCdnsFile(hs);
}

View File

@@ -13,8 +13,8 @@
#define __CASCPORT_H__
#ifndef __cplusplus
#define bool char
#define true 1
#define bool char
#define true 1
#define false 0
#endif
@@ -43,9 +43,9 @@
#include <direct.h>
#include <malloc.h>
#include <windows.h>
#include <wininet.h>
#include <ws2tcpip.h>
#include <strsafe.h>
#include <sys/types.h>
#define PLATFORM_LITTLE_ENDIAN
#pragma intrinsic(memset, memcmp, memcpy) // Make these functions intrinsic (inline)
@@ -56,6 +56,7 @@
#define PLATFORM_WINDOWS
#define PLATFORM_DEFINED // The platform is known now
#endif
#ifndef FIELD_OFFSET
@@ -70,6 +71,7 @@
// Macintosh
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <dirent.h>
@@ -85,6 +87,7 @@
#include <cassert>
#include <errno.h>
#include <pthread.h>
#include <netdb.h>
// Support for PowerPC on Max OS X
#if (__ppc__ == 1) || (__POWERPC__ == 1) || (_ARCH_PPC == 1)
@@ -104,6 +107,8 @@
#define PATH_SEP_CHAR '/'
#define PATH_SEP_STRING "/"
typedef int SOCKET;
#define PLATFORM_MAC
#define PLATFORM_DEFINED // The platform is known now
@@ -115,6 +120,7 @@
#if !defined(PLATFORM_DEFINED)
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <dirent.h>
@@ -130,11 +136,14 @@
#include <assert.h>
#include <errno.h>
#include <pthread.h>
#include <netdb.h>
#define URL_SEP_CHAR '/'
#define PATH_SEP_CHAR '/'
#define PATH_SEP_STRING "/"
typedef int SOCKET;
#define PLATFORM_LITTLE_ENDIAN
#define PLATFORM_LINUX
#define PLATFORM_DEFINED
@@ -202,6 +211,8 @@
#define _tcsicmp strcasecmp
#define _tcsnicmp strncasecmp
#define closesocket close
#endif // !PLATFORM_WINDOWS
// 64-bit calls are supplied by "normal" calls on Mac
@@ -235,6 +246,7 @@
#define ERROR_FILE_ENCRYPTED 1005 // Returned by encrypted stream when can't find file key
#define ERROR_FILE_TOO_LARGE 1006 // No such error code under Linux
#define ERROR_ARITHMETIC_OVERFLOW 1007 // The string value is too large to fit in the given type
#define ERROR_NETWORK_NOT_AVAILABLE 1008 // Cannot connect to the internet
#endif
#ifndef ERROR_FILE_INCOMPLETE

View File

@@ -48,7 +48,7 @@ static DWORD OpenDataStream(TCascFile * hf, PCASC_FILE_SPAN pFileSpan, PCASC_CKE
{
// Prepare the name of the data file
CascStrPrintf(szPlainName, _countof(szPlainName), _T("data.%03u"), dwArchiveIndex);
CombinePath(szDataFile, _countof(szDataFile), PATH_SEP_CHAR, hs->szIndexPath, szPlainName, NULL);
CombinePath(szDataFile, _countof(szDataFile), hs->szIndexPath, szPlainName, NULL);
// Open the data stream with read+write sharing to prevent Battle.net agent
// detecting a corruption and redownloading the entire package

View File

@@ -163,7 +163,7 @@ struct TRootHandler_WoW : public TFileTreeRoot
pbRootPtr = pbRootPtr + (sizeof(CONTENT_KEY) * RootGroup.Header.NumberOfFiles);
// Also include array of file hashes
if(FileCounter > FileCounterHashless)
if(!(RootGroup.Header.ContentFlags & CASC_CFLAG_NO_NAME_HASH))
{
if((pbRootPtr + (sizeof(ULONGLONG) * RootGroup.Header.NumberOfFiles)) > pbRootEnd)
return NULL;

View File

@@ -27,8 +27,8 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 1,50,0,204
PRODUCTVERSION 1,50,0,204
FILEVERSION 1,50,0,205
PRODUCTVERSION 1,50,0,205
FILEFLAGSMASK 0x17L
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -45,12 +45,12 @@ BEGIN
BEGIN
VALUE "Comments", "http://www.zezula.net/casc.html"
VALUE "FileDescription", "CascLib library for reading Blizzard CASC storages"
VALUE "FileVersion", "1, 50, 0, 204\0"
VALUE "FileVersion", "1, 50, 0, 205\0"
VALUE "InternalName", "CascLib"
VALUE "LegalCopyright", "Copyright (c) 2014 - 2019 Ladislav Zezula"
VALUE "OriginalFilename", "CascLib.dll"
VALUE "ProductName", "CascLib"
VALUE "ProductVersion", "1, 50, 0, 204\0"
VALUE "ProductVersion", "1, 50, 0, 205\0"
END
END
BLOCK "VarFileInfo"

View File

@@ -454,9 +454,9 @@ bool CutLastPathPart(LPTSTR szWorkPath)
return true;
}
size_t CombinePath(LPTSTR szBuffer, size_t nMaxChars, char chSeparator, va_list argList)
size_t CombinePath(LPTSTR szBuffer, size_t nMaxChars, va_list argList)
{
CASC_PATH<TCHAR> Path(chSeparator);
CASC_PATH<TCHAR> Path(PATH_SEP_CHAR);
LPCTSTR szFragment;
bool bWithSeparator = false;
@@ -470,28 +470,18 @@ size_t CombinePath(LPTSTR szBuffer, size_t nMaxChars, char chSeparator, va_list
return Path.Copy(szBuffer, nMaxChars);
}
size_t CombinePath(LPTSTR szBuffer, size_t nMaxChars, char chSeparator, ...)
size_t CombinePath(LPTSTR szBuffer, size_t nMaxChars, ...)
{
va_list argList;
size_t nLength;
va_start(argList, chSeparator);
nLength = CombinePath(szBuffer, nMaxChars, chSeparator, argList);
va_start(argList, nMaxChars);
nLength = CombinePath(szBuffer, nMaxChars, argList);
va_end(argList);
return nLength;
}
LPTSTR CombinePath(LPCTSTR szDirectory, LPCTSTR szSubDir)
{
CASC_PATH<TCHAR> Path(PATH_SEP_CHAR);
// Merge the path
Path.AppendString(szDirectory, false);
Path.AppendString(szSubDir, true);
return Path.New();
}
size_t NormalizeFileName(const unsigned char * NormTable, char * szNormName, const char * szFileName, size_t cchMaxChars)
{
char * szNormNameEnd = szNormName + cchMaxChars;

View File

@@ -328,9 +328,8 @@ wchar_t * CascNewStr(const wchar_t * szString, size_t nCharsToReserve = 0);
LPSTR CascNewStrT2A(LPCTSTR szString, size_t nCharsToReserve = 0);
LPTSTR CascNewStrA2T(LPCSTR szString, size_t nCharsToReserve = 0);
size_t CombinePath(LPTSTR szBuffer, size_t nMaxChars, char chSeparator, va_list argList);
size_t CombinePath(LPTSTR szBuffer, size_t nMaxChars, char chSeparator, ...);
LPTSTR CombinePath(LPCTSTR szPath, LPCTSTR szSubDir);
size_t CombinePath(LPTSTR szBuffer, size_t nMaxChars, va_list argList);
size_t CombinePath(LPTSTR szBuffer, size_t nMaxChars, ...);
LPTSTR GetLastPathPart(LPTSTR szWorkPath);
bool CutLastPathPart(LPTSTR szWorkPath);
@@ -438,7 +437,7 @@ xchar * StringFromBinary(LPBYTE pbBinary, size_t cbBinary, xchar * szBuffer)
}
//-----------------------------------------------------------------------------
// Structure query key
// Structures for data blobs
struct QUERY_KEY
{
@@ -454,6 +453,16 @@ struct QUERY_KEY
cbData = 0;
}
DWORD SetData(const void * pv, size_t cb)
{
if((pbData = CASC_ALLOC<BYTE>(cb)) == NULL)
return ERROR_NOT_ENOUGH_MEMORY;
memcpy(pbData, pv, cb);
cbData = cb;
return ERROR_SUCCESS;
}
LPBYTE pbData;
size_t cbData;
};

View File

@@ -21,7 +21,7 @@ static const CASC_CSV_LINE NullLine;
//-----------------------------------------------------------------------------
// Local functions
static char * NextLine(char * szLine)
static char * NextLine_Default(void * /* pvUserData */, char * szLine)
{
// Find the end of the line
while(szLine[0] != 0 && szLine[0] != 0x0A && szLine[0] != 0x0D)
@@ -35,7 +35,7 @@ static char * NextLine(char * szLine)
return (szLine[0] != 0) ? szLine : NULL;
}
static char * NextColumn(char * szColumn)
static char * NextColumn_Default(void * /* pvUserData */, char * szColumn)
{
// Find the end of the column
while(szColumn[0] != 0 && szColumn[0] != '|')
@@ -86,6 +86,7 @@ CASC_CSV_LINE::~CASC_CSV_LINE()
bool CASC_CSV_LINE::SetLine(CASC_CSV * pParent, char * szCsvLine)
{
CASC_CSV_NEXTPROC PfnNextColumn = pParent->GetNextColumnProc();
char * szCsvColumn;
size_t nHdrColumns = 0;
size_t nColumns = 0;
@@ -99,7 +100,7 @@ bool CASC_CSV_LINE::SetLine(CASC_CSV * pParent, char * szCsvLine)
{
// Get current line and the next one
szCsvColumn = szCsvLine;
szCsvLine = NextColumn(szCsvLine);
szCsvLine = PfnNextColumn(pParent->GetUserData(), szCsvLine);
// Save the current line
Columns[nColumns].szValue = szCsvColumn;
@@ -154,12 +155,17 @@ CASC_CSV::CASC_CSV(size_t nLinesMax, bool bHasHeader)
{
// Initialize the class variables
memset(HashTable, 0xFF, sizeof(HashTable));
m_pvUserData = NULL;
m_szCsvFile = NULL;
m_szCsvPtr = NULL;
m_nCsvFile = 0;
m_nLines = 0;
m_bHasHeader = bHasHeader;
// Initialize the "NextLine" function that will serve for finding next line in the text
PfnNextLine = NextLine_Default;
PfnNextColumn = NextColumn_Default;
// Nonzero number of lines means a finite CSV handler. The CSV will be loaded at once.
// Zero means that the user needs to call LoadNextLine() and then the line data
if(nLinesMax != 0)
@@ -185,6 +191,21 @@ CASC_CSV::~CASC_CSV()
m_szCsvFile = NULL;
}
DWORD CASC_CSV::SetNextLineProc(CASC_CSV_NEXTPROC PfnNextLineProc, CASC_CSV_NEXTPROC PfnNextColProc, void * pvUserData)
{
// Set the procedure for next line parsing
if(PfnNextLineProc != NULL)
PfnNextLine = PfnNextLineProc;
// Set procedure for next columne parsing
if(PfnNextColProc != NULL)
PfnNextColumn = PfnNextColProc;
// Save the user data
m_pvUserData = pvUserData;
return ERROR_SUCCESS;
}
DWORD CASC_CSV::Load(LPCTSTR szFileName)
{
DWORD cbFileData = 0;
@@ -193,8 +214,7 @@ DWORD CASC_CSV::Load(LPCTSTR szFileName)
m_szCsvFile = (char *)LoadFileToMemory(szFileName, &cbFileData);
if (m_szCsvFile != NULL)
{
// There is one extra byte reserved by LoadFileToMemory, so we can put zero there
m_szCsvFile[cbFileData] = 0;
// Assign the data to the CSV object
m_szCsvPtr = m_szCsvFile;
m_nCsvFile = cbFileData;
@@ -282,7 +302,7 @@ bool CASC_CSV::LoadNextLine(CASC_CSV_LINE & Line)
char * szCsvLine = m_szCsvPtr;
// Get the next line
m_szCsvPtr = NextLine(m_szCsvPtr);
m_szCsvPtr = PfnNextLine(m_pvUserData, m_szCsvPtr);
// Initialize the line
if (Line.SetLine(this, szCsvLine))

View File

@@ -19,6 +19,13 @@
#define CSV_MAX_COLUMNS 0x20
#define CSV_HASH_TABLE_SIZE 0x80
//-----------------------------------------------------------------------------
// Interface for finding of next text element (line, column)
// The function must find the next (line|column), put zero there and return begin
// of the next (line|column). In case there is no next (line|column), the function returns NULL
typedef char * (*CASC_CSV_NEXTPROC)(void * pvUserData, char * szLine);
//-----------------------------------------------------------------------------
// Class for CSV line
@@ -69,6 +76,12 @@ class CASC_CSV
CASC_CSV(size_t nLinesMax, bool bHasHeader);
~CASC_CSV();
DWORD SetNextLineProc(CASC_CSV_NEXTPROC PfnNextLineProc, CASC_CSV_NEXTPROC PfnNextColProc = NULL, void * pvUserData = NULL);
CASC_CSV_NEXTPROC GetNextColumnProc()
{
return PfnNextColumn;
}
DWORD Load(LPBYTE pbData, size_t cbData);
DWORD Load(LPCTSTR szFileName);
bool LoadNextLine();
@@ -79,6 +92,11 @@ class CASC_CSV
size_t GetHeaderColumns() const;
size_t GetColumnIndex(const char * szColumnName) const;
void * GetUserData() const
{
return m_pvUserData;
}
size_t GetLineCount() const
{
return m_nLines;
@@ -90,9 +108,13 @@ class CASC_CSV
bool LoadNextLine(CASC_CSV_LINE & Line);
bool ParseCsvData();
CASC_CSV_NEXTPROC PfnNextLine;
CASC_CSV_NEXTPROC PfnNextColumn;
CASC_CSV_LINE * m_pLines;
CASC_CSV_LINE Header;
BYTE HashTable[CSV_HASH_TABLE_SIZE];
void * m_pvUserData;
char * m_szCsvFile;
char * m_szCsvPtr;
size_t m_nCsvFile;

View File

@@ -60,13 +60,11 @@ int ScanIndexDirectory(
#ifdef PLATFORM_WINDOWS
WIN32_FIND_DATA wf;
LPTSTR szSearchMask;
HANDLE hFind;
TCHAR szSearchMask[MAX_PATH];
// Prepare the search mask
szSearchMask = CombinePath(szIndexPath, _T("*"));
if(szSearchMask == NULL)
return ERROR_NOT_ENOUGH_MEMORY;
CombinePath(szSearchMask, _countof(szSearchMask), szIndexPath, _T("*"), NULL);
// Prepare directory search
hFind = FindFirstFile(szSearchMask, &wf);
@@ -87,8 +85,6 @@ int ScanIndexDirectory(
FindClose(hFind);
}
CASC_FREE(szSearchMask);
#else // PLATFORM_WINDOWS
struct dirent * dir_entry;

View File

@@ -18,13 +18,17 @@
#include "../CascCommon.h"
#ifdef _MSC_VER
#pragma comment(lib, "wininet.lib") // Internet functions for HTTP stream
#pragma warning(disable: 4800) // 'BOOL' : forcing value to bool 'true' or 'false' (performance warning)
#endif
//-----------------------------------------------------------------------------
// Local functions - platform-specific functions
static ULONGLONG GetByteOffset(ULONGLONG * ByteOffset1, ULONGLONG ByteOffset2)
{
return (ByteOffset1 != NULL) ? ByteOffset1[0] : ByteOffset2;
}
static DWORD StringToInt(const char * szString)
{
DWORD dwValue = 0;
@@ -165,7 +169,7 @@ static bool BaseFile_Read(
// Synchronize the access to the TFileStream structure
CascLock(pStream->Lock);
{
ULONGLONG ByteOffset = (pByteOffset != NULL) ? *pByteOffset : pStream->Base.File.FilePos;
ULONGLONG ByteOffset = GetByteOffset(pByteOffset, pStream->Base.File.FilePos);
#ifdef PLATFORM_WINDOWS
{
@@ -265,7 +269,7 @@ static bool BaseFile_Write(TFileStream * pStream, ULONGLONG * pByteOffset, const
// Synchronize the access to the TFileStream structure
CascLock(pStream->Lock);
{
ULONGLONG ByteOffset = (pByteOffset != NULL) ? *pByteOffset : pStream->Base.File.FilePos;
ULONGLONG ByteOffset = GetByteOffset(pByteOffset, pStream->Base.File.FilePos);
#ifdef PLATFORM_WINDOWS
{
@@ -557,7 +561,7 @@ static bool BaseMap_Read(
void * pvBuffer, // Pointer to data to be read
DWORD dwBytesToRead) // Number of bytes to read from the file
{
ULONGLONG ByteOffset = (pByteOffset != NULL) ? *pByteOffset : pStream->Base.Map.FilePos;
ULONGLONG ByteOffset = GetByteOffset(pByteOffset, pStream->Base.Map.FilePos);
// Do we have to read anything at all?
if(dwBytesToRead != 0)
@@ -607,150 +611,100 @@ static void BaseMap_Init(TFileStream * pStream)
//-----------------------------------------------------------------------------
// Local functions - base HTTP file support
static LPCTSTR BaseHttp_ExtractServerName(LPCTSTR szFileName, LPTSTR szServerName)
static DWORD BaseHttp_ParseURL(TFileStream * pStream, LPCTSTR szFileName)
{
// Check for HTTP
if(!_tcsnicmp(szFileName, _T("http://"), 7))
szFileName += 7;
LPCTSTR szFilePtr = szFileName;
char * hostName;
char * fileName;
// Cut off the server name
if(szServerName != NULL)
// Find the end od the host name
if((szFilePtr = _tcschr(szFileName, '/')) == NULL)
return ERROR_INVALID_PARAMETER;
// Allocate and copy the host name
if((hostName = CASC_ALLOC<char>(szFilePtr - szFileName + 1)) != NULL)
{
while(szFileName[0] != 0 && szFileName[0] != _T('/'))
*szServerName++ = *szFileName++;
*szServerName = 0;
}
else
{
while(szFileName[0] != 0 && szFileName[0] != _T('/'))
szFileName++;
CascStrCopy(hostName, 256, szFileName, (szFilePtr - szFileName));
// Allocate and copy the resource name
if((fileName = CascNewStrT2A(szFilePtr)) != NULL)
{
pStream->Base.Socket.hostName = hostName;
pStream->Base.Socket.fileName = fileName;
return ERROR_SUCCESS;
}
CASC_FREE(hostName);
}
// Return the remainder
return szFileName;
return ERROR_NOT_ENOUGH_MEMORY;
}
static bool BaseHttp_Download(TFileStream * pStream)
{
CASC_MIME Mime;
const char * request_mask = "GET %s HTTP/1.1\r\nHost: %s\r\nConnection: Keep-Alive\r\n\r\n";
char * server_response;
char * fileName = pStream->Base.Socket.fileName;
char request[0x100];
size_t response_length = 0;
size_t request_length = 0;
DWORD dwErrCode;
// If we already have the data, it's success
if(pStream->Base.Socket.fileData == NULL)
{
// Construct the request, either HTTP or Ribbit (https://wowdev.wiki/Ribbit).
// Note that Ribbit requests don't start with slash
if((pStream->dwFlags & BASE_PROVIDER_MASK) == BASE_PROVIDER_RIBBIT)
{
if(fileName[0] == '/')
fileName++;
request_mask = "%s\r\n";
}
// Send the request and receive decoded response
request_length = CascStrPrintf(request, _countof(request), request_mask, fileName, pStream->Base.Socket.hostName);
server_response = pStream->Base.Socket.pSocket->ReadResponse(request, request_length, &response_length);
if(server_response != NULL)
{
// Decode the MIME document
if((dwErrCode = Mime.Load(server_response, response_length)) == ERROR_SUCCESS)
{
// Move the data from MIME to HTTP stream
pStream->Base.Socket.fileData = Mime.GiveAway(&pStream->Base.Socket.fileDataLength);
}
CASC_FREE(server_response);
}
}
// If we have data loaded, return true
return (pStream->Base.Socket.fileData != NULL);
}
static bool BaseHttp_Open(TFileStream * pStream, LPCTSTR szFileName, DWORD dwStreamFlags)
{
#ifdef PLATFORM_WINDOWS
DWORD dwErrCode;
INTERNET_PORT ServerPort = INTERNET_DEFAULT_HTTP_PORT;
HINTERNET hRequest;
DWORD dwTemp = 0;
bool bFileAvailable = false;
DWORD dwErrCode = ERROR_SUCCESS;
// Check alternate ports
if(dwStreamFlags & STREAM_FLAG_USE_PORT_1119)
// Extract the server part
if((dwErrCode = BaseHttp_ParseURL(pStream, szFileName)) == ERROR_SUCCESS)
{
ServerPort = 1119;
}
// Determine the proper port
PCASC_SOCKET pSocket;
int portNum = ((dwStreamFlags & BASE_PROVIDER_MASK) == BASE_PROVIDER_RIBBIT) ? CASC_PORT_RIBBIT : CASC_PORT_HTTP;
// Don't download if we are not connected to the internet
if(!InternetGetConnectedState(&dwTemp, 0))
dwErrCode = GetCascError();
// Initiate the connection to the internet
if(dwErrCode == ERROR_SUCCESS)
{
pStream->Base.Http.hInternet = InternetOpen(_T("agent/2.17.2.6700"),
INTERNET_OPEN_TYPE_PRECONFIG,
NULL,
NULL,
0);
if(pStream->Base.Http.hInternet == NULL)
dwErrCode = GetCascError();
}
// Connect to the server
if(dwErrCode == ERROR_SUCCESS)
{
TCHAR szServerName[MAX_PATH];
DWORD dwFlags = INTERNET_FLAG_KEEP_CONNECTION | INTERNET_FLAG_NO_UI | INTERNET_FLAG_NO_CACHE_WRITE;
// Initiate connection with the server
szFileName = BaseHttp_ExtractServerName(szFileName, szServerName);
pStream->Base.Http.hConnect = InternetConnect(pStream->Base.Http.hInternet,
szServerName,
ServerPort,
NULL,
NULL,
INTERNET_SERVICE_HTTP,
dwFlags,
0);
if(pStream->Base.Http.hConnect == NULL)
dwErrCode = GetCascError();
}
// Now try to query the file size
if(dwErrCode == ERROR_SUCCESS)
{
// Open HTTP request to the file
hRequest = HttpOpenRequest(pStream->Base.Http.hConnect, _T("GET"), szFileName, NULL, NULL, NULL, INTERNET_FLAG_NO_CACHE_WRITE, 0);
if(hRequest != NULL)
// Initiate the remote connection
if((pSocket = sockets_connect(pStream->Base.Socket.hostName, portNum)) != NULL)
{
if(HttpSendRequest(hRequest, NULL, 0, NULL, 0))
{
ULONGLONG FileTime = 0;
LPTSTR szEndPtr;
TCHAR szStatusCode[0x10];
DWORD dwStatusCode = 404;
DWORD dwFileSize = 0;
DWORD dwDataSize = sizeof(szStatusCode);
DWORD dwIndex = 0;
// Check whether the file exists
if (HttpQueryInfo(hRequest, HTTP_QUERY_STATUS_CODE, szStatusCode, &dwDataSize, &dwIndex))
dwStatusCode = _tcstoul(szStatusCode, &szEndPtr, 10);
// Compare the status code
if (dwStatusCode == 200)
{
// Check if the archive has Last Modified field
dwDataSize = sizeof(ULONGLONG);
if (HttpQueryInfo(hRequest, HTTP_QUERY_LAST_MODIFIED | HTTP_QUERY_FLAG_SYSTEMTIME, &FileTime, &dwDataSize, &dwIndex))
pStream->Base.Http.FileTime = FileTime;
// Verify if the server supports random access
dwDataSize = sizeof(DWORD);
if (HttpQueryInfo(hRequest, HTTP_QUERY_CONTENT_LENGTH | HTTP_QUERY_FLAG_NUMBER, &dwFileSize, &dwDataSize, &dwIndex))
{
if (dwFileSize != 0)
{
pStream->Base.Http.FileSize = dwFileSize;
pStream->Base.Http.FilePos = 0;
bFileAvailable = true;
}
}
}
else
{
dwErrCode = ERROR_FILE_NOT_FOUND;
}
}
InternetCloseHandle(hRequest);
pStream->Base.Socket.pSocket = pSocket;
return true;
}
}
// If the file is not there and is not available for random access,
// report error
if(bFileAvailable == false)
{
pStream->BaseClose(pStream);
SetCascError(dwErrCode);
return false;
}
return true;
#else
// Not supported
SetCascError(ERROR_NOT_SUPPORTED);
pStream = pStream;
// Failure: set the last error and return false
SetCascError(dwErrCode);
return false;
#endif
}
static bool BaseHttp_Read(
@@ -759,90 +713,94 @@ static bool BaseHttp_Read(
void * pvBuffer, // Pointer to data to be read
DWORD dwBytesToRead) // Number of bytes to read from the file
{
#ifdef PLATFORM_WINDOWS
ULONGLONG ByteOffset = (pByteOffset != NULL) ? *pByteOffset : pStream->Base.Http.FilePos;
DWORD dwTotalBytesRead = 0;
ULONGLONG ByteOffset = GetByteOffset(pByteOffset, pStream->Base.Socket.fileDataPos);
bool bCanReadTheWholeRange = true;
// Do we have to read anything at all?
if(dwBytesToRead != 0)
// Synchronize the access to the TFileStream structure
CascLock(pStream->Lock);
{
HINTERNET hRequest;
LPCTSTR szFileName;
LPBYTE pbBuffer = (LPBYTE)pvBuffer;
TCHAR szRangeRequest[0x80];
DWORD dwStartOffset = (DWORD)ByteOffset;
DWORD dwEndOffset = dwStartOffset + dwBytesToRead;
// Open HTTP request to the file
szFileName = BaseHttp_ExtractServerName(pStream->szFileName, NULL);
hRequest = HttpOpenRequest(pStream->Base.Http.hConnect, _T("GET"), szFileName, NULL, NULL, NULL, INTERNET_FLAG_NO_CACHE_WRITE, 0);
if(hRequest != NULL)
// Do we have to read anything at all?
if(dwBytesToRead != 0)
{
// Add range request to the HTTP headers
// http://www.clevercomponents.com/articles/article015/resuming.asp
CascStrPrintf(szRangeRequest, _countof(szRangeRequest), _T("Range: bytes=%u-%u"), (unsigned int)dwStartOffset, (unsigned int)dwEndOffset);
HttpAddRequestHeaders(hRequest, szRangeRequest, 0xFFFFFFFF, HTTP_ADDREQ_FLAG_ADD_IF_NEW);
// Send the request to the server
if(HttpSendRequest(hRequest, NULL, 0, NULL, 0))
// Make sure that we have the file downloaded
if(!BaseHttp_Download(pStream))
{
while(dwTotalBytesRead < dwBytesToRead)
CascUnlock(pStream->Lock);
return false;
}
// Are we trying to read more than available?
if(ByteOffset <= pStream->Base.Socket.fileDataLength)
{
if((ByteOffset + dwBytesToRead) > pStream->Base.Socket.fileDataLength)
{
DWORD dwBlockBytesToRead = dwBytesToRead - dwTotalBytesRead;
DWORD dwBlockBytesRead = 0;
// Read the block from the file
if(dwBlockBytesToRead > 0x200)
dwBlockBytesToRead = 0x200;
InternetReadFile(hRequest, pbBuffer, dwBlockBytesToRead, &dwBlockBytesRead);
// Check for end
if(dwBlockBytesRead == 0)
break;
// Move buffers
dwTotalBytesRead += dwBlockBytesRead;
pbBuffer += dwBlockBytesRead;
bCanReadTheWholeRange = false;
dwBytesToRead = (DWORD)(pStream->Base.Socket.fileDataLength - ByteOffset);
}
}
InternetCloseHandle(hRequest);
}
}
else
{
bCanReadTheWholeRange = false;
dwBytesToRead = 0;
}
// Increment the current file position by number of bytes read
pStream->Base.Http.FilePos = ByteOffset + dwTotalBytesRead;
// Copy the data
if(dwBytesToRead != 0)
{
memcpy(pvBuffer, pStream->Base.Socket.fileData + ByteOffset, dwBytesToRead);
}
}
// Increment the current file position by number of bytes read
pStream->Base.Socket.fileDataPos = (size_t)(ByteOffset + dwBytesToRead);
}
CascUnlock(pStream->Lock);
// If the number of bytes read doesn't match the required amount, return false
if(dwTotalBytesRead != dwBytesToRead)
if(bCanReadTheWholeRange == false)
SetCascError(ERROR_HANDLE_EOF);
return (dwTotalBytesRead == dwBytesToRead);
return bCanReadTheWholeRange;
}
#else
// Gives the current file size
static bool BaseHttp_GetSize(TFileStream * pStream, ULONGLONG * pFileSize)
{
bool bResult;
// Not supported
pStream = pStream;
pByteOffset = pByteOffset;
pvBuffer = pvBuffer;
dwBytesToRead = dwBytesToRead;
SetCascError(ERROR_NOT_SUPPORTED);
return false;
// Synchronize the access to the TFileStream structure
CascLock(pStream->Lock);
{
// Make sure that we have the file data
bResult = BaseHttp_Download(pStream);
if(bResult)
{
*pFileSize = pStream->Base.Socket.fileDataLength;
}
}
CascUnlock(pStream->Lock);
#endif
// Give the result
return bResult;
}
static bool BaseHttp_GetPos(TFileStream * pStream, ULONGLONG * pByteOffset)
{
// Give the current position
*pByteOffset = pStream->Base.Socket.fileDataPos;
return true;
}
static void BaseHttp_Close(TFileStream * pStream)
{
#ifdef PLATFORM_WINDOWS
if(pStream->Base.Http.hConnect != NULL)
InternetCloseHandle(pStream->Base.Http.hConnect);
pStream->Base.Http.hConnect = NULL;
if(pStream->Base.Http.hInternet != NULL)
InternetCloseHandle(pStream->Base.Http.hInternet);
pStream->Base.Http.hInternet = NULL;
#else
pStream = pStream;
#endif
if(pStream->Base.Socket.fileData != NULL)
CASC_FREE(pStream->Base.Socket.fileData);
if(pStream->Base.Socket.hostName != NULL)
CASC_FREE(pStream->Base.Socket.hostName);
if(pStream->Base.Socket.fileName != NULL)
CASC_FREE(pStream->Base.Socket.fileName);
if(pStream->Base.Socket.pSocket != NULL)
pStream->Base.Socket.pSocket->Release();
memset(&pStream->Base.Socket, 0, sizeof(pStream->Base.Socket));
}
// Initializes base functions for the mapped file
@@ -851,8 +809,8 @@ static void BaseHttp_Init(TFileStream * pStream)
// Supply the stream functions
pStream->BaseOpen = BaseHttp_Open;
pStream->BaseRead = BaseHttp_Read;
pStream->BaseGetSize = BaseFile_GetSize; // Reuse BaseFile function
pStream->BaseGetPos = BaseFile_GetPos; // Reuse BaseFile function
pStream->BaseGetSize = BaseHttp_GetSize;
pStream->BaseGetPos = BaseHttp_GetPos;
pStream->BaseClose = BaseHttp_Close;
// HTTP files are read-only
@@ -894,7 +852,7 @@ static bool BlockStream_Read(
return true;
// Get the current position in the stream
ByteOffset = (pByteOffset != NULL) ? pByteOffset[0] : pStream->StreamPos;
ByteOffset = GetByteOffset(pByteOffset, pStream->StreamPos);
EndOffset = ByteOffset + dwBytesToRead;
if(EndOffset > pStream->StreamSize)
{
@@ -1029,11 +987,12 @@ static void BlockStream_Close(TBlockStream * pStream)
//-----------------------------------------------------------------------------
// File stream allocation function
static STREAM_INIT StreamBaseInit[4] =
static STREAM_INIT StreamBaseInit[5] =
{
BaseFile_Init,
BaseMap_Init,
BaseHttp_Init,
BaseHttp_Init, // Ribbit provider shares code with HTTP provider
BaseNone_Init
};
@@ -2526,42 +2485,50 @@ size_t FileStream_Prefix(LPCTSTR szFileName, DWORD * pdwProvider)
nPrefixLength1 = 5;
}
// Cut out the stream provider
szFileName += nPrefixLength1;
//
// Determine the base provider
//
if(!_tcsnicmp(szFileName+nPrefixLength1, _T("file:"), 5))
if(!_tcsnicmp(szFileName, _T("file:"), 5))
{
dwProvider |= BASE_PROVIDER_FILE;
nPrefixLength2 = 5;
}
else if(!_tcsnicmp(szFileName+nPrefixLength1, _T("map:"), 4))
else if(!_tcsnicmp(szFileName, _T("map:"), 4))
{
dwProvider |= BASE_PROVIDER_MAP;
nPrefixLength2 = 4;
}
else if(!_tcsnicmp(szFileName+nPrefixLength1, _T("http:"), 5))
else if(!_tcsnicmp(szFileName, _T("http:"), 5))
{
dwProvider |= BASE_PROVIDER_HTTP;
nPrefixLength2 = 5;
}
else if(!_tcsnicmp(szFileName, _T("ribbit:"), 7))
{
dwProvider |= BASE_PROVIDER_RIBBIT;
nPrefixLength2 = 7;
}
// Only accept stream provider if we recognized the base provider
if(nPrefixLength2 != 0)
{
// It is also allowed to put "//" after the base provider, e.g. "file://", "http://"
if(szFileName[nPrefixLength1+nPrefixLength2] == '/' && szFileName[nPrefixLength1+nPrefixLength2+1] == '/')
if(szFileName[nPrefixLength2] == '/' && szFileName[nPrefixLength2+1] == '/')
nPrefixLength2 += 2;
if(pdwProvider != NULL)
*pdwProvider = dwProvider;
return nPrefixLength1 + nPrefixLength2;
}
}
return 0;
return nPrefixLength1 + nPrefixLength2;
}
/**

View File

@@ -16,7 +16,8 @@
#define BASE_PROVIDER_FILE 0x00000000 // Base data source is a file
#define BASE_PROVIDER_MAP 0x00000001 // Base data source is memory-mapped file
#define BASE_PROVIDER_HTTP 0x00000002 // Base data source is a file on web server
#define BASE_PROVIDER_HTTP 0x00000002 // Base data source is a file on web server via the HTTP protocol
#define BASE_PROVIDER_RIBBIT 0x00000003 // Base data source is a file on web server via the Ribbit protocol
#define BASE_PROVIDER_MASK 0x0000000F // Mask for base provider value
#define STREAM_PROVIDER_FLAT 0x00000000 // Stream is linear with no offset mapping
@@ -29,7 +30,6 @@
#define STREAM_FLAG_WRITE_SHARE 0x00000200 // Allow write sharing when open for write
#define STREAM_FLAG_USE_BITMAP 0x00000400 // If the file has a file bitmap, load it and use it
#define STREAM_FLAG_FILL_MISSING 0x00000800 // If less than expected was read from the file, fill the missing part with zeros
#define STREAM_FLAG_USE_PORT_1119 0x00001000 // For HTTP streams, use port 1119
#define STREAM_OPTIONS_MASK 0x0000FF00 // Mask for stream options
#define STREAM_PROVIDERS_MASK 0x000000FF // Mask to get stream providers
@@ -172,12 +172,13 @@ union TBaseProviderData
struct
{
ULONGLONG FileSize; // Size of the file
ULONGLONG FilePos; // Current file position
ULONGLONG FileTime; // Last write time
HANDLE hInternet; // Internet handle
HANDLE hConnect; // Connection to the internet server
} Http;
class CASC_SOCKET * pSocket; // An open socket
unsigned char * fileData; // Raw response converted to file data
char * hostName; // Name of the remote host
char * fileName; // Name of the remote resource
size_t fileDataLength; // Length of the file data, in bytes
size_t fileDataPos; // Current position in the data
} Socket;
};
struct TFileStream
@@ -249,7 +250,7 @@ struct TEncryptedStream : public TBlockStream
// Public functions for file stream
TFileStream * FileStream_CreateFile(LPCTSTR szFileName, DWORD dwStreamFlags);
TFileStream * FileStream_OpenFile(LPCTSTR szFileName, DWORD dwStreamFlags);
TFileStream * FileStream_OpenFile(LPCTSTR szFileName, DWORD dwStreamFlags = 0);
LPCTSTR FileStream_GetFileName(TFileStream * pStream);
size_t FileStream_Prefix(LPCTSTR szFileName, DWORD * pdwProvider);

View File

@@ -0,0 +1,674 @@
/*****************************************************************************/
/* Mime.cpp Copyright (c) Ladislav Zezula 2021 */
/*---------------------------------------------------------------------------*/
/* Mime functions for CascLib */
/*---------------------------------------------------------------------------*/
/* Date Ver Who Comment */
/* -------- ---- --- ------- */
/* 21.01.21 1.00 Lad Created */
/*****************************************************************************/
#define __CASCLIB_SELF__
#include "../CascLib.h"
#include "../CascCommon.h"
//-----------------------------------------------------------------------------
// Local variables
#define BASE64_INVALID_CHAR 0xFF
#define BASE64_WHITESPACE_CHAR 0xFE
static const char * CascBase64Table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static unsigned char CascBase64ToBits[0x80] = {0};
//-----------------------------------------------------------------------------
// CASC_MIME_HTTP implementation
static size_t DecodeValueInt32(const char * string, const char * string_end)
{
size_t result = 0;
while(string < string_end && isdigit(string[0]))
{
result = (result * 10) + (string[0] - '0');
string++;
}
return result;
}
bool CASC_MIME_HTTP::IsDataComplete(const char * response, size_t response_length)
{
const char * content_length_ptr;
const char * content_begin_ptr;
// Do not parse the HTTP response multiple times
if(response_valid == 0 && response_length > 8)
{
// Check the begin of the response
if(!strncmp(response, "HTTP/1.1", 8))
{
// Check if there's begin of the content
if((content_begin_ptr = strstr(response, "\r\n\r\n")) != NULL)
{
// HTTP responses contain "Content-Length: %u\n\r"
if((content_length_ptr = strstr(response, "Content-Length: ")) != NULL)
{
// The content length info muse be before the actual content
if(content_length_ptr < content_begin_ptr)
{
// Fill the HTTP info cache
response_valid = 0x48545450; // 'HTTP'
content_offset = (content_begin_ptr + 4) - response;
content_length = DecodeValueInt32(content_length_ptr + 16, content_begin_ptr);
total_length = content_offset + content_length;
}
}
}
}
}
// If we know the expected total length, we can tell whether it's complete or not
return ((response_valid != 0) && (total_length == response_length));
}
//-----------------------------------------------------------------------------
// The MIME blob class
CASC_MIME_BLOB::CASC_MIME_BLOB(char * mime_ptr, char * mime_end)
{
ptr = mime_ptr;
end = mime_end;
}
CASC_MIME_BLOB::~CASC_MIME_BLOB()
{
ptr = end = NULL;
}
char * CASC_MIME_BLOB::GetNextLine()
{
char * mime_line = ptr;
while(ptr < end)
{
// Every line, even the last one, must be terminated with 0D 0A
if(ptr[0] == 0x0D && ptr[1] == 0x0A)
{
// If space or tabulator follows, then this is continuation of the line
if(ptr[2] == 0x09 || ptr[2] == 0x20)
{
ptr = ptr + 2;
continue;
}
// Terminate the line and return its begin
ptr[0] = 0;
ptr[1] = 0;
ptr = ptr + 2;
return mime_line;
}
// Move to tne next character
ptr++;
}
// No EOL terminated line found, break the search
return NULL;
}
//-----------------------------------------------------------------------------
// The MIME element class
CASC_MIME_ELEMENT::CASC_MIME_ELEMENT()
{
memset(this, 0, sizeof(CASC_MIME_ELEMENT));
}
CASC_MIME_ELEMENT::~CASC_MIME_ELEMENT()
{
// Free the children and next elements
if(folder.pChild != NULL)
delete folder.pChild;
folder.pChild = NULL;
if(folder.pNext != NULL)
delete folder.pNext;
folder.pNext = NULL;
// Free the data
if(data.begin != NULL)
CASC_FREE(data.begin);
data.begin = NULL;
}
unsigned char * CASC_MIME_ELEMENT::GiveAway(size_t * ptr_data_length)
{
unsigned char * give_away_data = data.begin;
size_t give_away_length = data.length;
// Clear the data (DO NOT FREE)
data.begin = NULL;
data.length = 0;
// Copy the data to local buffer
if(ptr_data_length != NULL)
ptr_data_length[0] = give_away_length;
return give_away_data;
}
DWORD CASC_MIME_ELEMENT::Load(char * mime_data_begin, char * mime_data_end, const char * boundary_ptr)
{
CASC_MIME_ENCODING Encoding = MimeEncodingTextPlain;
CASC_MIME_BLOB mime_data(mime_data_begin, mime_data_end);
CASC_MIME_HTTP HttpInfo;
size_t length_begin;
size_t length_end;
char * mime_line;
char boundary_begin[MAX_LENGTH_BOUNDARY + 2];
char boundary_end[MAX_LENGTH_BOUNDARY + 4];
DWORD dwErrCode = ERROR_SUCCESS;
bool mime_version = false;
// Diversion for HTTP: No need to parse the entire headers and stuff.
// Just give the data right away
if(HttpInfo.IsDataComplete(mime_data_begin, (mime_data_end - mime_data_begin)))
{
if((data.begin = CASC_ALLOC<BYTE>(HttpInfo.content_length)) == NULL)
return ERROR_NOT_ENOUGH_MEMORY;
memcpy(data.begin, mime_data_begin + HttpInfo.content_offset, HttpInfo.content_length);
data.length = HttpInfo.content_length;
return ERROR_SUCCESS;
}
// Reset the boundary
boundary[0] = 0;
// Parse line-by-line untile we find the end of data
while((mime_line = mime_data.GetNextLine()) != NULL)
{
// If the line is empty, this means that it's the end of the MIME header and begin of the MIME data
if(mime_line[0] == 0)
{
mime_data.ptr = mime_line + 2;
break;
}
// Root nodes are required to have MIME version
if(boundary_ptr == NULL && mime_version == false)
{
if(!strcmp(mime_line, "MIME-Version: 1.0"))
{
mime_version = true;
continue;
}
if(!strcmp(mime_line, "HTTP/1.1 200 OK"))
{
mime_version = true;
continue;
}
}
// Get the encoding
if(!strncmp(mime_line, "Content-Transfer-Encoding: ", 27))
{
ExtractEncoding(mime_line + 27, Encoding);
continue;
}
// Is there content type?
if(!strncmp(mime_line, "Content-Type: ", 14))
{
ExtractBoundary(mime_line + 14);
continue;
}
}
// Keep going only if we have MIME version
if(boundary_ptr != NULL || mime_version == true)
{
// If we have boundary, it means that the element begin
// and end is marked by the boundary
if(boundary[0])
{
CASC_MIME_ELEMENT * pLast = NULL;
CASC_MIME_ELEMENT * pChild;
CASC_MIME_BLOB sub_mime(mime_data.ptr, NULL);
// Construct the begin of the boundary. Don't include newline there,
// as it must also match end of the boundary
length_begin = CascStrPrintf(boundary_begin, _countof(boundary_begin), "--%s", boundary);
dwErrCode = ERROR_SUCCESS;
// The current line must point to the begin of the boundary
// Find the end of the boundary
if(memcmp(sub_mime.ptr, boundary_begin, length_begin))
return ERROR_BAD_FORMAT;
sub_mime.ptr += length_begin;
// Find the end of the boundary
length_end = CascStrPrintf(boundary_end, _countof(boundary_end), "--%s--\r\n", boundary);
sub_mime.end = strstr(sub_mime.ptr, boundary_end);
if(sub_mime.end == NULL)
return ERROR_BAD_FORMAT;
// This is the end of the MIME section. Cut it to blocks
// and put each into the separate CASC_MIME_ELEMENT
while(sub_mime.ptr < sub_mime.end)
{
char * sub_mime_next;
// At this point, there must be newline in the current data pointer
if(sub_mime.ptr[0] != 0x0D || sub_mime.ptr[1] != 0x0A)
return ERROR_BAD_FORMAT;
sub_mime.ptr += 2;
// Find the next MIME element. This must succeed, as the last element also matches the first element
sub_mime_next = strstr(sub_mime.ptr, boundary_begin);
if(sub_mime_next == NULL)
return ERROR_BAD_FORMAT;
// Allocate the element
pChild = AllocateAndLoadElement(sub_mime.ptr, sub_mime_next - 2, boundary_begin);
if(pChild == NULL)
{
dwErrCode = GetCascError();
break;
}
// Link the element
if(folder.pChild == NULL)
folder.pChild = pChild;
if(pLast != NULL)
pLast->folder.pNext = pChild;
pLast = pChild;
// Move to the next MIME element. Note that if we are at the ending boundary,
// this moves past the end, but it's OK, because the while loop will catch that
sub_mime.ptr = sub_mime_next + length_begin;
}
}
else
{
CASC_MIME_BLOB content(mime_data.ptr, NULL);
unsigned char * data_buffer;
size_t data_length = 0;
size_t raw_length;
// If we have boundary pointer, we need to cut the data up to the boundary end.
// Otherwise, we decode the data to the end of the document
if(boundary_ptr != NULL)
{
// Find the end of the current data by the boundary. It is 2 characters before the next boundary
content.end = strstr(content.ptr, boundary_ptr);
if(content.end == NULL)
return ERROR_BAD_FORMAT;
if((content.ptr + 2) >= content.end)
return ERROR_BAD_FORMAT;
content.end -= 2;
}
else
{
content.end = mime_data_end - 2;
if(content.end[0] != 0x0D || content.end[1] != 0x0A)
return ERROR_BAD_FORMAT;
if((content.ptr + 2) >= content.end)
return ERROR_BAD_FORMAT;
}
// Allocate buffer for decoded data.
// Make it the same size like source data plus zero at the end
raw_length = (content.end - content.ptr);
data_buffer = CASC_ALLOC<unsigned char>(raw_length);
if(data_buffer != NULL)
{
// Decode the data
switch(Encoding)
{
case MimeEncodingTextPlain:
dwErrCode = DecodeTextPlain(content.ptr, content.end, data_buffer, &data_length);
break;
case MimeEncodingQuotedPrintable:
dwErrCode = DecodeQuotedPrintable(content.ptr, content.end, data_buffer, &data_length);
break;
case MimeEncodingBase64:
dwErrCode = DecodeBase64(content.ptr, content.end, data_buffer, &data_length);
break;
default:;
// to remove warning
}
// If failed, free the buffer back
if(dwErrCode != ERROR_SUCCESS)
{
CASC_FREE(data_buffer);
data_buffer = NULL;
data_length = 0;
}
}
else
{
dwErrCode = ERROR_NOT_ENOUGH_MEMORY;
}
// Put the data there, even if they are invalid
data.begin = data_buffer;
data.length = data_length;
}
}
else
{
dwErrCode = ERROR_NOT_SUPPORTED;
}
// Return the result of the decoding
return dwErrCode;
}
#ifdef _DEBUG
#define MAX_LEVEL 0x10
void CASC_MIME_ELEMENT::Print(size_t nLevel, size_t nIndex)
{
char Prefix[(MAX_LEVEL * 4) + 0x20 + 1] = {0};
size_t nSpaces;
// Fill-in the spaces
nSpaces = (nLevel < MAX_LEVEL) ? (nLevel * 4) : (MAX_LEVEL * 4);
memset(Prefix, ' ', nSpaces);
// Print the spaces and index
nSpaces = printf("%s* [%u]: ", Prefix, (int)nIndex);
memset(Prefix, ' ', nSpaces);
// Is this a folder item?
if(folder.pChild != NULL)
{
printf("Folder item (boundary: %s)\n", boundary);
folder.pChild->Print(nLevel + 1, 0);
}
else
{
char data_printable[0x20] = {0};
for(size_t i = 0; (i < data.length && i < _countof(data_printable) - 1); i++)
{
if(0x20 <= data.begin[i] && data.begin[i] <= 0x7F)
data_printable[i] = data.begin[i];
else
data_printable[i] = '.';
}
printf("Data item (%u bytes): \"%s\"\n", (int)data.length, data_printable);
}
// Do we have a next element?
if(folder.pNext != NULL)
{
folder.pNext->Print(nLevel, nIndex + 1);
}
}
#endif
CASC_MIME_ELEMENT * CASC_MIME_ELEMENT::AllocateAndLoadElement(char * a_mime_data, char * a_mime_data_end, const char * boundary_begin)
{
CASC_MIME_ELEMENT * pElement;
DWORD dwErrCode = ERROR_NOT_ENOUGH_MEMORY;
// Allocate the element
if((pElement = new CASC_MIME_ELEMENT()) != NULL)
{
// Load the element
dwErrCode = pElement->Load(a_mime_data, a_mime_data_end, boundary_begin);
if(dwErrCode == ERROR_SUCCESS)
return pElement;
// Free the element on failure
delete pElement;
}
SetCascError(dwErrCode);
return NULL;
}
bool CASC_MIME_ELEMENT::ExtractEncoding(const char * line, CASC_MIME_ENCODING & Encoding)
{
if(!_stricmp(line, "base64"))
{
Encoding = MimeEncodingBase64;
return true;
}
if(!_stricmp(line, "quoted-printable"))
{
Encoding = MimeEncodingQuotedPrintable;
return true;
}
// Unknown encoding
return false;
}
bool CASC_MIME_ELEMENT::ExtractBoundary(const char * line)
{
const char * begin;
const char * end;
// Find the begin of the boundary
if((begin = strstr(line, "boundary=\"")) != NULL)
{
// Set begin of the boundary
begin = begin + 10;
// Is there also end?
if((end = strchr(begin, '\"')) != NULL)
{
CascStrCopy(boundary, _countof(boundary), begin, end - begin);
return true;
}
}
return false;
}
DWORD CASC_MIME_ELEMENT::DecodeTextPlain(char * content_begin, char * content_end, unsigned char * data_buffer, size_t * ptr_length)
{
size_t data_length = (size_t)(content_end - content_begin);
// Sanity checks
assert(content_begin && content_end);
assert(content_end > content_begin);
// Plain copy
memcpy(data_buffer, content_begin, data_length);
// Give the result
if(ptr_length != NULL)
ptr_length[0] = data_length;
return ERROR_SUCCESS;
}
DWORD CASC_MIME_ELEMENT::DecodeQuotedPrintable(char * content_begin, char * content_end, unsigned char * data_buffer, size_t * ptr_length)
{
unsigned char * save_data_buffer = data_buffer;
char * content_ptr;
DWORD dwErrCode;
// Sanity checks
assert(content_begin && content_end);
assert(content_end > content_begin);
// Decode the data
for(content_ptr = content_begin; content_ptr < content_end; )
{
// If the data begins with '=', there is either newline or 2-char hexa number
if(content_ptr[0] == '=')
{
// Is there a newline after the equal sign?
if(content_ptr[1] == 0x0D && content_ptr[2] == 0x0A)
{
content_ptr += 3;
continue;
}
// Is there hexa number after the equal sign?
dwErrCode = BinaryFromString(content_ptr + 1, 2, data_buffer);
if(dwErrCode != ERROR_SUCCESS)
return dwErrCode;
content_ptr += 3;
data_buffer++;
continue;
}
else
{
*data_buffer++ = (unsigned char)(*content_ptr++);
}
}
if(ptr_length != NULL)
ptr_length[0] = (size_t)(data_buffer - save_data_buffer);
return ERROR_SUCCESS;
}
DWORD CASC_MIME_ELEMENT::DecodeBase64(char * content_begin, char * content_end, unsigned char * data_buffer, size_t * ptr_length)
{
unsigned char * save_data_buffer = data_buffer;
DWORD BitBuffer = 0;
DWORD BitCount = 0;
BYTE OneByte;
// One time preparation of the conversion table
if(CascBase64ToBits[0] == 0)
{
// Fill the entire table with 0xFF to mark invalid characters
memset(CascBase64ToBits, BASE64_INVALID_CHAR, sizeof(CascBase64ToBits));
// Set all whitespace characters
for(BYTE i = 1; i <= 0x20; i++)
CascBase64ToBits[i] = BASE64_WHITESPACE_CHAR;
// Set all valid characters
for(BYTE i = 0; CascBase64Table[i] != 0; i++)
{
OneByte = CascBase64Table[i];
CascBase64ToBits[OneByte] = i;
}
}
// Do the decoding
while(content_begin < content_end && content_begin[0] != '=')
{
// Check for end of string
if(content_begin[0] > sizeof(CascBase64ToBits))
return ERROR_BAD_FORMAT;
if((OneByte = CascBase64ToBits[*content_begin++]) == BASE64_INVALID_CHAR)
return ERROR_BAD_FORMAT;
if(OneByte == BASE64_WHITESPACE_CHAR)
continue;
// Put the 6 bits into the bit buffer
BitBuffer = (BitBuffer << 6) | OneByte;
BitCount += 6;
// Flush all values
while(BitCount >= 8)
{
// Decrement the bit count in the bit buffer
BitCount -= 8;
// The byte is the upper 8 bits of the bit buffer
OneByte = (BYTE)(BitBuffer >> BitCount);
BitBuffer = BitBuffer % (1 << BitCount);
// Put the byte value. The buffer can not overflow,
// because it is guaranteed to be equal to the length of the base64 string
*data_buffer++ = OneByte;
}
}
// Return the decoded length
if(ptr_length != NULL)
ptr_length[0] = (data_buffer - save_data_buffer);
return ERROR_SUCCESS;
}
//-----------------------------------------------------------------------------
// The MIME class
CASC_MIME::CASC_MIME()
{}
CASC_MIME::~CASC_MIME()
{}
unsigned char * CASC_MIME::GiveAway(size_t * ptr_data_length)
{
CASC_MIME_ELEMENT * pElement = &root;
unsigned char * data;
// 1) Give the data from the root
if((data = root.GiveAway(ptr_data_length)) != NULL)
return data;
// 2) If we have children, then give away from the first child
pElement = root.GetChild();
if(pElement && (data = pElement->GiveAway(ptr_data_length)) != NULL)
return data;
// Return NULL
if(ptr_data_length != NULL)
ptr_data_length[0] = 0;
return NULL;
}
DWORD CASC_MIME::Load(char * data, size_t length)
{
// Clear the root element
memset(&root, 0, sizeof(CASC_MIME_ELEMENT));
//FILE * fp = fopen("E:\\html_response.txt", "wb");
//if(fp != NULL)
//{
// fwrite(data, 1, length, fp);
// fclose(fp);
//}
// Load the root element
return root.Load(data, data + length);
}
DWORD CASC_MIME::Load(LPCTSTR szFileName)
{
char * szFileData;
DWORD cbFileData = 0;
DWORD dwErrCode = ERROR_SUCCESS;
// Note that LoadFileToMemory allocated one byte more and puts zero at the end
// Thus, we can treat it as zero-terminated string
szFileData = (char *)LoadFileToMemory(szFileName, &cbFileData);
if(szFileData != NULL)
{
dwErrCode = Load(szFileData, cbFileData);
CASC_FREE(szFileData);
}
else
{
dwErrCode = GetCascError();
}
return dwErrCode;
}
#ifdef _DEBUG
void CASC_MIME::Print()
{
root.Print(0, 0);
}
#endif

View File

@@ -0,0 +1,130 @@
/*****************************************************************************/
/* Mime.h Copyright (c) Ladislav Zezula 2021 */
/*---------------------------------------------------------------------------*/
/* MIME parsing functions for CascLib */
/*---------------------------------------------------------------------------*/
/* Date Ver Who Comment */
/* -------- ---- --- ------- */
/* 21.01.21 1.00 Lad Created */
/*****************************************************************************/
#ifndef __MIME_H__
#define __MIME_H__
//-----------------------------------------------------------------------------
// MIME constants
#define MAX_LENGTH_BOUNDARY 128
enum CASC_MIME_ENCODING
{
MimeEncodingTextPlain,
MimeEncodingBase64,
MimeEncodingQuotedPrintable,
MimeEncodingMax
};
//-----------------------------------------------------------------------------
// Structure for caching parsed HTTP response information
struct CASC_MIME_HTTP
{
CASC_MIME_HTTP()
{
response_valid = content_length = content_offset = total_length = 0;
}
bool IsDataComplete(const char * response, size_t response_length);
size_t response_valid; // Nonzero if this is an already parsed HTTP response
size_t content_length; // Parsed value of "Content-Length"
size_t content_offset; // Offset of the HTTP data, relative to the begin of the response
size_t total_length; // Expected total length of the HTTP response (content_offset + content_size)
};
//-----------------------------------------------------------------------------
// MIME blob class
struct CASC_MIME_BLOB
{
CASC_MIME_BLOB(char * mime_ptr, char * mime_end);
~CASC_MIME_BLOB();
char * GetNextLine();
char * ptr;
char * end;
};
//-----------------------------------------------------------------------------
// MIME class
class CASC_MIME_ELEMENT
{
public:
CASC_MIME_ELEMENT();
~CASC_MIME_ELEMENT();
unsigned char * GiveAway(size_t * ptr_data_length);
DWORD Load(char * mime_data_begin, char * mime_data_end, const char * boundary_ptr = NULL);
CASC_MIME_ELEMENT * GetChild() { return folder.pChild; }
#ifdef _DEBUG
void Print(size_t nLevel, size_t nIndex);
#endif
protected:
CASC_MIME_ELEMENT * AllocateAndLoadElement(char * a_mime_data, char * a_mime_data_end, const char * boundary_begin);
bool ExtractEncoding(const char * line, CASC_MIME_ENCODING & Encoding);
bool ExtractBoundary(const char * line);
DWORD DecodeTextPlain(char * content_begin, char * content_end, unsigned char * data_ptr, size_t * ptr_length);
DWORD DecodeQuotedPrintable(char * content_begin, char * content_end, unsigned char * data_ptr, size_t * ptr_length);
DWORD DecodeBase64(char * content_begin, char * content_end, unsigned char * data_ptr, size_t * ptr_length);
struct
{
CASC_MIME_ELEMENT * pChild; // Pointer to the first child
CASC_MIME_ELEMENT * pNext; // Pointer to the next-in-folder element
} folder;
struct
{
unsigned char * begin;
size_t length;
} data;
char boundary[MAX_LENGTH_BOUNDARY];
};
class CASC_MIME
{
public:
CASC_MIME();
~CASC_MIME();
unsigned char * GiveAway(size_t * ptr_data_length);
DWORD Load(char * data, size_t length);
DWORD Load(LPCTSTR fileName);
#ifdef _DEBUG
void Print();
#endif
protected:
CASC_MIME_ELEMENT root;
};
//-----------------------------------------------------------------------------
// HTTP helpers
bool IsHttpResponseComplete(CASC_MIME_HTTP & HttpInfo, const char * response, size_t response_length);
#endif // __MIME_H__

View File

@@ -17,11 +17,11 @@
template <typename xchar>
struct CASC_PATH
{
CASC_PATH(xchar chSeparator = PATH_SEP_CHAR)
CASC_PATH(int chSeparator = PATH_SEP_CHAR)
{
m_szBufferBegin = m_szBufferPtr = m_Buffer;
m_szBufferEnd = m_szBufferBegin + _countof(m_Buffer);
m_chSeparator = chSeparator;
m_chSeparator = (xchar)chSeparator;
m_Buffer[0] = 0;
}

View File

@@ -0,0 +1,441 @@
/*****************************************************************************/
/* Sockets.cpp Copyright (c) Ladislav Zezula 2021 */
/*---------------------------------------------------------------------------*/
/* Don't call this module "Socket.cpp", otherwise VS 2019 will not link it */
/* Socket functions for CascLib. */
/*---------------------------------------------------------------------------*/
/* Date Ver Who Comment */
/* -------- ---- --- ------- */
/* 13.02.21 1.00 Lad Created */
/*****************************************************************************/
#define __CASCLIB_SELF__
#include "../CascLib.h"
#include "../CascCommon.h"
//-----------------------------------------------------------------------------
// Local variables
CASC_SOCKET_CACHE SocketCache;
//-----------------------------------------------------------------------------
// CASC_SOCKET functions
// Guarantees that there is zero terminator after the response
char * CASC_SOCKET::ReadResponse(const char * request, size_t request_length, size_t * PtrLength)
{
CASC_MIME_HTTP HttpInfo;
char * server_response = NULL;
size_t total_received = 0;
size_t block_increment = 0x8000;
size_t buffer_size = block_increment;
int bytes_received = 0;
// Pre-set the result length
if(PtrLength != NULL)
PtrLength[0] = 0;
if(request_length == 0)
request_length = strlen(request);
// Lock the socket
CascLock(Lock);
// Send the request to the remote host. On Linux, this call may send signal(SIGPIPE),
// we need to prevend that by using the MSG_NOSIGNAL flag. On Windows, it fails normally.
while(send(sock, request, (int)request_length, MSG_NOSIGNAL) == SOCKET_ERROR)
{
// If the connection was closed by the remote host, we try to reconnect
if(ReconnectAfterShutdown(sock, remoteItem) == INVALID_SOCKET)
{
SetCascError(ERROR_NETWORK_NOT_AVAILABLE);
CascUnlock(Lock);
return NULL;
}
}
// Allocate buffer for server response. Allocate one extra byte for zero terminator
if((server_response = CASC_ALLOC<char>(buffer_size + 1)) != NULL)
{
for(;;)
{
// Reallocate the buffer size, if needed
if(total_received == buffer_size)
{
if((server_response = CASC_REALLOC(char, server_response, buffer_size + block_increment + 1)) == NULL)
{
SetCascError(ERROR_NOT_ENOUGH_MEMORY);
CascUnlock(Lock);
return NULL;
}
buffer_size += block_increment;
}
// Receive the next part of the response, up to buffer size
// Return value 0 means "connection closed", -1 means an error
bytes_received = recv(sock, server_response + total_received, (int)(buffer_size - total_received), 0);
if(bytes_received <= 0)
break;
// Append the number of bytes received. Also terminate response with zero
total_received += bytes_received;
server_response[total_received] = 0;
// On a HTTP protocol, we need to check whether we received all data
if(HttpInfo.IsDataComplete(server_response, total_received))
break;
}
}
// Unlock the socket
CascUnlock(Lock);
// Give the result to the caller
if(PtrLength != NULL)
PtrLength[0] = total_received;
return server_response;
}
DWORD CASC_SOCKET::AddRef()
{
return CascInterlockedIncrement(&dwRefCount);
}
void CASC_SOCKET::Release()
{
// Note: If this is a cached socket, there will be extra reference from the cache
if(CascInterlockedDecrement(&dwRefCount) == 0)
{
Delete();
}
}
int CASC_SOCKET::GetSockError()
{
#ifdef PLATFORM_WINDOWS
return WSAGetLastError();
#else
return errno;
#endif
}
DWORD CASC_SOCKET::GetAddrInfoWrapper(const char * hostName, unsigned portNum, PADDRINFO hints, PADDRINFO * ppResult)
{
char portNumString[16];
// Prepare the port number
CascStrPrintf(portNumString, _countof(portNumString), "%d", portNum);
// Attempt to connect
for(;;)
{
// Attempt to call the addrinfo
DWORD dwErrCode = getaddrinfo(hostName, portNumString, hints, ppResult);
// Error-specific handling
switch(dwErrCode)
{
#ifdef PLATFORM_WINDOWS
case WSANOTINITIALISED: // Windows-specific: WSAStartup not called
{
WSADATA wsd;
WSAStartup(MAKEWORD(2, 2), &wsd);
continue;
}
#endif
case EAI_AGAIN: // Temporary error, try again
continue;
default: // Any other result, incl. ERROR_SUCCESS
return dwErrCode;
}
}
}
SOCKET CASC_SOCKET::CreateAndConnect(addrinfo * remoteItem)
{
SOCKET sock;
// Create new socket
// On error, returns returns INVALID_SOCKET (-1) on Windows, -1 on Linux
if((sock = socket(remoteItem->ai_family, remoteItem->ai_socktype, remoteItem->ai_protocol)) > 0)
{
// Connect to the remote host
// On error, returns SOCKET_ERROR (-1) on Windows, -1 on Linux
if(connect(sock, remoteItem->ai_addr, (int)remoteItem->ai_addrlen) == 0)
return sock;
// Failed. Close the socket and return 0
closesocket(sock);
sock = INVALID_SOCKET;
}
return sock;
}
SOCKET CASC_SOCKET::ReconnectAfterShutdown(SOCKET & sock, addrinfo * remoteItem)
{
// Retrieve the error code related to previous socket operation
switch(GetSockError())
{
case EPIPE: // Non-Windows
case WSAECONNRESET: // Windows
{
// Close the old socket
if(sock != INVALID_SOCKET)
closesocket(sock);
// Attempt to reconnect
sock = CreateAndConnect(remoteItem);
return sock;
}
}
// Another problem
return INVALID_SOCKET;
}
PCASC_SOCKET CASC_SOCKET::New(addrinfo * remoteList, addrinfo * remoteItem, const char * hostName, unsigned portNum, SOCKET sock)
{
PCASC_SOCKET pSocket;
size_t length = strlen(hostName);
// Allocate enough bytes
pSocket = (PCASC_SOCKET)CASC_ALLOC<BYTE>(sizeof(CASC_SOCKET) + length);
if(pSocket != NULL)
{
// Fill the entire object with zero
memset(pSocket, 0, sizeof(CASC_SOCKET) + length);
pSocket->remoteList = remoteList;
pSocket->remoteItem = remoteItem;
pSocket->dwRefCount = 1;
pSocket->portNum = portNum;
pSocket->sock = sock;
// Init the remote host name
CascStrCopy((char *)pSocket->hostName, length + 1, hostName);
// Init the socket lock
CascInitLock(pSocket->Lock);
}
return pSocket;
}
PCASC_SOCKET CASC_SOCKET::Connect(const char * hostName, unsigned portNum)
{
PCASC_SOCKET pSocket;
addrinfo * remoteList;
addrinfo * remoteItem;
addrinfo hints = {0};
SOCKET sock;
int nErrCode;
// Retrieve the information about the remote host
// This will fail immediately if there is no connection to the internet
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
nErrCode = GetAddrInfoWrapper(hostName, portNum, &hints, &remoteList);
// Handle error code
if(nErrCode == 0)
{
// Try to connect to any address provided by the getaddrinfo()
for(remoteItem = remoteList; remoteItem != NULL; remoteItem = remoteItem->ai_next)
{
// Create new socket and connect to the remote host
if((sock = CreateAndConnect(remoteItem)) != 0)
{
// Create new instance of the CASC_SOCKET structure
if((pSocket = CASC_SOCKET::New(remoteList, remoteItem, hostName, portNum, sock)) != NULL)
{
return pSocket;
}
// Close the socket
closesocket(sock);
}
}
// Couldn't find a network
nErrCode = ERROR_NETWORK_NOT_AVAILABLE;
}
SetCascError(nErrCode);
return NULL;
}
void CASC_SOCKET::Delete()
{
PCASC_SOCKET pThis = this;
// Remove the socket from the cache
if(pCache != NULL)
pCache->UnlinkSocket(this);
pCache = NULL;
// Close the socket, if any
if(sock != 0)
closesocket(sock);
sock = 0;
// Free the lock
CascFreeLock(Lock);
// Free the socket itself
CASC_FREE(pThis);
}
//-----------------------------------------------------------------------------
// The CASC_SOCKET_CACHE class
CASC_SOCKET_CACHE::CASC_SOCKET_CACHE()
{
pFirst = pLast = NULL;
dwRefCount = 0;
}
CASC_SOCKET_CACHE::~CASC_SOCKET_CACHE()
{
PurgeAll();
}
PCASC_SOCKET CASC_SOCKET_CACHE::Find(const char * hostName, unsigned portNum)
{
PCASC_SOCKET pSocket;
for(pSocket = pFirst; pSocket != NULL; pSocket = pSocket->pNext)
{
if(!_stricmp(pSocket->hostName, hostName) && (pSocket->portNum == portNum))
break;
}
return pSocket;
}
PCASC_SOCKET CASC_SOCKET_CACHE::InsertSocket(PCASC_SOCKET pSocket)
{
if(pSocket != NULL && pSocket->pCache == NULL)
{
// Do we have caching turned on?
if(dwRefCount > 0)
{
// Insert one reference to the socket to mark it as cached
pSocket->AddRef();
// Insert the socket to the chain
if(pFirst == NULL && pLast == NULL)
{
pFirst = pLast = pSocket;
}
else
{
pSocket->pPrev = pLast;
pLast->pNext = pSocket;
pLast = pSocket;
}
// Mark the socket as cached
pSocket->pCache = this;
}
}
return pSocket;
}
void CASC_SOCKET_CACHE::UnlinkSocket(PCASC_SOCKET pSocket)
{
// Only if it's a valid socket
if(pSocket != NULL)
{
// Check the first and the last items
if(pSocket == pFirst)
pFirst = pSocket->pNext;
if(pSocket == pLast)
pLast = pSocket->pPrev;
// Disconnect the socket from the chain
if(pSocket->pPrev != NULL)
pSocket->pPrev->pNext = pSocket->pNext;
if(pSocket->pNext != NULL)
pSocket->pNext->pPrev = pSocket->pPrev;
}
}
void CASC_SOCKET_CACHE::SetCaching(bool bAddRef)
{
PCASC_SOCKET pSocket;
PCASC_SOCKET pNext;
// We need to increment reference count for each enabled caching
if(bAddRef)
{
// Add one reference to all currently held sockets
if(dwRefCount == 0)
{
for(pSocket = pFirst; pSocket != NULL; pSocket = pSocket->pNext)
pSocket->AddRef();
}
// Increment of references for the future sockets
CascInterlockedIncrement(&dwRefCount);
}
else
{
// Sanity check for multiple calls to dereference
assert(dwRefCount > 0);
// Dereference the reference count. If drops to zero, dereference all sockets as well
if(CascInterlockedDecrement(&dwRefCount) == 0)
{
for(pSocket = pFirst; pSocket != NULL; pSocket = pNext)
{
pNext = pSocket->pNext;
pSocket->Release();
}
}
}
}
void CASC_SOCKET_CACHE::PurgeAll()
{
PCASC_SOCKET pSocket;
PCASC_SOCKET pNext;
// Dereference all current sockets
for(pSocket = pFirst; pSocket != NULL; pSocket = pNext)
{
pNext = pSocket->pNext;
pSocket->Delete();
}
}
//-----------------------------------------------------------------------------
// Public functions
PCASC_SOCKET sockets_connect(const char * hostName, unsigned portNum)
{
PCASC_SOCKET pSocket;
// Try to find the item in the cache
if((pSocket = SocketCache.Find(hostName, portNum)) != NULL)
{
pSocket->AddRef();
}
else
{
// Create new socket and connect it to the remote host
pSocket = CASC_SOCKET::Connect(hostName, portNum);
// Insert it to the cache, if it's a HTTP connection
if(pSocket->portNum == CASC_PORT_HTTP)
pSocket = SocketCache.InsertSocket(pSocket);
}
return pSocket;
}
void sockets_set_caching(bool caching)
{
SocketCache.SetCaching(caching);
}

View File

@@ -0,0 +1,115 @@
/*****************************************************************************/
/* Sockets.h Copyright (c) Ladislav Zezula 2021 */
/*---------------------------------------------------------------------------*/
/* MIME parsing functions for CascLib */
/*---------------------------------------------------------------------------*/
/* Date Ver Who Comment */
/* -------- ---- --- ------- */
/* 13.02.21 1.00 Lad Created */
/*****************************************************************************/
#ifndef __SOCKET_H__
#define __SOCKET_H__
//-----------------------------------------------------------------------------
// Defines
#ifndef INVALID_SOCKET
#define INVALID_SOCKET (SOCKET)(-1)
#endif
#ifndef SOCKET_ERROR
#define SOCKET_ERROR (-1)
#endif
#ifndef MSG_NOSIGNAL
#define MSG_NOSIGNAL 0
#endif
#ifndef WSAECONNRESET
#define WSAECONNRESET 10054L
#endif
#ifndef EPIPE
#define EPIPE 32
#endif
#define CASC_PORT_HTTP 80
#define CASC_PORT_RIBBIT 1119
//-----------------------------------------------------------------------------
// The CASC_SOCKET class
typedef class CASC_SOCKET_CACHE * PCASC_SOCKET_CACHE;
typedef class CASC_SOCKET * PCASC_SOCKET;
typedef struct addrinfo * PADDRINFO;
class CASC_SOCKET
{
public:
char * ReadResponse(const char * request, size_t request_length = 0, size_t * PtrLength = NULL);
DWORD AddRef();
void Release();
private:
// Constructor and destructor
static int GetSockError();
static DWORD GetAddrInfoWrapper(const char * hostName, unsigned portNum, PADDRINFO hints, PADDRINFO * ppResult);
static SOCKET CreateAndConnect(addrinfo * remoteItem);
static SOCKET ReconnectAfterShutdown(SOCKET & sock, addrinfo * remoteItem);
static PCASC_SOCKET New(addrinfo * remoteList, addrinfo * remoteItem, const char * hostName, unsigned portNum, SOCKET sock);
static PCASC_SOCKET Connect(const char * hostName, unsigned portNum);
// Frees all resources and deletes the socket
void Delete();
// Entities allowed to manipulate with the class
friend CASC_SOCKET * sockets_connect(const char * hostName, unsigned portNum);
friend char * sockets_read_response(PCASC_SOCKET pSocket, const char * request, size_t request_length, size_t * PtrLength);
friend class CASC_SOCKET_CACHE;
PCASC_SOCKET_CACHE pCache; // Pointer to the cache. If NULL, the socket is not cached
PCASC_SOCKET pPrev; // Pointer to the prev socket in the list
PCASC_SOCKET pNext; // Pointer to the next socket in the list
PADDRINFO remoteList; // List of the remote host informations
PADDRINFO remoteItem; // The particular host picked during the last connection attempt
CASC_LOCK Lock; // Lock for single threaded access
SOCKET sock; // Opened and connected socket
DWORD dwRefCount; // Number of references
DWORD portNum; // Port number
char hostName[1]; // Buffer for storing remote host (variable length)
};
//-----------------------------------------------------------------------------
// Socket cache class
class CASC_SOCKET_CACHE
{
public:
CASC_SOCKET_CACHE();
~CASC_SOCKET_CACHE();
PCASC_SOCKET Find(const char * hostName, unsigned portNum);
PCASC_SOCKET InsertSocket(PCASC_SOCKET pSocket);
void UnlinkSocket(PCASC_SOCKET pSocket);
void SetCaching(bool bAddRef);
void PurgeAll();
private:
PCASC_SOCKET pFirst;
PCASC_SOCKET pLast;
DWORD dwRefCount;
};
//-----------------------------------------------------------------------------
// Public functions
PCASC_SOCKET sockets_connect(const char * hostName, unsigned portNum);
void sockets_set_caching(bool caching);
#endif // __SOCKET_H__

View File

@@ -58,7 +58,7 @@ recastnavigation (Recast is state of the art navigation mesh construction toolse
CascLib (An open-source implementation of library for reading CASC storage from Blizzard games since 2014)
https://github.com/ladislav-zezula/CascLib
Version: 737a8705b5b8f7ce3917f5d5ff9767b18de1285e
Version: 37a948bdb5f493b6a0959489baa07e1636002c3b
rapidjson (A fast JSON parser/generator for C++ with both SAX/DOM style API http://rapidjson.org/)
https://github.com/miloyip/rapidjson