void print_conflicting_item(const CItemData &item, const CItemData &otherItem, const CItemData::FieldBits &fields, item_diff_func_t diff_fn) { for( auto ft: diff_fields ) { switch(ft) { case CItem::GROUP: case CItem::TITLE: case CItem::USER: break; default: if (fields.test(ft)) { switch (ft) { case CItemData::POLICY: { // Policy comparison compares default policies for the safes if the // item's policy is empty. We just consider them to be same if empty. if ( have_empty_policies(item, otherItem) ) { continue; } break; } default: break; } diff_fn(item, otherItem, fields, ft); } break; } } }
static void CompareField(CItemData::FieldType field, const CItemData::FieldBits &bsTest, const CItemData &first, const CItemData &second, CItemData::FieldBits &bsConflicts, bool bTreatWhiteSpaceasEmpty = false) { if (bsTest.test(field)) { bool flip; if (bTreatWhiteSpaceasEmpty) { StringX a(first.GetFieldValue(field)), b(second.GetFieldValue(field)); EmptyIfOnlyWhiteSpace(a); EmptyIfOnlyWhiteSpace(b); flip = a != b; } else { flip = first.GetFieldValue(field) != second.GetFieldValue(field); } if (flip) bsConflicts.flip(field); } }
void CAdvancedDlg::Set(CItemData::FieldBits bsFields) { LVFINDINFO findinfo; CString cs_text; int iItem; DWORD_PTR dw_data; SecureZeroMemory(&findinfo, sizeof(LVFINDINFO)); findinfo.flags = LVFI_PARAM; // Note: Mandatory fields have a ItemData value + 0x800 rather than 0x1000 // and so will not be found and so not moved anywhere. for (int i = 0; i < CItem::LAST_DATA; i++) { // Don't move or allow non-allowed fields if (!m_bsAllowedFields.test(i)) continue; if (bsFields.test(i)) { // Selected - find entry in list of available fields and move it findinfo.lParam = i | NORMALFIELD; iItem = m_pLC_List->FindItem(&findinfo); if (iItem == -1) continue; cs_text = m_pLC_List->GetItemText(iItem, 0); dw_data = m_pLC_List->GetItemData(iItem); m_pLC_List->DeleteItem(iItem); iItem = m_pLC_Selected->InsertItem(0, cs_text); m_pLC_Selected->SetItemData(iItem, dw_data); } else { // Not selected - find entry in list of selected fields and move it findinfo.lParam = i | NORMALFIELD; iItem = m_pLC_Selected->FindItem(&findinfo); if (iItem == -1) continue; cs_text = m_pLC_Selected->GetItemText(iItem, 0); dw_data = m_pLC_Selected->GetItemData(iItem); m_pLC_Selected->DeleteItem(iItem); iItem = m_pLC_List->InsertItem(0, cs_text); m_pLC_List->SetItemData(iItem, dw_data); } } }
///////////////////////////////////////////////////////////////// // Context diff //////// inline wchar_t context_tag(CItem::FieldType ft, const CItemData::FieldBits &fields, const CItemData &item, const CItemData &otherItem) { // The two items were compared & found to be differing on this field // only show this tag for fields there were compared if (fields.test(ft)) return '!'; const StringX val{item.GetFieldValue(ft)}; // This field was not compared, it could be different. Print it only if // it is the same in both items if (val == otherItem.GetFieldValue(ft)) return L' '; if (val.empty()) return L'+'; // Don't print it return L'-'; }
void PasswordSafeSearch::FindMatches(const StringX& searchText, bool fCaseSensitive, SearchPointer& searchPtr, const CItemData::FieldBits& bsFields, bool fUseSubgroups, const wxString& subgroupText, CItemData::FieldType subgroupObject, PWSMatch::MatchRule subgroupFunction, bool subgroupFunctionCaseSensitive, Iter begin, Iter end, Accessor afn) { if (searchText.empty()) return; searchPtr.Clear(); typedef StringX (CItemData::*ItemDataFuncT)() const; struct { CItemData::FieldType type; ItemDataFuncT func; } ItemDataFields[] = { {CItemData::GROUP, &CItemData::GetGroup}, {CItemData::TITLE, &CItemData::GetTitle}, {CItemData::USER, &CItemData::GetUser}, {CItemData::PASSWORD, &CItemData::GetPassword}, // {CItemData::NOTES, &CItemData::GetNotes}, {CItemData::URL, &CItemData::GetURL}, {CItemData::EMAIL, &CItemData::GetEmail}, {CItemData::RUNCMD, &CItemData::GetRunCommand}, {CItemData::AUTOTYPE, &CItemData::GetAutoType}, {CItemData::XTIME_INT, &CItemData::GetXTimeInt}, }; for ( Iter itr = begin; itr != end; ++itr) { const int fn = (subgroupFunctionCaseSensitive? -subgroupFunction: subgroupFunction); if (fUseSubgroups && !afn(itr).Matches(stringT(subgroupText.c_str()), subgroupObject, fn)) continue; bool found = false; for (size_t idx = 0; idx < NumberOf(ItemDataFields) && !found; ++idx) { if (bsFields.test(ItemDataFields[idx].type)) { const StringX str = (afn(itr).*ItemDataFields[idx].func)(); found = fCaseSensitive? str.find(searchText) != StringX::npos: FindNoCase(searchText, str); } } if (!found && bsFields.test(CItemData::NOTES)) { StringX str = afn(itr).GetNotes(); found = fCaseSensitive? str.find(searchText) != StringX::npos: FindNoCase(searchText, str); } if (!found && bsFields.test(CItemData::PWHIST)) { size_t pwh_max, err_num; PWHistList pwhistlist; CreatePWHistoryList(afn(itr).GetPWHistory(), pwh_max, err_num, pwhistlist, PWSUtil::TMC_XML); for (PWHistList::iterator iter = pwhistlist.begin(); iter != pwhistlist.end(); iter++) { PWHistEntry pwshe = *iter; found = fCaseSensitive? pwshe.password.find(searchText) != StringX::npos: FindNoCase(searchText, pwshe.password ); if (found) break; // break out of for loop } pwhistlist.clear(); } if (found) { uuid_array_t uuid; afn(itr).GetUUID(uuid); searchPtr.Add(pws_os::CUUID(uuid)); } } }
void PWScore::Synchronize(PWScore *pothercore, const CItemData::FieldBits &bsFields, const bool &subgroup_bset, const stringT &subgroup_name, const int &subgroup_object, const int &subgroup_function, int &numUpdated, CReport *pRpt, bool *pbCancel) { /* Purpose: Synchronize entries from otherCore to m_core Algorithm: Foreach entry in otherCore Find in m_core if find a match update requested fields */ std::vector<StringX> vs_updated; numUpdated = 0; MultiCommands *pmulticmds = MultiCommands::Create(this); Command *pcmd1 = UpdateGUICommand::Create(this, UpdateGUICommand::WN_UNDO, UpdateGUICommand::GUI_UNDO_MERGESYNC); pmulticmds->Add(pcmd1); // Make sure we don't add it multiple times std::map<StringX, StringX> mapRenamedPolicies; std::vector<StringX> vs_PoliciesAdded; const StringX sxSync_DateTime = PWSUtil::GetTimeStamp(true).c_str(); ItemListConstIter otherPos; for (otherPos = pothercore->GetEntryIter(); otherPos != pothercore->GetEntryEndIter(); otherPos++) { // See if user has cancelled if (pbCancel != NULL && *pbCancel) { delete pmulticmds; return; } CItemData otherItem = pothercore->GetEntry(otherPos); CItemData::EntryType et = otherItem.GetEntryType(); // Do not process Aliases and Shortcuts if (et == CItemData::ET_ALIAS || et == CItemData::ET_SHORTCUT) continue; if (subgroup_bset && !otherItem.Matches(subgroup_name, subgroup_object, subgroup_function)) continue; const StringX sx_otherGroup = otherItem.GetGroup(); const StringX sx_otherTitle = otherItem.GetTitle(); const StringX sx_otherUser = otherItem.GetUser(); StringX sx_mergedentry; Format(sx_mergedentry, GROUPTITLEUSERINCHEVRONS, sx_otherGroup.c_str(), sx_otherTitle.c_str(), sx_otherUser.c_str()); ItemListConstIter foundPos = Find(sx_otherGroup, sx_otherTitle, sx_otherUser); if (foundPos != GetEntryEndIter()) { // found a match CItemData curItem = GetEntry(foundPos); // Don't update if entry is protected if (curItem.IsProtected()) continue; CItemData updItem(curItem); updItem.SetDisplayInfo(NULL); if (curItem.GetUUID() != otherItem.GetUUID()) { pws_os::Trace(_T("Synchronize: Mis-match UUIDs for [%ls:%ls:%ls]\n"), sx_otherGroup.c_str(), sx_otherTitle.c_str(), sx_otherUser.c_str()); } bool bUpdated(false); // Do not try and change GROUPTITLE = 0x00 (use GROUP & TITLE separately) or UUID = 0x01 for (size_t i = 2; i < bsFields.size(); i++) { if (bsFields.test(i)) { StringX sxValue = otherItem.GetFieldValue(static_cast<CItemData::FieldType>(i)); // Special processing for password policies (default & named) if (static_cast<CItemData::FieldType>(i) == CItemData::POLICYNAME) { Command *pPolicyCmd = ProcessPolicyName(pothercore, updItem, mapRenamedPolicies, vs_PoliciesAdded, sxValue, bUpdated, sxSync_DateTime, IDSC_SYNCPOLICY); if (pPolicyCmd != NULL) pmulticmds->Add(pPolicyCmd); } else { if (sxValue != updItem.GetFieldValue(static_cast<CItemData::FieldType>(i))) { bUpdated = true; updItem.SetFieldValue(static_cast<CItemData::FieldType>(i), sxValue); } } } } if (!bUpdated) continue; GUISetupDisplayInfo(updItem); updItem.SetStatus(CItemData::ES_MODIFIED); StringX sx_updated; Format(sx_updated, GROUPTITLEUSERINCHEVRONS, sx_otherGroup.c_str(), sx_otherTitle.c_str(), sx_otherUser.c_str()); vs_updated.push_back(sx_updated); Command *pcmd = EditEntryCommand::Create(this, curItem, updItem); pcmd->SetNoGUINotify(); pmulticmds->Add(pcmd); // Update the Wizard page UpdateWizard(sx_updated.c_str()); numUpdated++; } // Found match via [g:t:u] } // iteration over other core's entries stringT str_results; if (numUpdated > 0 && pRpt != NULL) { std::sort(vs_updated.begin(), vs_updated.end(), MergeSyncGTUCompare); stringT str_singular_plural_type, str_singular_plural_verb; LoadAString(str_singular_plural_type, numUpdated == 1 ? IDSC_ENTRY : IDSC_ENTRIES); LoadAString(str_singular_plural_verb, numUpdated == 1 ? IDSC_WAS : IDSC_WERE); Format(str_results, IDSC_SYNCHUPDATED, str_singular_plural_type.c_str(), str_singular_plural_verb.c_str()); pRpt->WriteLine(str_results.c_str()); for (size_t i = 0; i < vs_updated.size(); i++) { Format(str_results, L"\t%ls", vs_updated[i].c_str()); pRpt->WriteLine(str_results.c_str()); } } // See if user has cancelled if (pbCancel != NULL && *pbCancel) { delete pmulticmds; return; } Command *pcmd2 = UpdateGUICommand::Create(this, UpdateGUICommand::WN_REDO, UpdateGUICommand::GUI_REDO_MERGESYNC); pmulticmds->Add(pcmd2); Execute(pmulticmds); // See if user has cancelled too late - reset flag so incorrect information not given to user if (pbCancel != NULL && *pbCancel) { *pbCancel = false; return; } // tell the user we're done & provide short Synchronize report stringT str_entries; LoadAString(str_entries, numUpdated == 1 ? IDSC_ENTRY : IDSC_ENTRIES); Format(str_results, IDSC_SYNCHCOMPLETED, numUpdated, str_entries.c_str()); pRpt->WriteLine(str_results.c_str()); }
void PWScore::Compare(PWScore *pothercore, const CItemData::FieldBits &bsFields, const bool &subgroup_bset, const bool &bTreatWhiteSpaceasEmpty, const stringT &subgroup_name, const int &subgroup_object, const int &subgroup_function, CompareData &list_OnlyInCurrent, CompareData &list_OnlyInComp, CompareData &list_Conflicts, CompareData &list_Identical, bool *pbCancel) { /* Purpose: Compare entries from comparison database (compCore) with current database (m_core) Algorithm: Foreach entry in current database { Find in comparison database - subject to subgroup checking if found { Compare if match OK else There are conflicts; note them & increment numConflicts } else { save & increment numOnlyInCurrent } } Foreach entry in comparison database { Find in current database - subject to subgroup checking if not found save & increment numOnlyInComp } */ CItemData::FieldBits bsConflicts(0); st_CompareData st_data; int numOnlyInCurrent(0), numOnlyInComp(0), numConflicts(0), numIdentical(0); ItemListIter currentPos; for (currentPos = GetEntryIter(); currentPos != GetEntryEndIter(); currentPos++) { // See if user has cancelled if (pbCancel != NULL && *pbCancel) { return; } st_data.Empty(); const CItemData ¤tItem = GetEntry(currentPos); if (!subgroup_bset || currentItem.Matches(std::wstring(subgroup_name), subgroup_object, subgroup_function)) { st_data.group = currentItem.GetGroup(); st_data.title = currentItem.GetTitle(); st_data.user = currentItem.GetUser(); StringX sx_original; Format(sx_original, GROUPTITLEUSERINCHEVRONS, st_data.group.c_str(), st_data.title.c_str(), st_data.user.c_str()); // Update the Wizard page UpdateWizard(sx_original.c_str()); ItemListIter foundPos = pothercore->Find(st_data.group, st_data.title, st_data.user); if (foundPos != pothercore->GetEntryEndIter()) { // found a match, see if all other fields also match // Difference flags: /* First byte (values in square brackets taken from ItemData.h) 1... .... NAME [0x00] - n/a - depreciated .1.. .... UUID [0x01] - n/a - unique ..1. .... GROUP [0x02] - not checked - must be identical ...1 .... TITLE [0x03] - not checked - must be identical .... 1... USER [0x04] - not checked - must be identical .... .1.. NOTES [0x05] .... ..1. PASSWORD [0x06] .... ...1 CTIME [0x07] - not checked by default Second byte 1... .... PMTIME [0x08] - not checked by default .1.. .... ATIME [0x09] - not checked by default ..1. .... XTIME [0x0a] - not checked by default ...1 .... RESERVED [0x0b] - not used .... 1... RMTIME [0x0c] - not checked by default .... .1.. URL [0x0d] .... ..1. AUTOTYPE [0x0e] .... ...1 PWHIST [0x0f] Third byte 1... .... POLICY [0x10] - not checked by default .1.. .... XTIME_INT [0x11] - not checked by default ..1. .... RUNCMD [0x12] ...1 .... DCA [0x13] .... 1... EMAIL [0x14] .... .1.. PROTECTED [0x15] .... ..1. SYMBOLS [0x16] .... ...1 SHIFTDCA [0x17] Fourth byte 1... .... POLICYNAME [0x18] - not checked by default .1.. .... KBSHORTCUT [0x19] - not checked by default */ bsConflicts.reset(); StringX sxCurrentPassword, sxComparisonPassword; const CItemData &compItem = pothercore->GetEntry(foundPos); if (currentItem.IsDependent()) { CItemData *pci_base = GetBaseEntry(¤tItem); sxCurrentPassword = pci_base->GetPassword(); } else sxCurrentPassword = currentItem.GetPassword(); if (compItem.IsDependent()) { CItemData *pci_base = pothercore->GetBaseEntry(&compItem); sxComparisonPassword = pci_base->GetPassword(); } else sxComparisonPassword = compItem.GetPassword(); if (bsFields.test(CItemData::PASSWORD) && sxCurrentPassword != sxComparisonPassword) bsConflicts.flip(CItemData::PASSWORD); CompareField(CItemData::NOTES, bsFields, currentItem, compItem, bsConflicts, bTreatWhiteSpaceasEmpty); CompareField(CItemData::CTIME, bsFields, currentItem, compItem, bsConflicts); CompareField(CItemData::PMTIME, bsFields, currentItem, compItem, bsConflicts); CompareField(CItemData::ATIME, bsFields, currentItem, compItem, bsConflicts); CompareField(CItemData::XTIME, bsFields, currentItem, compItem, bsConflicts); CompareField(CItemData::RMTIME, bsFields, currentItem, compItem, bsConflicts); if (bsFields.test(CItemData::XTIME_INT)) { int32 current_xint, comp_xint; currentItem.GetXTimeInt(current_xint); compItem.GetXTimeInt(comp_xint); if (current_xint != comp_xint) bsConflicts.flip(CItemData::XTIME_INT); } CompareField(CItemData::URL, bsFields, currentItem, compItem, bsConflicts, bTreatWhiteSpaceasEmpty); CompareField(CItemData::AUTOTYPE, bsFields, currentItem, compItem, bsConflicts, bTreatWhiteSpaceasEmpty); CompareField(CItemData::PWHIST, bsFields, currentItem, compItem, bsConflicts); CompareField(CItemData::POLICYNAME, bsFields, currentItem, compItem, bsConflicts); // Don't test policy or symbols if either entry is using a named policy // as these are meaningless to compare if (currentItem.GetPolicyName().empty() && compItem.GetPolicyName().empty()) { if (bsFields.test(CItemData::POLICY)) { PWPolicy cur_pwp, cmp_pwp; if (currentItem.GetPWPolicy().empty()) cur_pwp = PWSprefs::GetInstance()->GetDefaultPolicy(); else currentItem.GetPWPolicy(cur_pwp); if (compItem.GetPWPolicy().empty()) cmp_pwp = PWSprefs::GetInstance()->GetDefaultPolicy(true); else compItem.GetPWPolicy(cmp_pwp); if (cur_pwp != cmp_pwp) bsConflicts.flip(CItemData::POLICY); } CompareField(CItemData::SYMBOLS, bsFields, currentItem, compItem, bsConflicts); } CompareField(CItemData::RUNCMD, bsFields, currentItem, compItem, bsConflicts); CompareField(CItemData::DCA, bsFields, currentItem, compItem, bsConflicts); CompareField(CItemData::SHIFTDCA, bsFields, currentItem, compItem, bsConflicts); CompareField(CItemData::EMAIL, bsFields, currentItem, compItem, bsConflicts); CompareField(CItemData::PROTECTED, bsFields, currentItem, compItem, bsConflicts); if (bsFields.test(CItemData::KBSHORTCUT) && currentItem.GetKBShortcut() != compItem.GetKBShortcut()) bsConflicts.flip(CItemData::KBSHORTCUT); st_data.uuid0 = currentPos->first; st_data.uuid1 = foundPos->first; st_data.bsDiffs = bsConflicts; st_data.indatabase = BOTH; st_data.unknflds0 = currentItem.NumberUnknownFields() > 0; st_data.unknflds1 = compItem.NumberUnknownFields() > 0; st_data.bIsProtected0 = currentItem.IsProtected(); if (bsConflicts.any()) { numConflicts++; st_data.id = numConflicts; list_Conflicts.push_back(st_data); } else { numIdentical++; st_data.id = numIdentical; list_Identical.push_back(st_data); } } else { // didn't find any match... numOnlyInCurrent++; st_data.uuid0 = currentPos->first; st_data.uuid1 = CUUID::NullUUID(); st_data.bsDiffs.reset(); st_data.indatabase = CURRENT; st_data.unknflds0 = currentItem.NumberUnknownFields() > 0; st_data.unknflds1 = false; st_data.id = numOnlyInCurrent; list_OnlyInCurrent.push_back(st_data); } } } // iteration over our entries ItemListIter compPos; for (compPos = pothercore->GetEntryIter(); compPos != pothercore->GetEntryEndIter(); compPos++) { // See if user has cancelled if (pbCancel != NULL && *pbCancel) { return; } st_data.Empty(); CItemData compItem = pothercore->GetEntry(compPos); if (!subgroup_bset || compItem.Matches(std::wstring(subgroup_name), subgroup_object, subgroup_function)) { st_data.group = compItem.GetGroup(); st_data.title = compItem.GetTitle(); st_data.user = compItem.GetUser(); StringX sx_compare; Format(sx_compare, GROUPTITLEUSERINCHEVRONS, st_data.group.c_str(), st_data.title.c_str(), st_data.user.c_str()); // Update the Wizard page UpdateWizard(sx_compare.c_str()); if (Find(st_data.group, st_data.title, st_data.user) == GetEntryEndIter()) { // Didn't find any match... numOnlyInComp++; st_data.uuid0 = CUUID::NullUUID(); st_data.uuid1 = compPos->first; st_data.bsDiffs.reset(); st_data.indatabase = COMPARE; st_data.unknflds0 = false; st_data.unknflds1 = compItem.NumberUnknownFields() > 0; st_data.id = numOnlyInComp; list_OnlyInComp.push_back(st_data); } } } // iteration over other core's element // See if user has cancelled too late - reset flag so incorrect information not given to user if (pbCancel != NULL && *pbCancel) { *pbCancel = false; } }