1 /* 2 KeePass Password Safe - The Open-Source Password Manager 3 Copyright (C) 2003-2021 Dominik Reichl <dominik.reichl@t-online.de> 4 5 This program is free software; you can redistribute it and/or modify 6 it under the terms of the GNU General Public License as published by 7 the Free Software Foundation; either version 2 of the License, or 8 (at your option) any later version. 9 10 This program is distributed in the hope that it will be useful, 11 but WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 GNU General Public License for more details. 14 15 You should have received a copy of the GNU General Public License 16 along with this program; if not, write to the Free Software 17 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 18 */ 19 20 using System; 21 using System.Collections.Generic; 22 using System.Diagnostics; 23 using System.IO; 24 using System.Text; 25 using System.Text.RegularExpressions; 26 using System.Windows.Forms; 27 using System.Xml; 28 using System.Xml.XPath; 29 30 using KeePass.Forms; 31 using KeePass.Resources; 32 using KeePass.UI; 33 34 using KeePassLib; 35 using KeePassLib.Collections; 36 using KeePassLib.Delegates; 37 using KeePassLib.Interfaces; 38 using KeePassLib.Security; 39 using KeePassLib.Serialization; 40 using KeePassLib.Utility; 41 42 namespace KeePass.Util 43 { 44 [Flags] 45 public enum XmlReplaceFlags 46 { 47 None = 0x0, 48 StatusUI = 0x1, 49 50 CaseSensitive = 0x10, 51 Regex = 0x20 52 } 53 54 public enum XmlReplaceOp 55 { 56 None = 0, 57 RemoveNodes, 58 ReplaceData 59 } 60 61 public enum XmlReplaceData 62 { 63 None = 0, 64 InnerText, 65 InnerXml, 66 OuterXml 67 } 68 69 public sealed class XmlReplaceOptions 70 { 71 private XmlReplaceFlags m_f = XmlReplaceFlags.None; 72 public XmlReplaceFlags Flags 73 { 74 get { return m_f; } 75 set { m_f = value; } 76 } 77 78 private string m_strSelXPath = string.Empty; 79 public string SelectNodesXPath 80 { 81 get { return m_strSelXPath; } 82 set 83 { 84 if(value == null) { Debug.Assert(false); m_strSelXPath = string.Empty; } 85 else m_strSelXPath = value; 86 } 87 } 88 89 private XmlReplaceOp m_op = XmlReplaceOp.None; 90 public XmlReplaceOp Operation 91 { 92 get { return m_op; } 93 set { m_op = value; } 94 } 95 96 private XmlReplaceData m_d = XmlReplaceData.None; 97 public XmlReplaceData Data 98 { 99 get { return m_d; } 100 set { m_d = value; } 101 } 102 103 private string m_strFind = string.Empty; 104 public string FindText 105 { 106 get { return m_strFind; } 107 set 108 { 109 if(value == null) { Debug.Assert(false); m_strFind = string.Empty; } 110 else m_strFind = value; 111 } 112 } 113 114 private string m_strReplace = string.Empty; 115 public string ReplaceText 116 { 117 get { return m_strReplace; } 118 set 119 { 120 if(value == null) { Debug.Assert(false); m_strReplace = string.Empty; } 121 else m_strReplace = value; 122 } 123 } 124 125 private Form m_fParent = null; 126 public Form ParentForm 127 { 128 get { return m_fParent; } 129 set { m_fParent = value; } 130 } 131 XmlReplaceOptions()132 public XmlReplaceOptions() { } 133 } 134 135 public static partial class XmlUtil 136 { Replace(PwDatabase pd, XmlReplaceOptions opt)137 public static void Replace(PwDatabase pd, XmlReplaceOptions opt) 138 { 139 if(pd == null) { Debug.Assert(false); return; } 140 if(opt == null) { Debug.Assert(false); return; } 141 142 StatusProgressForm dlg = null; 143 try 144 { 145 if((opt.Flags & XmlReplaceFlags.StatusUI) != XmlReplaceFlags.None) 146 dlg = StatusProgressForm.ConstructEx(KPRes.XmlReplace, 147 true, false, opt.ParentForm, KPRes.XmlReplace + "..."); 148 149 PerformXmlReplace(pd, opt, dlg); 150 } 151 finally 152 { 153 if(dlg != null) StatusProgressForm.DestroyEx(dlg); 154 } 155 } 156 PerformXmlReplace(PwDatabase pd, XmlReplaceOptions opt, IStatusLogger sl)157 private static void PerformXmlReplace(PwDatabase pd, XmlReplaceOptions opt, 158 IStatusLogger sl) 159 { 160 if(opt.SelectNodesXPath.Length == 0) return; 161 if(opt.Operation == XmlReplaceOp.None) return; 162 163 bool bRemove = (opt.Operation == XmlReplaceOp.RemoveNodes); 164 bool bReplace = (opt.Operation == XmlReplaceOp.ReplaceData); 165 bool bMatchCase = ((opt.Flags & XmlReplaceFlags.CaseSensitive) != XmlReplaceFlags.None); 166 bool bRegex = ((opt.Flags & XmlReplaceFlags.Regex) != XmlReplaceFlags.None); 167 168 Regex rxFind = null; 169 if(bReplace && bRegex) 170 rxFind = new Regex(opt.FindText, (bMatchCase ? RegexOptions.None : 171 RegexOptions.IgnoreCase)); 172 173 EnsureStandardFieldsExist(pd); 174 175 XmlDocument xd; 176 XPathNodeIterator xpIt = XmlUtilEx.FindNodes(pd, opt.SelectNodesXPath, 177 sl, out xd); 178 179 // XPathNavigators must be cloned to make them independent 180 List<XPathNavigator> lNodes = new List<XPathNavigator>(); 181 while(xpIt.MoveNext()) lNodes.Add(xpIt.Current.Clone()); 182 183 if(lNodes.Count == 0) return; 184 185 for(int i = lNodes.Count - 1; i >= 0; --i) 186 { 187 if((sl != null) && !sl.ContinueWork()) return; 188 189 XPathNavigator xpNav = lNodes[i]; 190 191 if(bRemove) xpNav.DeleteSelf(); 192 else if(bReplace) ApplyReplace(xpNav, opt, rxFind); 193 else { Debug.Assert(false); } // Unknown action 194 } 195 196 MemoryStream msMod = new MemoryStream(); 197 using(XmlWriter xw = XmlUtilEx.CreateXmlWriter(msMod)) 198 { 199 xd.Save(xw); 200 } 201 byte[] pbMod = msMod.ToArray(); 202 msMod.Close(); 203 204 PwDatabase pdMod = new PwDatabase(); 205 msMod = new MemoryStream(pbMod, false); 206 try 207 { 208 KdbxFile kdbxMod = new KdbxFile(pdMod); 209 kdbxMod.Load(msMod, KdbxFormat.PlainXml, sl); 210 } 211 catch(Exception) 212 { 213 throw new Exception(KPRes.XmlModInvalid + MessageService.NewParagraph + 214 KPRes.OpAborted + MessageService.NewParagraph + 215 KPRes.DbNoModBy.Replace(@"{PARAM}", @"'" + KPRes.XmlReplace + @"'")); 216 } 217 finally { msMod.Close(); } 218 219 PrepareModDbForMerge(pdMod, pd); 220 221 pd.Modified = true; 222 pd.UINeedsIconUpdate = true; 223 pd.MergeIn(pdMod, PwMergeMethod.Synchronize, sl); 224 } 225 ApplyReplace(XPathNavigator xpNav, XmlReplaceOptions opt, Regex rxFind)226 private static void ApplyReplace(XPathNavigator xpNav, XmlReplaceOptions opt, 227 Regex rxFind) 228 { 229 string strData; 230 if(opt.Data == XmlReplaceData.InnerText) strData = xpNav.Value; 231 else if(opt.Data == XmlReplaceData.InnerXml) strData = xpNav.InnerXml; 232 else if(opt.Data == XmlReplaceData.OuterXml) strData = xpNav.OuterXml; 233 else return; 234 if(strData == null) { Debug.Assert(false); strData = string.Empty; } 235 236 string str = null; 237 if(rxFind != null) str = rxFind.Replace(strData, opt.ReplaceText); 238 else 239 { 240 if((opt.Flags & XmlReplaceFlags.CaseSensitive) != XmlReplaceFlags.None) 241 str = strData.Replace(opt.FindText, opt.ReplaceText); 242 else 243 str = StrUtil.ReplaceCaseInsensitive(strData, opt.FindText, 244 opt.ReplaceText); 245 } 246 247 if((str != null) && (str != strData)) 248 { 249 if(opt.Data == XmlReplaceData.InnerText) 250 xpNav.SetValue(str); 251 else if(opt.Data == XmlReplaceData.InnerXml) 252 xpNav.InnerXml = str; 253 else if(opt.Data == XmlReplaceData.OuterXml) 254 xpNav.OuterXml = str; 255 else { Debug.Assert(false); } 256 } 257 } 258 PrepareModDbForMerge(PwDatabase pd, PwDatabase pdOrg)259 private static void PrepareModDbForMerge(PwDatabase pd, PwDatabase pdOrg) 260 { 261 PwGroup pgRootOrg = pdOrg.RootGroup; 262 PwGroup pgRootNew = pd.RootGroup; 263 if(pgRootNew == null) { Debug.Assert(false); return; } 264 265 PwCompareOptions pwCmp = (PwCompareOptions.IgnoreParentGroup | 266 PwCompareOptions.NullEmptyEquivStd); 267 DateTime dtNow = DateTime.UtcNow; 268 269 GroupHandler ghOrg = delegate(PwGroup pg) 270 { 271 PwGroup pgNew = pgRootNew.FindGroup(pg.Uuid, true); 272 if(pgNew == null) 273 { 274 AddDeletedObject(pd, pg.Uuid); 275 return true; 276 } 277 278 if(!pgNew.EqualsGroup(pg, (pwCmp | PwCompareOptions.PropertiesOnly), 279 MemProtCmpMode.Full)) 280 pgNew.Touch(true, false); 281 282 PwGroup pgParentA = pg.ParentGroup; 283 PwGroup pgParentB = pgNew.ParentGroup; 284 if((pgParentA != null) && (pgParentB != null)) 285 { 286 if(!pgParentA.Uuid.Equals(pgParentB.Uuid)) 287 pgNew.LocationChanged = dtNow; 288 } 289 else if((pgParentA == null) && (pgParentB == null)) { } 290 else pgNew.LocationChanged = dtNow; 291 292 return true; 293 }; 294 295 EntryHandler ehOrg = delegate(PwEntry pe) 296 { 297 PwEntry peNew = pgRootNew.FindEntry(pe.Uuid, true); 298 if(peNew == null) 299 { 300 AddDeletedObject(pd, pe.Uuid); 301 return true; 302 } 303 304 if(!peNew.EqualsEntry(pe, pwCmp, MemProtCmpMode.Full)) 305 { 306 peNew.Touch(true, false); 307 308 bool bRestoreHistory = false; 309 if(peNew.History.UCount != pe.History.UCount) 310 bRestoreHistory = true; 311 else 312 { 313 for(uint u = 0; u < pe.History.UCount; ++u) 314 { 315 if(!peNew.History.GetAt(u).EqualsEntry( 316 pe.History.GetAt(u), pwCmp, MemProtCmpMode.CustomOnly)) 317 { 318 bRestoreHistory = true; 319 break; 320 } 321 } 322 } 323 324 if(bRestoreHistory) 325 { 326 peNew.History = pe.History.CloneDeep(); 327 foreach(PwEntry peHistNew in peNew.History) 328 peHistNew.ParentGroup = peNew.ParentGroup; 329 } 330 } 331 332 PwGroup pgParentA = pe.ParentGroup; 333 PwGroup pgParentB = peNew.ParentGroup; 334 if((pgParentA != null) && (pgParentB != null)) 335 { 336 if(!pgParentA.Uuid.Equals(pgParentB.Uuid)) 337 peNew.LocationChanged = dtNow; 338 } 339 else if((pgParentA == null) && (pgParentB == null)) { } 340 else peNew.LocationChanged = dtNow; 341 342 return true; 343 }; 344 345 pgRootOrg.TraverseTree(TraversalMethod.PreOrder, ghOrg, ehOrg); 346 } 347 AddDeletedObject(PwDatabase pd, PwUuid pu)348 private static void AddDeletedObject(PwDatabase pd, PwUuid pu) 349 { 350 foreach(PwDeletedObject pdo in pd.DeletedObjects) 351 { 352 if(pdo.Uuid.Equals(pu)) { Debug.Assert(false); return; } 353 } 354 355 PwDeletedObject pdoNew = new PwDeletedObject(pu, DateTime.UtcNow); 356 pd.DeletedObjects.Add(pdoNew); 357 } 358 EnsureStandardFieldsExist(PwDatabase pd)359 private static void EnsureStandardFieldsExist(PwDatabase pd) 360 { 361 List<string> l = PwDefs.GetStandardFields(); 362 363 EntryHandler eh = delegate(PwEntry pe) 364 { 365 foreach(string strName in l) 366 { 367 ProtectedString ps = pe.Strings.Get(strName); 368 if(ps == null) 369 pe.Strings.Set(strName, new ProtectedString( 370 pd.MemoryProtection.GetProtection(strName), string.Empty)); 371 } 372 373 return true; 374 }; 375 376 pd.RootGroup.TraverseTree(TraversalMethod.PreOrder, null, eh); 377 } 378 } 379 } 380