/* Miranda NG: the free IM client for Microsoft* Windows* Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org) all portions of this codebase are copyrighted to the people listed in contributors.txt. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "stdafx.h" #pragma warning(disable: 4701) #define VLT(n) ((n == DBVT_UTF8 || n == DBVT_ENCRYPTED) ? DBVT_ASCIIZ : n) static bool ValidLookupName(LPCSTR szModule, LPCSTR szSetting) { if (!mir_strcmp(szModule, META_PROTO)) return mir_strcmp(szSetting, "IsSubcontact") && mir_strcmp(szSetting, "ParentMetaID"); if (!mir_strcmp(szModule, "Ignore")) return false; return true; } int CDb3Mmap::GetContactSettingWorker(MCONTACT contactID, LPCSTR szModule, LPCSTR szSetting, DBVARIANT *dbv, int isStatic) { if (szSetting == nullptr || szModule == nullptr) return 1; // the db format can't tolerate more than 255 bytes of space (incl. null) for settings+module name int settingNameLen = (int)mir_strlen(szSetting); int moduleNameLen = (int)mir_strlen(szModule); if (settingNameLen > 0xFE || moduleNameLen > 0xFE) return 1; mir_cslock lck(m_csDbAccess); LBL_Seek: char *szCachedSettingName = m_cache->GetCachedSetting(szModule, szSetting, moduleNameLen, settingNameLen); log3("get [%08p] %s (%p)", hContact, szCachedSettingName, szCachedSettingName); DBVARIANT *pCachedValue = m_cache->GetCachedValuePtr(contactID, szCachedSettingName, 0); if (pCachedValue != nullptr) { if (pCachedValue->type == DBVT_ASCIIZ || pCachedValue->type == DBVT_UTF8) { int cbOrigLen = dbv->cchVal; char *cbOrigPtr = dbv->pszVal; memcpy(dbv, pCachedValue, sizeof(DBVARIANT)); if (isStatic) { int cbLen = 0; if (pCachedValue->pszVal != nullptr) cbLen = (int)mir_strlen(pCachedValue->pszVal); cbOrigLen--; dbv->pszVal = cbOrigPtr; if (cbLen < cbOrigLen) cbOrigLen = cbLen; memcpy(dbv->pszVal, pCachedValue->pszVal, cbOrigLen); dbv->pszVal[cbOrigLen] = 0; dbv->cchVal = cbLen; } else { dbv->pszVal = (char*)mir_alloc(mir_strlen(pCachedValue->pszVal) + 1); mir_strcpy(dbv->pszVal, pCachedValue->pszVal); } } else memcpy(dbv, pCachedValue, sizeof(DBVARIANT)); log2("get cached %s (%p)", printVariant(dbv), pCachedValue); return (pCachedValue->type == DBVT_DELETED) ? 1 : 0; } // never look db for the resident variable if (szCachedSettingName[-1] != 0) return 1; DBCachedContact *cc; uint32_t ofsContact = GetContactOffset(contactID, &cc); uint32_t ofsModuleName = GetModuleNameOfs(szModule); DBContact dbc = *(DBContact*)DBRead(ofsContact, nullptr); if (dbc.signature != DBCONTACT_SIGNATURE) return 1; uint32_t ofsSettingsGroup = GetSettingsGroupOfsByModuleNameOfs(&dbc, ofsModuleName); if (ofsSettingsGroup) { int bytesRemaining; unsigned varLen; uint32_t ofsBlobPtr = ofsSettingsGroup + offsetof(DBContactSettings, blob); uint8_t *pBlob = DBRead(ofsBlobPtr, &bytesRemaining); while (pBlob[0]) { NeedBytes(1 + settingNameLen); if (pBlob[0] == settingNameLen && !memcmp(pBlob + 1, szSetting, settingNameLen)) { MoveAlong(1 + settingNameLen); NeedBytes(5); if (isStatic && (pBlob[0] & DBVTF_VARIABLELENGTH) && VLT(dbv->type) != VLT(pBlob[0])) return 1; uint8_t iType = dbv->type = pBlob[0]; switch (iType) { case DBVT_DELETED: /* this setting is deleted */ dbv->type = DBVT_DELETED; return 2; case DBVT_BYTE: dbv->bVal = pBlob[1]; break; case DBVT_WORD: memmove(&(dbv->wVal), (PWORD)(pBlob + 1), 2); break; case DBVT_DWORD: memmove(&(dbv->dVal), (PDWORD)(pBlob + 1), 4); break; case DBVT_UTF8: case DBVT_ASCIIZ: varLen = *(PWORD)(pBlob + 1); NeedBytes(int(3 + varLen)); if (isStatic) { dbv->cchVal--; if (varLen < dbv->cchVal) dbv->cchVal = varLen; memmove(dbv->pszVal, pBlob + 3, dbv->cchVal); // decode dbv->pszVal[dbv->cchVal] = 0; dbv->cchVal = varLen; } else { dbv->pszVal = (char*)mir_alloc(1 + varLen); memmove(dbv->pszVal, pBlob + 3, varLen); dbv->pszVal[varLen] = 0; } break; case DBVT_BLOB: varLen = *(PWORD)(pBlob + 1); NeedBytes(int(3 + varLen)); if (isStatic) { if (varLen < dbv->cpbVal) dbv->cpbVal = varLen; memmove(dbv->pbVal, pBlob + 3, dbv->cpbVal); } else { dbv->pbVal = (uint8_t *)mir_alloc(varLen); memmove(dbv->pbVal, pBlob + 3, varLen); } dbv->cpbVal = varLen; break; case DBVT_ENCRYPTED: if (m_crypto == nullptr) return 1; else { varLen = *(PWORD)(pBlob + 1); NeedBytes(int(3 + varLen)); size_t realLen; ptrA decoded(m_crypto->decodeString(pBlob + 3, varLen, &realLen)); if (decoded == nullptr) return 1; varLen = (uint16_t)realLen; dbv->type = DBVT_UTF8; if (isStatic) { dbv->cchVal--; if (varLen < dbv->cchVal) dbv->cchVal = varLen; memmove(dbv->pszVal, decoded, dbv->cchVal); dbv->pszVal[dbv->cchVal] = 0; dbv->cchVal = varLen; } else { dbv->pszVal = (char*)mir_alloc(1 + varLen); memmove(dbv->pszVal, decoded, varLen); dbv->pszVal[varLen] = 0; } } break; } /**** add to cache **********************/ if (iType != DBVT_BLOB && iType != DBVT_ENCRYPTED) { pCachedValue = m_cache->GetCachedValuePtr(contactID, szCachedSettingName, 1); if (pCachedValue != nullptr) { m_cache->SetCachedVariant(dbv, pCachedValue); log3("set cached [%08p] %s (%p)", hContact, szCachedSettingName, pCachedValue); } } return 0; } NeedBytes(1); MoveAlong(pBlob[0] + 1); NeedBytes(3); MoveAlong(1 + GetSettingValueLength(pBlob)); NeedBytes(1); } } // try to get the missing mc setting from the active sub if (cc && cc->IsMeta() && ValidLookupName(szModule, szSetting)) { if (contactID = db_mc_getDefault(contactID)) { if (szModule = Proto_GetBaseAccountName(contactID)) { moduleNameLen = (int)mir_strlen(szModule); goto LBL_Seek; } } } logg(); return 1; } STDMETHODIMP_(BOOL) CDb3Mmap::WriteContactSettingWorker(MCONTACT contactID, DBCONTACTWRITESETTING &dbcws) { log1(" write database as %s", printVariant(&dbcws.value)); uint32_t settingNameLen = (uint32_t)mir_strlen(dbcws.szSetting); uint32_t ofsBlobPtr, ofsContact = GetContactOffset(contactID); if (ofsContact == 0) { _ASSERT(false); // contact doesn't exist? return 2; } uint32_t ofsModuleName = GetModuleNameOfs(dbcws.szModule); DBContact dbc = *(DBContact*)DBRead(ofsContact, nullptr); if (dbc.signature != DBCONTACT_SIGNATURE) return 1; // make sure the module group exists uint8_t *pBlob; int bytesRequired, bytesRemaining; DBContactSettings dbcs; uint32_t ofsSettingsGroup = GetSettingsGroupOfsByModuleNameOfs(&dbc, ofsModuleName); if (ofsSettingsGroup == 0) { //module group didn't exist - make it switch (dbcws.value.type) { case DBVT_ASCIIZ: case DBVT_UTF8: bytesRequired = dbcws.value.cchVal + 2; break; case DBVT_BLOB: case DBVT_ENCRYPTED: bytesRequired = dbcws.value.cpbVal + 2; break; default: bytesRequired = dbcws.value.type; } bytesRequired += 2 + settingNameLen; bytesRequired += (DB_SETTINGS_RESIZE_GRANULARITY - (bytesRequired % DB_SETTINGS_RESIZE_GRANULARITY)) % DB_SETTINGS_RESIZE_GRANULARITY; ofsSettingsGroup = CreateNewSpace(bytesRequired + offsetof(DBContactSettings, blob)); dbcs.signature = DBCONTACTSETTINGS_SIGNATURE; dbcs.ofsNext = dbc.ofsFirstSettings; dbcs.ofsModuleName = ofsModuleName; dbcs.cbBlob = bytesRequired; dbcs.blob[0] = 0; dbc.ofsFirstSettings = ofsSettingsGroup; DBWrite(ofsContact, &dbc, sizeof(DBContact)); DBWrite(ofsSettingsGroup, &dbcs, sizeof(DBContactSettings)); ofsBlobPtr = ofsSettingsGroup + offsetof(DBContactSettings, blob); pBlob = (uint8_t*)DBRead(ofsBlobPtr, &bytesRemaining); } else { dbcs = *(DBContactSettings*)DBRead(ofsSettingsGroup, &bytesRemaining); // find if the setting exists ofsBlobPtr = ofsSettingsGroup + offsetof(DBContactSettings, blob); pBlob = (uint8_t*)DBRead(ofsBlobPtr, &bytesRemaining); while (pBlob[0]) { NeedBytes(settingNameLen + 1); if (pBlob[0] == settingNameLen && !memcmp(pBlob + 1, dbcws.szSetting, settingNameLen)) break; NeedBytes(1); MoveAlong(pBlob[0] + 1); NeedBytes(3); MoveAlong(1 + GetSettingValueLength(pBlob)); NeedBytes(1); } // setting already existed, and up to end of name is in cache if (pBlob[0]) { MoveAlong(1 + settingNameLen); // if different type or variable length and length is different NeedBytes(3); if (pBlob[0] != dbcws.value.type || ((pBlob[0] == DBVT_ASCIIZ || pBlob[0] == DBVT_UTF8) && *(PWORD)(pBlob + 1) != dbcws.value.cchVal) || ((pBlob[0] == DBVT_BLOB || pBlob[0] == DBVT_ENCRYPTED) && *(PWORD)(pBlob + 1) != dbcws.value.cpbVal)) { // bin it NeedBytes(3); int nameLen = 1 + settingNameLen; int valLen = 1 + GetSettingValueLength(pBlob); uint32_t ofsSettingToCut = ofsBlobPtr - nameLen; MoveAlong(valLen); NeedBytes(1); while (pBlob[0]) { MoveAlong(pBlob[0] + 1); NeedBytes(3); MoveAlong(1 + GetSettingValueLength(pBlob)); NeedBytes(1); } DBMoveChunk(ofsSettingToCut, ofsSettingToCut + nameLen + valLen, ofsBlobPtr + 1 - ofsSettingToCut); ofsBlobPtr -= nameLen + valLen; pBlob = (uint8_t*)DBRead(ofsBlobPtr, &bytesRemaining); } else { // replace existing setting at pBlob MoveAlong(1); // skip data type switch (dbcws.value.type) { case DBVT_BYTE: DBWrite(ofsBlobPtr, &dbcws.value.bVal, 1); break; case DBVT_WORD: DBWrite(ofsBlobPtr, &dbcws.value.wVal, 2); break; case DBVT_DWORD: DBWrite(ofsBlobPtr, &dbcws.value.dVal, 4); break; case DBVT_BLOB: DBWrite(ofsBlobPtr + 2, dbcws.value.pbVal, dbcws.value.cpbVal); break; case DBVT_ENCRYPTED: DBWrite(ofsBlobPtr + 2, dbcws.value.pbVal, dbcws.value.cpbVal); break; case DBVT_UTF8: case DBVT_ASCIIZ: DBWrite(ofsBlobPtr + 2, dbcws.value.pszVal, dbcws.value.cchVal); break; } // quit DBFlush(1); return 0; } } } // cannot do a simple replace, add setting to end of list // pBlob already points to end of list // see if it fits switch (dbcws.value.type) { case DBVT_ASCIIZ: case DBVT_UTF8: bytesRequired = dbcws.value.cchVal + 2; break; case DBVT_BLOB: case DBVT_ENCRYPTED: bytesRequired = dbcws.value.cpbVal + 2; break; default: bytesRequired = dbcws.value.type; } bytesRequired += 2 + settingNameLen; bytesRequired += ofsBlobPtr + 1 - (ofsSettingsGroup + offsetof(DBContactSettings, blob)); if ((uint32_t)bytesRequired > dbcs.cbBlob) { // doesn't fit: move entire group DBContactSettings *dbcsPrev; uint32_t ofsDbcsPrev, ofsNew; InvalidateSettingsGroupOfsCacheEntry(ofsSettingsGroup); bytesRequired += (DB_SETTINGS_RESIZE_GRANULARITY - (bytesRequired % DB_SETTINGS_RESIZE_GRANULARITY)) % DB_SETTINGS_RESIZE_GRANULARITY; // find previous group to change its offset ofsDbcsPrev = dbc.ofsFirstSettings; if (ofsDbcsPrev == ofsSettingsGroup) ofsDbcsPrev = 0; else { dbcsPrev = (DBContactSettings*)DBRead(ofsDbcsPrev, nullptr); while (dbcsPrev->ofsNext != ofsSettingsGroup) { if (dbcsPrev->ofsNext == 0) DatabaseCorruption(nullptr); ofsDbcsPrev = dbcsPrev->ofsNext; dbcsPrev = (DBContactSettings*)DBRead(ofsDbcsPrev, nullptr); } } // create the new one ofsNew = ReallocSpace(ofsSettingsGroup, dbcs.cbBlob + offsetof(DBContactSettings, blob), bytesRequired + offsetof(DBContactSettings, blob)); dbcs.cbBlob = bytesRequired; DBWrite(ofsNew, &dbcs, offsetof(DBContactSettings, blob)); if (ofsDbcsPrev == 0) { dbc.ofsFirstSettings = ofsNew; DBWrite(ofsContact, &dbc, sizeof(DBContact)); } else { dbcsPrev = (DBContactSettings*)DBRead(ofsDbcsPrev, nullptr); dbcsPrev->ofsNext = ofsNew; DBWrite(ofsDbcsPrev, dbcsPrev, offsetof(DBContactSettings, blob)); } ofsBlobPtr += ofsNew - ofsSettingsGroup; ofsSettingsGroup = ofsNew; pBlob = (uint8_t*)DBRead(ofsBlobPtr, &bytesRemaining); } // we now have a place to put it and enough space: make it DBWrite(ofsBlobPtr, &settingNameLen, 1); DBWrite(ofsBlobPtr + 1, (PVOID)dbcws.szSetting, settingNameLen); MoveAlong(1 + settingNameLen); DBWrite(ofsBlobPtr, &dbcws.value.type, 1); MoveAlong(1); switch (dbcws.value.type) { case DBVT_BYTE: DBWrite(ofsBlobPtr, &dbcws.value.bVal, 1); MoveAlong(1); break; case DBVT_WORD: DBWrite(ofsBlobPtr, &dbcws.value.wVal, 2); MoveAlong(2); break; case DBVT_DWORD: DBWrite(ofsBlobPtr, &dbcws.value.dVal, 4); MoveAlong(4); break; case DBVT_BLOB: DBWrite(ofsBlobPtr, &dbcws.value.cpbVal, 2); DBWrite(ofsBlobPtr + 2, dbcws.value.pbVal, dbcws.value.cpbVal); MoveAlong(2 + dbcws.value.cpbVal); break; case DBVT_ENCRYPTED: DBWrite(ofsBlobPtr, &dbcws.value.cpbVal, 2); DBWrite(ofsBlobPtr + 2, dbcws.value.pbVal, dbcws.value.cpbVal); MoveAlong(2 + dbcws.value.cpbVal); break; case DBVT_UTF8: case DBVT_ASCIIZ: DBWrite(ofsBlobPtr, &dbcws.value.cchVal, 2); DBWrite(ofsBlobPtr + 2, dbcws.value.pszVal, dbcws.value.cchVal); MoveAlong(2 + dbcws.value.cchVal); break; } uint8_t zero = 0; DBWrite(ofsBlobPtr, &zero, 1); // quit DBFlush(1); return 0; } STDMETHODIMP_(BOOL) CDb3Mmap::DeleteContactSetting(MCONTACT contactID, LPCSTR szModule, LPCSTR szSetting) { if (!szModule || !szSetting) return 1; // the db format can't tolerate more than 255 bytes of space (incl. null) for settings+module name int settingNameLen = (int)mir_strlen(szSetting); int moduleNameLen = (int)mir_strlen(szModule); if (settingNameLen > 0xFE) { #ifdef _DEBUG OutputDebugStringA("DeleteContactSetting() got a > 255 setting name length. \n"); #endif return 1; } if (moduleNameLen > 0xFE) { #ifdef _DEBUG OutputDebugStringA("DeleteContactSetting() got a > 255 module name length. \n"); #endif return 1; } MCONTACT saveContact = contactID; { mir_cslock lck(m_csDbAccess); char *szCachedSettingName = m_cache->GetCachedSetting(szModule, szSetting, moduleNameLen, settingNameLen); if (szCachedSettingName[-1] == 0) { // it's not a resident variable uint32_t ofsModuleName = GetModuleNameOfs(szModule); uint32_t ofsContact = GetContactOffset(contactID); DBContact *dbc = (DBContact*)DBRead(ofsContact, nullptr); if (dbc->signature != DBCONTACT_SIGNATURE) return 1; // make sure the module group exists uint32_t ofsSettingsGroup = GetSettingsGroupOfsByModuleNameOfs(dbc, ofsModuleName); if (ofsSettingsGroup == 0) return 1; // find if the setting exists uint32_t ofsBlobPtr = ofsSettingsGroup + offsetof(DBContactSettings, blob); int bytesRemaining; uint8_t *pBlob = (uint8_t*)DBRead(ofsBlobPtr, &bytesRemaining); while (pBlob[0]) { NeedBytes(settingNameLen + 1); if (pBlob[0] == settingNameLen && !memcmp(pBlob + 1, szSetting, settingNameLen)) break; NeedBytes(1); MoveAlong(pBlob[0] + 1); NeedBytes(3); MoveAlong(1 + GetSettingValueLength(pBlob)); NeedBytes(1); } if (!pBlob[0]) //setting didn't exist return 1; // bin it MoveAlong(1 + settingNameLen); NeedBytes(3); int nameLen = 1 + settingNameLen; int valLen = 1 + GetSettingValueLength(pBlob); uint32_t ofsSettingToCut = ofsBlobPtr - nameLen; MoveAlong(valLen); NeedBytes(1); while (pBlob[0]) { MoveAlong(pBlob[0] + 1); NeedBytes(3); MoveAlong(1 + GetSettingValueLength(pBlob)); NeedBytes(1); } DBMoveChunk(ofsSettingToCut, ofsSettingToCut + nameLen + valLen, ofsBlobPtr + 1 - ofsSettingToCut); DBFlush(1); // remove a value from cache anyway m_cache->GetCachedValuePtr(saveContact, szCachedSettingName, -1); } else { // resident variable // if a value doesn't exist, simply return error if (m_cache->GetCachedValuePtr(saveContact, szCachedSettingName, -1) == nullptr) return 1; } } // notify DBCONTACTWRITESETTING dbcws = {}; dbcws.szModule = szModule; dbcws.szSetting = szSetting; dbcws.value.type = DBVT_DELETED; NotifyEventHooks(g_hevSettingChanged, saveContact, (LPARAM)&dbcws); return 0; } STDMETHODIMP_(BOOL) CDb3Mmap::EnumContactSettings(MCONTACT contactID, DBSETTINGENUMPROC pfnEnumProc, const char *szModule, void *param) { if (!szModule) return -1; mir_cslock lck(m_csDbAccess); uint32_t ofsContact = GetContactOffset(contactID); if (ofsContact == 0) return -1; DBContact *dbc = (DBContact*)DBRead(ofsContact, nullptr); if (dbc->signature != DBCONTACT_SIGNATURE) return -1; uint32_t ofsModuleName = GetModuleNameOfs(szModule); uint32_t ofsSettings = GetSettingsGroupOfsByModuleNameOfs(dbc, ofsModuleName); if (!ofsSettings) return -1; uint32_t ofsBlobPtr = ofsSettings + offsetof(DBContactSettings, blob); int bytesRemaining; uint8_t *pBlob = (uint8_t*)DBRead(ofsBlobPtr, &bytesRemaining); if (pBlob[0] == 0) return -1; int result = 0; while (pBlob[0]) { NeedBytes(1); NeedBytes(1 + pBlob[0]); char szSetting[256]; memcpy(szSetting, pBlob + 1, pBlob[0]); szSetting[pBlob[0]] = 0; result = pfnEnumProc(szSetting, param); MoveAlong(1 + pBlob[0]); NeedBytes(3); MoveAlong(1 + GetSettingValueLength(pBlob)); NeedBytes(1); } return result; }