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.Windows.Forms; 26 27 using KeePass.App; 28 using KeePass.Forms; 29 using KeePass.Resources; 30 using KeePass.UI; 31 using KeePass.Util; 32 33 using KeePassLib; 34 using KeePassLib.Delegates; 35 using KeePassLib.Interfaces; 36 using KeePassLib.Keys; 37 using KeePassLib.Serialization; 38 using KeePassLib.Utility; 39 40 using NativeLib = KeePassLib.Native.NativeLib; 41 42 namespace KeePass.DataExchange 43 { 44 public static class ExportUtil 45 { Export(PwExportInfo pwExportInfo, IStatusLogger slLogger)46 public static bool Export(PwExportInfo pwExportInfo, IStatusLogger slLogger) 47 { 48 if(pwExportInfo == null) throw new ArgumentNullException("pwExportInfo"); 49 if(pwExportInfo.DataGroup == null) throw new ArgumentException(); 50 51 if(!AppPolicy.Try(AppPolicyId.Export)) return false; 52 53 ExchangeDataForm dlg = new ExchangeDataForm(); 54 dlg.InitEx(true, pwExportInfo.ContextDatabase, pwExportInfo.DataGroup); 55 dlg.ExportInfo = pwExportInfo; 56 57 bool bStatusActive = false; 58 59 try 60 { 61 if(dlg.ShowDialog() == DialogResult.OK) 62 { 63 FileFormatProvider ffp = dlg.ResultFormat; 64 if(ffp == null) { Debug.Assert(false); return false; } 65 66 IOConnectionInfo ioc = null; 67 if(ffp.RequiresFile) 68 { 69 string[] vFiles = dlg.ResultFiles; 70 if(vFiles == null) { Debug.Assert(false); return false; } 71 if(vFiles.Length == 0) { Debug.Assert(false); return false; } 72 Debug.Assert(vFiles.Length == 1); 73 74 string strFile = vFiles[0]; 75 if(string.IsNullOrEmpty(strFile)) { Debug.Assert(false); return false; } 76 77 ioc = IOConnectionInfo.FromPath(strFile); 78 } 79 80 if(slLogger != null) 81 { 82 slLogger.StartLogging(KPRes.ExportingStatusMsg, true); 83 bStatusActive = true; 84 } 85 86 Application.DoEvents(); // Redraw parent window 87 return Export(pwExportInfo, ffp, ioc, slLogger); 88 } 89 } 90 catch(Exception ex) { MessageService.ShowWarning(ex); } 91 finally 92 { 93 UIUtil.DestroyForm(dlg); 94 if(bStatusActive) slLogger.EndLogging(); 95 } 96 97 return false; 98 } 99 Export(PwExportInfo pwExportInfo, string strFormatName, IOConnectionInfo iocOutput)100 public static bool Export(PwExportInfo pwExportInfo, string strFormatName, 101 IOConnectionInfo iocOutput) 102 { 103 if(strFormatName == null) throw new ArgumentNullException("strFormatName"); 104 // iocOutput may be null 105 106 FileFormatProvider ffp = Program.FileFormatPool.Find(strFormatName); 107 if(ffp == null) return false; 108 109 NullStatusLogger slLogger = new NullStatusLogger(); 110 return Export(pwExportInfo, ffp, iocOutput, slLogger); 111 } 112 Export(PwExportInfo pwExportInfo, FileFormatProvider fileFormat, IOConnectionInfo iocOutput, IStatusLogger slLogger)113 public static bool Export(PwExportInfo pwExportInfo, FileFormatProvider fileFormat, 114 IOConnectionInfo iocOutput, IStatusLogger slLogger) 115 { 116 if(pwExportInfo == null) throw new ArgumentNullException("pwExportInfo"); 117 if(pwExportInfo.DataGroup == null) throw new ArgumentException(); 118 if(fileFormat == null) throw new ArgumentNullException("fileFormat"); 119 120 bool bFileReq = fileFormat.RequiresFile; 121 if(bFileReq && (iocOutput == null)) 122 throw new ArgumentNullException("iocOutput"); 123 if(bFileReq && (iocOutput.Path.Length == 0)) 124 throw new ArgumentException(); 125 126 PwDatabase pd = pwExportInfo.ContextDatabase; 127 Debug.Assert(pd != null); 128 129 if(!AppPolicy.Try(AppPolicyId.Export)) return false; 130 if(!AppPolicy.Current.ExportNoKey && (pd != null)) 131 { 132 if(!KeyUtil.ReAskKey(pd, true)) return false; 133 } 134 135 if(!fileFormat.SupportsExport) return false; 136 if(!fileFormat.TryBeginExport()) return false; 137 138 CompositeKey ckOrgMasterKey = null; 139 DateTime dtOrgMasterKey = PwDefs.DtDefaultNow; 140 141 PwGroup pgOrgData = pwExportInfo.DataGroup; 142 PwGroup pgOrgRoot = ((pd != null) ? pd.RootGroup : null); 143 bool bParentGroups = (pwExportInfo.ExportParentGroups && (pd != null) && 144 (pgOrgData != pgOrgRoot)); 145 146 bool bExistedAlready = true; // No deletion by default 147 bool bResult = false; 148 149 try 150 { 151 if(pwExportInfo.ExportMasterKeySpec && fileFormat.RequiresKey && 152 (pd != null)) 153 { 154 KeyCreationForm kcf = new KeyCreationForm(); 155 kcf.InitEx((iocOutput ?? new IOConnectionInfo()), true); 156 157 if(UIUtil.ShowDialogNotValue(kcf, DialogResult.OK)) return false; 158 159 ckOrgMasterKey = pd.MasterKey; 160 dtOrgMasterKey = pd.MasterKeyChanged; 161 162 pd.MasterKey = kcf.CompositeKey; 163 pd.MasterKeyChanged = DateTime.UtcNow; 164 165 UIUtil.DestroyForm(kcf); 166 } 167 168 if(bParentGroups) 169 { 170 PwGroup pgNew = WithParentGroups(pgOrgData, pd); 171 pwExportInfo.DataGroup = pgNew; 172 pd.RootGroup = pgNew; 173 } 174 175 if(bFileReq) bExistedAlready = IOConnection.FileExists(iocOutput); 176 177 Stream s = (bFileReq ? IOConnection.OpenWrite(iocOutput) : null); 178 try { bResult = fileFormat.Export(pwExportInfo, s, slLogger); } 179 finally { if(s != null) s.Close(); } 180 181 if(bFileReq && bResult) 182 { 183 if(pwExportInfo.ExportPostOpen) 184 NativeLib.StartProcess(iocOutput.Path); 185 if(pwExportInfo.ExportPostShow) 186 WinUtil.ShowFileInFileManager(iocOutput.Path, true); 187 } 188 } 189 catch(Exception ex) { MessageService.ShowWarning(ex); } 190 finally 191 { 192 if(ckOrgMasterKey != null) 193 { 194 pd.MasterKey = ckOrgMasterKey; 195 pd.MasterKeyChanged = dtOrgMasterKey; 196 } 197 198 if(bParentGroups) 199 { 200 pwExportInfo.DataGroup = pgOrgData; 201 pd.RootGroup = pgOrgRoot; 202 } 203 } 204 205 if(bFileReq && !bResult && !bExistedAlready) 206 { 207 try { IOConnection.DeleteFile(iocOutput); } 208 catch(Exception) { } 209 } 210 211 return bResult; 212 } 213 WithParentGroups(PwGroup pg, PwDatabase pd)214 private static PwGroup WithParentGroups(PwGroup pg, PwDatabase pd) 215 { 216 if(pg == null) { Debug.Assert(false); return null; } 217 if(pd == null) { Debug.Assert(false); return pg; } 218 219 Dictionary<PwUuid, bool> dUuids = CollectUuids(pg); 220 221 PwGroup pgNew = FilterCloneGroup(pd.RootGroup, dUuids); 222 Debug.Assert(pgNew.GetEntriesCount(true) == pg.GetEntriesCount(true)); 223 return pgNew; 224 } 225 CollectUuids(PwGroup pg)226 private static Dictionary<PwUuid, bool> CollectUuids(PwGroup pg) 227 { 228 Dictionary<PwUuid, bool> d = new Dictionary<PwUuid, bool>(); 229 230 Action<IStructureItem> fAdd = delegate(IStructureItem it) 231 { 232 if(it == null) { Debug.Assert(false); return; } 233 234 Debug.Assert(!d.ContainsKey(it.Uuid)); 235 d[it.Uuid] = true; 236 237 PwGroup pgParent = it.ParentGroup; 238 while(pgParent != null) 239 { 240 d[pgParent.Uuid] = true; 241 pgParent = pgParent.ParentGroup; 242 } 243 }; 244 245 GroupHandler gh = delegate(PwGroup pgCur) { fAdd(pgCur); return true; }; 246 EntryHandler eh = delegate(PwEntry peCur) { fAdd(peCur); return true; }; 247 248 fAdd(pg); 249 pg.TraverseTree(TraversalMethod.PreOrder, gh, eh); 250 251 return d; 252 } 253 FilterCloneGroup(PwGroup pg, Dictionary<PwUuid, bool> dUuids)254 private static PwGroup FilterCloneGroup(PwGroup pg, Dictionary<PwUuid, bool> dUuids) 255 { 256 PwGroup pgNew = new PwGroup(); 257 pgNew.Uuid = pg.Uuid; 258 pgNew.AssignProperties(pg, false, true); 259 Debug.Assert(pgNew.EqualsGroup(pg, (PwCompareOptions.IgnoreParentGroup | 260 PwCompareOptions.PropertiesOnly), MemProtCmpMode.Full)); 261 262 foreach(PwEntry pe in pg.Entries) 263 { 264 if(dUuids.ContainsKey(pe.Uuid)) 265 pgNew.AddEntry(pe.CloneDeep(), true, false); 266 } 267 268 foreach(PwGroup pgSub in pg.Groups) 269 { 270 if(dUuids.ContainsKey(pgSub.Uuid)) 271 pgNew.AddGroup(FilterCloneGroup(pgSub, dUuids), true, false); 272 } 273 274 return pgNew; 275 } 276 } 277 } 278