mirror of
https://github.com/TrinityCore/TrinityCore.git
synced 2026-01-16 07:30:42 +01:00
Dep/CascLib: Update to ladislav-zezula/CascLib@37a948bdb5
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
674
dep/CascLib/src/common/Mime.cpp
Normal file
674
dep/CascLib/src/common/Mime.cpp
Normal 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
|
||||
|
||||
130
dep/CascLib/src/common/Mime.h
Normal file
130
dep/CascLib/src/common/Mime.h
Normal 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__
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
441
dep/CascLib/src/common/Sockets.cpp
Normal file
441
dep/CascLib/src/common/Sockets.cpp
Normal 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);
|
||||
}
|
||||
115
dep/CascLib/src/common/Sockets.h
Normal file
115
dep/CascLib/src/common/Sockets.h
Normal 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__
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user