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.ComponentModel; 23 using System.Diagnostics; 24 using System.Drawing; 25 using System.Globalization; 26 using System.Text; 27 using System.Windows.Forms; 28 29 using KeePass.App; 30 using KeePass.DataExchange; 31 using KeePass.Resources; 32 using KeePass.UI; 33 using KeePass.Util; 34 35 using KeePassLib; 36 using KeePassLib.Utility; 37 38 namespace KeePass.Forms 39 { 40 public partial class CsvImportForm : Form 41 { 42 private byte[] m_pbData = null; 43 private PwDatabase m_pwDatabase = null; 44 45 private bool m_bInitializing = true; 46 private uint m_uStartOffset = 0; 47 private CsvFieldType m_tLastCsvType = CsvFieldType.Count; 48 49 private readonly string StrCharTab = @"{Tab}"; 50 private readonly string StrCharNewLine = @"{" + KPRes.NewLine + @"}"; 51 52 private enum CsvFieldType 53 { 54 Ignore = 0, 55 Group, 56 Title, 57 UserName, 58 Password, 59 Url, 60 Notes, 61 CustomString, 62 CreationTime, 63 // LastAccessTime, 64 LastModTime, 65 ExpiryTime, 66 Tags, 67 68 Count, // Last enum item + 1 69 First = 0 70 } 71 72 private sealed class CsvFieldInfo 73 { 74 private readonly CsvFieldType m_t; 75 public CsvFieldType Type { get { return m_t; } } 76 77 private readonly string m_strName; 78 public string Name { get { return m_strName; } } 79 80 private readonly string m_strFormat; 81 public string Format { get { return m_strFormat; } } 82 CsvFieldInfo(CsvFieldType t, string strName, string strFormat)83 public CsvFieldInfo(CsvFieldType t, string strName, string strFormat) 84 { 85 m_t = t; 86 m_strName = strName; // May be null 87 m_strFormat = strFormat; // May be null 88 } 89 } 90 InitEx(PwDatabase pwStorage, byte[] pbInData)91 public void InitEx(PwDatabase pwStorage, byte[] pbInData) 92 { 93 m_pwDatabase = pwStorage; 94 m_pbData = pbInData; 95 } 96 CsvImportForm()97 public CsvImportForm() 98 { 99 InitializeComponent(); 100 GlobalWindowManager.InitializeForm(this); 101 } 102 OnFormLoad(object sender, EventArgs e)103 private void OnFormLoad(object sender, EventArgs e) 104 { 105 if((m_pbData == null) || (m_pwDatabase == null)) 106 throw new InvalidOperationException(); 107 108 m_bInitializing = true; 109 110 GlobalWindowManager.AddWindow(this); 111 112 // Callable from KPScript without parent form 113 Debug.Assert(this.StartPosition == FormStartPosition.CenterScreen); 114 115 this.Icon = AppIcons.Default; 116 this.Text = KPRes.GenericCsvImporter; 117 118 BannerFactory.CreateBannerEx(this, m_bannerImage, 119 Properties.Resources.B48x48_Binary, KPRes.GenericCsvImporter, 120 KPRes.CsvImportDesc); 121 122 // FontUtil.AssignDefaultBold(m_grpSyntax); 123 // FontUtil.AssignDefaultBold(m_grpSem); 124 125 UIUtil.SetExplorerTheme(m_lvFields, false); 126 UIUtil.SetExplorerTheme(m_lvImportPreview, false); 127 128 foreach(StrEncodingInfo seiEnum in StrUtil.Encodings) 129 { 130 m_cmbEnc.Items.Add(seiEnum.Name); 131 } 132 133 StrEncodingInfo seiGuess = BinaryDataClassifier.GetStringEncoding( 134 m_pbData, out m_uStartOffset); 135 136 int iSel = 0; 137 if(seiGuess != null) 138 iSel = Math.Max(m_cmbEnc.FindStringExact(seiGuess.Name), 0); 139 m_cmbEnc.SelectedIndex = iSel; 140 141 string[] vChars = new string[] { ",", ";", ".", ":", "\"", @"'", 142 StrCharTab, StrCharNewLine }; 143 foreach(string strChar in vChars) 144 { 145 m_cmbFieldSep.Items.Add(strChar); 146 m_cmbRecSep.Items.Add(strChar); 147 m_cmbTextQual.Items.Add(strChar); 148 } 149 m_cmbFieldSep.SelectedIndex = 0; 150 m_cmbRecSep.SelectedIndex = 7; 151 m_cmbTextQual.SelectedIndex = 4; 152 153 m_lvFields.Columns.Add(KPRes.Field); 154 155 AddCsvField(CsvFieldType.Title, null, null); 156 AddCsvField(CsvFieldType.UserName, null, null); 157 AddCsvField(CsvFieldType.Password, null, null); 158 AddCsvField(CsvFieldType.Url, null, null); 159 AddCsvField(CsvFieldType.Notes, null, null); 160 161 for(int i = (int)CsvFieldType.First; i < (int)CsvFieldType.Count; ++i) 162 m_cmbFieldType.Items.Add(CsvFieldToString((CsvFieldType)i)); 163 m_cmbFieldType.SelectedIndex = (int)CsvFieldType.Group; 164 165 m_cmbFieldFormat.Text = string.Empty; 166 167 UIUtil.AccSetName(m_btnFieldMoveUp, KPRes.MoveUp); 168 UIUtil.AccSetName(m_btnFieldMoveDown, KPRes.MoveDown); 169 170 m_bInitializing = false; 171 172 UpdateTextPreview(); 173 UpdateImportPreview(); 174 GuessCsvStructure(); 175 176 ProcessResize(); 177 EnableControlsEx(); 178 } 179 OnFormClosed(object sender, FormClosedEventArgs e)180 private void OnFormClosed(object sender, FormClosedEventArgs e) 181 { 182 GlobalWindowManager.RemoveWindow(this); 183 } 184 EnableControlsEx()185 private void EnableControlsEx() 186 { 187 if(m_bInitializing) return; 188 189 List<CsvFieldInfo> lFields = GetCsvFieldInfos(); 190 191 bool bSelField = (m_lvFields.SelectedIndices.Count >= 1); 192 bool bSel1Field = (m_lvFields.SelectedIndices.Count == 1); 193 m_btnFieldDel.Enabled = bSelField; 194 m_btnFieldMoveUp.Enabled = bSel1Field; 195 m_btnFieldMoveDown.Enabled = bSel1Field; 196 197 bool bFieldName, bFieldFormat; 198 CsvFieldType t = GetCsvFieldType(out bFieldName, out bFieldFormat); 199 m_lblFieldName.Enabled = bFieldName; 200 m_tbFieldName.Enabled = bFieldName; 201 m_lblFieldFormat.Enabled = bFieldFormat; 202 m_cmbFieldFormat.Enabled = bFieldFormat; 203 m_linkFieldFormat.Enabled = IsTimeField(t); 204 205 int iTab = m_tabMain.SelectedIndex, nTabs = m_tabMain.TabCount; 206 m_btnTabBack.Enabled = (iTab > 0); 207 m_btnTabNext.Enabled = (iTab < (nTabs - 1)); 208 209 bool bValidFieldSep = (GetCharFromDef(m_cmbFieldSep.Text) != char.MinValue); 210 bool bValidRecSep = (GetCharFromDef(m_cmbRecSep.Text) != char.MinValue); 211 bool bValidTextQual = (GetCharFromDef(m_cmbTextQual.Text) != char.MinValue); 212 213 if(bValidFieldSep) m_cmbFieldSep.ResetBackColor(); 214 else m_cmbFieldSep.BackColor = AppDefs.ColorEditError; 215 if(bValidRecSep) m_cmbRecSep.ResetBackColor(); 216 else m_cmbRecSep.BackColor = AppDefs.ColorEditError; 217 if(bValidTextQual) m_cmbTextQual.ResetBackColor(); 218 else m_cmbTextQual.BackColor = AppDefs.ColorEditError; 219 220 bool bOK = true; 221 bOK &= (iTab == (nTabs - 1)); 222 bOK &= (bValidFieldSep && bValidRecSep && bValidTextQual); 223 m_btnOK.Enabled = bOK; 224 225 if(t != m_tLastCsvType) 226 { 227 m_cmbFieldFormat.Items.Clear(); 228 229 string[] vItems; 230 if(IsTimeField(t)) 231 vItems = new string[] { 232 string.Empty, 233 @"yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fffffffzz", 234 @"ddd, dd MMM yyyy HH':'mm':'ss 'GMT'", @"yyyy'-'MM'-'dd'T'HH':'mm':'ss", 235 @"yyyy'-'MM'-'dd HH':'mm':'ss'Z'", 236 @"yyyy/MM/dd HH:mm:ss", 237 @"yyyy/MM/dd", @"MM/dd/yy", @"MMMM dd, yyyy", @"MM/dd/yy H:mm:ss zzz" 238 }; 239 else if(t == CsvFieldType.Group) 240 vItems = new string[] { string.Empty, ".", "/", "\\" }; 241 else vItems = new string[0]; 242 243 foreach(string strPre in vItems) 244 m_cmbFieldFormat.Items.Add(strPre); 245 246 if(t == CsvFieldType.Group) 247 UIUtil.SetText(m_lblFieldFormat, KPRes.Separator + ":"); 248 else UIUtil.SetText(m_lblFieldFormat, KPRes.Format + ":"); 249 } 250 m_tLastCsvType = t; 251 252 bool bHasGroup = false; 253 foreach(CsvFieldInfo cfi in lFields) 254 { 255 if(cfi.Type == CsvFieldType.Group) { bHasGroup = true; break; } 256 } 257 if(!bHasGroup) m_cbMergeGroups.Checked = false; 258 m_cbMergeGroups.Enabled = bHasGroup; 259 } 260 GetDecodedText()261 private string GetDecodedText() 262 { 263 StrEncodingInfo sei = StrUtil.GetEncoding(m_cmbEnc.Text); 264 try 265 { 266 string str = (sei.Encoding.GetString(m_pbData, (int)m_uStartOffset, 267 m_pbData.Length - (int)m_uStartOffset) ?? string.Empty); 268 return StrUtil.ReplaceNulls(str); 269 } 270 catch(Exception) { } 271 272 return string.Empty; 273 } 274 UpdateTextPreview()275 private void UpdateTextPreview() 276 { 277 if(m_bInitializing) return; 278 279 m_rtbEncPreview.Clear(); // Clear formatting 280 m_rtbEncPreview.Text = GetDecodedText(); 281 } 282 UpdateImportPreview()283 private void UpdateImportPreview() 284 { 285 if(m_bInitializing) return; 286 287 try { PerformImport(new PwGroup(true, true), true); } 288 catch(Exception) { Debug.Assert(false); } 289 } 290 ProcessResize()291 private void ProcessResize() 292 { 293 if(m_bInitializing) return; 294 295 int dx = m_lvFields.ClientRectangle.Width; 296 m_lvFields.Columns[0].Width = dx - 297 UIUtil.GetVScrollBarWidth(); // Add some space for usability 298 299 UIUtil.ResizeColumns(m_lvImportPreview, true); 300 } 301 CsvFieldToString(CsvFieldType t)302 private static string CsvFieldToString(CsvFieldType t) 303 { 304 string strText; 305 if(t == CsvFieldType.Ignore) strText = "(" + KPRes.Ignore + ")"; 306 else if(t == CsvFieldType.Group) strText = KPRes.Group; 307 else if(t == CsvFieldType.Title) strText = KPRes.Title; 308 else if(t == CsvFieldType.UserName) strText = KPRes.UserName; 309 else if(t == CsvFieldType.Password) strText = KPRes.Password; 310 else if(t == CsvFieldType.Url) strText = KPRes.Url; 311 else if(t == CsvFieldType.Notes) strText = KPRes.Notes; 312 else if(t == CsvFieldType.CustomString) 313 strText = KPRes.String; 314 else if(t == CsvFieldType.CreationTime) 315 strText = KPRes.CreationTime; 316 // else if(t == CsvFieldType.LastAccessTime) 317 // strText = KPRes.LastAccessTime; 318 else if(t == CsvFieldType.LastModTime) 319 strText = KPRes.LastModificationTime; 320 else if(t == CsvFieldType.ExpiryTime) 321 strText = KPRes.ExpiryTime; 322 else if(t == CsvFieldType.Tags) 323 strText = KPRes.Tags; 324 else { Debug.Assert(false); strText = KPRes.Unknown; } 325 326 return strText; 327 } 328 AddCsvField(CsvFieldType t, string strName, string strFormat)329 private void AddCsvField(CsvFieldType t, string strName, string strFormat) 330 { 331 string strText = CsvFieldToString(t); 332 333 string strSub = string.Empty; 334 if(strName != null) strSub += strName; 335 if(!string.IsNullOrEmpty(strFormat)) 336 { 337 if(strSub.Length > 0) strSub += ", "; 338 strSub += strFormat; 339 } 340 341 if(strSub.Length > 0) strText += " (" + strSub + ")"; 342 343 ListViewItem lvi = m_lvFields.Items.Add(strText); 344 lvi.Tag = new CsvFieldInfo(t, strName, strFormat); 345 } 346 GetCsvFieldType()347 private CsvFieldType GetCsvFieldType() 348 { 349 bool bName, bFormat; 350 return GetCsvFieldType(out bName, out bFormat); 351 } 352 GetCsvFieldType(out bool bName, out bool bFormat)353 private CsvFieldType GetCsvFieldType(out bool bName, out bool bFormat) 354 { 355 int i = m_cmbFieldType.SelectedIndex; 356 if((i < (int)CsvFieldType.First) || (i >= (int)CsvFieldType.Count)) 357 { 358 Debug.Assert(false); 359 bName = false; 360 bFormat = false; 361 return CsvFieldType.Ignore; 362 } 363 364 CsvFieldType t = (CsvFieldType)i; 365 bName = (t == CsvFieldType.CustomString); 366 bFormat = (IsTimeField(t) || (t == CsvFieldType.Group)); 367 return t; 368 } 369 GetCharFromDef(string strDef)370 private char GetCharFromDef(string strDef) 371 { 372 if(strDef.Equals(StrCharTab, StrUtil.CaseIgnoreCmp)) 373 return '\t'; 374 if(strDef.Equals(StrCharNewLine, StrUtil.CaseIgnoreCmp)) 375 return '\n'; 376 if(strDef.Length == 1) return strDef[0]; 377 return char.MinValue; 378 } 379 OnEncSelectedIndexChanged(object sender, EventArgs e)380 private void OnEncSelectedIndexChanged(object sender, EventArgs e) 381 { 382 UpdateTextPreview(); 383 } 384 OnFieldSepTextUpdate(object sender, EventArgs e)385 private void OnFieldSepTextUpdate(object sender, EventArgs e) 386 { 387 EnableControlsEx(); 388 } 389 OnRecSepTextUpdate(object sender, EventArgs e)390 private void OnRecSepTextUpdate(object sender, EventArgs e) 391 { 392 EnableControlsEx(); 393 } 394 OnTextQualTextUpdate(object sender, EventArgs e)395 private void OnTextQualTextUpdate(object sender, EventArgs e) 396 { 397 EnableControlsEx(); 398 } 399 OnBtnFieldDel(object sender, EventArgs e)400 private void OnBtnFieldDel(object sender, EventArgs e) 401 { 402 ListView.SelectedIndexCollection lvsic = m_lvFields.SelectedIndices; 403 for(int i = lvsic.Count - 1; i >= 0; --i) 404 m_lvFields.Items.RemoveAt(lvsic[i]); 405 406 EnableControlsEx(); 407 } 408 OnFieldsSelectedIndexChanged(object sender, EventArgs e)409 private void OnFieldsSelectedIndexChanged(object sender, EventArgs e) 410 { 411 EnableControlsEx(); 412 } 413 OnBtnFieldMoveUp(object sender, EventArgs e)414 private void OnBtnFieldMoveUp(object sender, EventArgs e) 415 { 416 ListView.SelectedIndexCollection lvsic = m_lvFields.SelectedIndices; 417 if(lvsic.Count != 1) { Debug.Assert(false); return; } 418 419 int iPos = lvsic[0]; 420 if(iPos == 0) return; 421 422 ListViewItem lviMove = m_lvFields.Items[iPos]; 423 m_lvFields.Items.RemoveAt(iPos); 424 m_lvFields.Items.Insert(iPos - 1, lviMove); 425 } 426 OnBtnFieldMoveDown(object sender, EventArgs e)427 private void OnBtnFieldMoveDown(object sender, EventArgs e) 428 { 429 ListView.SelectedIndexCollection lvsic = m_lvFields.SelectedIndices; 430 if(lvsic.Count != 1) { Debug.Assert(false); return; } 431 432 int iPos = lvsic[0]; 433 if(iPos == (m_lvFields.Items.Count - 1)) return; 434 435 ListViewItem lviMove = m_lvFields.Items[iPos]; 436 m_lvFields.Items.RemoveAt(iPos); 437 m_lvFields.Items.Insert(iPos + 1, lviMove); 438 } 439 OnFieldTypeSelectedIndexChanged(object sender, EventArgs e)440 private void OnFieldTypeSelectedIndexChanged(object sender, EventArgs e) 441 { 442 CsvFieldType t = GetCsvFieldType(); 443 if(t != m_tLastCsvType) m_cmbFieldFormat.Text = string.Empty; 444 445 EnableControlsEx(); 446 } 447 OnFieldFormatLinkClicked(object sender, LinkLabelLinkClickedEventArgs e)448 private void OnFieldFormatLinkClicked(object sender, LinkLabelLinkClickedEventArgs e) 449 { 450 CsvFieldType t = GetCsvFieldType(); 451 452 string strUrl = null; 453 if(IsTimeField(t)) 454 strUrl = "https://msdn.microsoft.com/en-us/library/8kb3ddd4.aspx"; 455 // else if(t == CsvFieldType.Group) 456 // { 457 // AppHelp.ShowHelp(AppDefs.HelpTopics.ImportExport, 458 // AppDefs.HelpTopics.ImportExportGenericCsv); 459 // return; 460 // } 461 else { Debug.Assert(false); return; } 462 463 WinUtil.OpenUrl(strUrl, null); 464 } 465 OnBtnFieldAdd(object sender, EventArgs e)466 private void OnBtnFieldAdd(object sender, EventArgs e) 467 { 468 bool bName, bFormat; 469 CsvFieldType t = GetCsvFieldType(out bName, out bFormat); 470 string strName = (bName ? m_tbFieldName.Text : null); 471 string strFormat = (bFormat ? m_cmbFieldFormat.Text : null); 472 473 AddCsvField(t, strName, strFormat); 474 ProcessResize(); 475 for(int i = 0; i < (m_lvFields.Items.Count - 1); ++i) 476 m_lvFields.Items[i].Selected = false; 477 m_lvFields.EnsureVisible(m_lvFields.Items.Count - 1); 478 UIUtil.SetFocusedItem(m_lvFields, m_lvFields.Items[ 479 m_lvFields.Items.Count - 1], true); 480 } 481 OnTabMainSelectedIndexChanged(object sender, EventArgs e)482 private void OnTabMainSelectedIndexChanged(object sender, EventArgs e) 483 { 484 if(m_tabMain.SelectedTab == m_tabPreview) 485 UpdateImportPreview(); 486 487 EnableControlsEx(); 488 } 489 GetCsvOptions()490 private CsvOptions GetCsvOptions() 491 { 492 CsvOptions opt = new CsvOptions(); 493 opt.FieldSeparator = GetCharFromDef(m_cmbFieldSep.Text); 494 opt.RecordSeparator = GetCharFromDef(m_cmbRecSep.Text); 495 opt.TextQualifier = GetCharFromDef(m_cmbTextQual.Text); 496 if((opt.FieldSeparator == char.MinValue) || (opt.RecordSeparator == char.MinValue) || 497 (opt.TextQualifier == char.MinValue)) 498 return null; 499 opt.BackslashIsEscape = m_cbBackEscape.Checked; 500 opt.TrimFields = m_cbTrim.Checked; 501 502 return opt; 503 } 504 GetCsvFieldInfos()505 private List<CsvFieldInfo> GetCsvFieldInfos() 506 { 507 List<CsvFieldInfo> lFields = new List<CsvFieldInfo>(); 508 509 for(int i = 0; i < m_lvFields.Items.Count; ++i) 510 { 511 CsvFieldInfo cfi = (m_lvFields.Items[i].Tag as CsvFieldInfo); 512 if(cfi == null) { Debug.Assert(false); continue; } 513 lFields.Add(cfi); 514 } 515 516 return lFields; 517 } 518 PerformImport(PwGroup pgStorage, bool bCreatePreview)519 private void PerformImport(PwGroup pgStorage, bool bCreatePreview) 520 { 521 List<CsvFieldInfo> lFields = GetCsvFieldInfos(); 522 523 if(bCreatePreview) 524 { 525 int dx = m_lvImportPreview.ClientRectangle.Width; // Before clearing 526 527 m_lvImportPreview.Items.Clear(); 528 m_lvImportPreview.Columns.Clear(); 529 530 foreach(CsvFieldInfo cfi in lFields) 531 { 532 string strCol = CsvFieldToString(cfi.Type); 533 if(cfi.Type == CsvFieldType.CustomString) 534 strCol = (cfi.Name ?? string.Empty); 535 m_lvImportPreview.Columns.Add(strCol, dx / lFields.Count); 536 } 537 } 538 539 CsvOptions opt = GetCsvOptions(); 540 if(opt == null) { Debug.Assert(bCreatePreview); return; } 541 542 string strData = GetDecodedText(); 543 CsvStreamReaderEx csr = new CsvStreamReaderEx(strData, opt); 544 545 Dictionary<string, PwGroup> dGroups = new Dictionary<string, PwGroup>(); 546 dGroups[string.Empty] = pgStorage; 547 548 if(bCreatePreview) m_lvImportPreview.BeginUpdate(); 549 550 DateTime dtNow = DateTime.UtcNow; 551 DateTime dtNoExpire = KdbTime.NeverExpireTime.ToDateTime(); 552 bool bIgnoreFirstRow = m_cbIgnoreFirst.Checked; 553 bool bIsFirstRow = true; 554 bool bMergeGroups = m_cbMergeGroups.Checked; 555 556 while(true) 557 { 558 string[] v = csr.ReadLine(); 559 if(v == null) break; 560 if(v.Length == 0) continue; 561 if((v.Length == 1) && (v[0].Length == 0)) continue; 562 563 if(bIsFirstRow && bIgnoreFirstRow) 564 { 565 bIsFirstRow = false; 566 continue; 567 } 568 bIsFirstRow = false; 569 570 PwGroup pg = pgStorage; 571 PwEntry pe = new PwEntry(true, true); 572 573 ListViewItem lvi = null; 574 for(int i = 0; i < Math.Min(v.Length, lFields.Count); ++i) 575 { 576 string strField = v[i]; 577 CsvFieldInfo cfi = lFields[i]; 578 579 if(cfi.Type == CsvFieldType.Ignore) { } 580 else if(cfi.Type == CsvFieldType.Group) 581 pg = FindCreateGroup(strField, pgStorage, dGroups, 582 cfi.Format, opt, bMergeGroups); 583 else if(cfi.Type == CsvFieldType.Title) 584 ImportUtil.AppendToField(pe, PwDefs.TitleField, 585 strField, m_pwDatabase); 586 else if(cfi.Type == CsvFieldType.UserName) 587 ImportUtil.AppendToField(pe, PwDefs.UserNameField, 588 strField, m_pwDatabase); 589 else if(cfi.Type == CsvFieldType.Password) 590 ImportUtil.AppendToField(pe, PwDefs.PasswordField, 591 strField, m_pwDatabase); 592 else if(cfi.Type == CsvFieldType.Url) 593 ImportUtil.AppendToField(pe, PwDefs.UrlField, 594 strField, m_pwDatabase); 595 else if(cfi.Type == CsvFieldType.Notes) 596 ImportUtil.AppendToField(pe, PwDefs.NotesField, 597 strField, m_pwDatabase); 598 else if(cfi.Type == CsvFieldType.CustomString) 599 ImportUtil.AppendToField(pe, (string.IsNullOrEmpty(cfi.Name) ? 600 PwDefs.NotesField : cfi.Name), strField, m_pwDatabase); 601 else if(cfi.Type == CsvFieldType.CreationTime) 602 pe.CreationTime = ParseDateTime(ref strField, cfi, dtNow); 603 // else if(cfi.Type == CsvFieldType.LastAccessTime) 604 // pe.LastAccessTime = ParseDateTime(ref strField, cfi, dtNow); 605 else if(cfi.Type == CsvFieldType.LastModTime) 606 pe.LastModificationTime = ParseDateTime(ref strField, cfi, dtNow); 607 else if(cfi.Type == CsvFieldType.ExpiryTime) 608 { 609 bool bParseSuccess; 610 pe.ExpiryTime = ParseDateTime(ref strField, cfi, dtNow, 611 out bParseSuccess); 612 pe.Expires = (bParseSuccess && (pe.ExpiryTime != dtNoExpire)); 613 } 614 else if(cfi.Type == CsvFieldType.Tags) 615 StrUtil.AddTags(pe.Tags, StrUtil.StringToTags(strField)); 616 else { Debug.Assert(false); } 617 618 if(bCreatePreview) 619 { 620 strField = StrUtil.MultiToSingleLine(strField); 621 622 if(lvi != null) lvi.SubItems.Add(strField); 623 else lvi = m_lvImportPreview.Items.Add(strField); 624 } 625 } 626 627 if(bCreatePreview) 628 { 629 // Create remaining subitems 630 for(int r = v.Length; r < lFields.Count; ++r) 631 { 632 if(lvi != null) lvi.SubItems.Add(string.Empty); 633 else lvi = m_lvImportPreview.Items.Add(string.Empty); 634 } 635 } 636 637 pg.AddEntry(pe, true); 638 } 639 640 if(bCreatePreview) 641 { 642 m_lvImportPreview.EndUpdate(); 643 ProcessResize(); 644 } 645 } 646 ParseDateTime(ref string strData, CsvFieldInfo cfi, DateTime dtDefault)647 private static DateTime ParseDateTime(ref string strData, CsvFieldInfo cfi, 648 DateTime dtDefault) 649 { 650 bool bDummy; 651 return ParseDateTime(ref strData, cfi, dtDefault, out bDummy); 652 } 653 ParseDateTime(ref string strData, CsvFieldInfo cfi, DateTime dtDefault, out bool bSuccess)654 private static DateTime ParseDateTime(ref string strData, CsvFieldInfo cfi, 655 DateTime dtDefault, out bool bSuccess) 656 { 657 DateTime? odt = null; 658 659 if(!string.IsNullOrEmpty(cfi.Format)) 660 { 661 const DateTimeStyles dts = (DateTimeStyles.AllowWhiteSpaces | 662 DateTimeStyles.AssumeLocal); 663 664 DateTime dtExact; 665 if(DateTime.TryParseExact(strData, cfi.Format, null, dts, 666 out dtExact)) 667 odt = TimeUtil.ToUtc(dtExact, false); 668 } 669 670 if(!odt.HasValue) 671 { 672 DateTime dtStd; 673 if(DateTime.TryParse(strData, out dtStd)) 674 odt = TimeUtil.ToUtc(dtStd, false); 675 } 676 677 if(odt.HasValue) 678 { 679 strData = TimeUtil.ToDisplayString(odt.Value); 680 bSuccess = true; 681 } 682 else 683 { 684 strData = string.Empty; 685 bSuccess = false; 686 687 odt = dtDefault; 688 } 689 690 return odt.Value; 691 } 692 SplitGroupPath(string strSpec, string strSep, CsvOptions opt)693 private static List<string> SplitGroupPath(string strSpec, string strSep, 694 CsvOptions opt) 695 { 696 List<string> l = new List<string>(); 697 698 if(string.IsNullOrEmpty(strSep)) l.Add(strSpec); 699 else 700 { 701 string[] vChain = strSpec.Split(new string[1] { strSep }, 702 StringSplitOptions.RemoveEmptyEntries); 703 for(int i = 0; i < vChain.Length; ++i) 704 { 705 string strGrp = vChain[i]; 706 if(opt.TrimFields) strGrp = strGrp.Trim(); 707 if(strGrp.Length > 0) l.Add(strGrp); 708 } 709 if(l.Count == 0) l.Add(strSpec); 710 } 711 712 return l; 713 } 714 AssembleGroupPath(List<string> l, int iOffset, char chSep)715 private static string AssembleGroupPath(List<string> l, int iOffset, 716 char chSep) 717 { 718 StringBuilder sb = new StringBuilder(); 719 720 for(int i = iOffset; i < l.Count; ++i) 721 { 722 if(i > iOffset) sb.Append(chSep); 723 sb.Append(l[i]); 724 } 725 726 return sb.ToString(); 727 } 728 FindCreateGroup(string strSpec, PwGroup pgStorage, Dictionary<string, PwGroup> dRootGroups, string strSep, CsvOptions opt, bool bMergeGroups)729 private static PwGroup FindCreateGroup(string strSpec, PwGroup pgStorage, 730 Dictionary<string, PwGroup> dRootGroups, string strSep, CsvOptions opt, 731 bool bMergeGroups) 732 { 733 List<string> l = SplitGroupPath(strSpec, strSep, opt); 734 735 if(bMergeGroups) 736 { 737 char chSep = StrUtil.GetUnusedChar(strSpec); 738 string strPath = AssembleGroupPath(l, 0, chSep); 739 740 return pgStorage.FindCreateSubTree(strPath, new char[1] { chSep }); 741 } 742 743 string strRootSub = l[0]; 744 if(!dRootGroups.ContainsKey(strRootSub)) 745 { 746 PwGroup pgNew = new PwGroup(true, true); 747 pgNew.Name = strRootSub; 748 pgStorage.AddGroup(pgNew, true); 749 dRootGroups[strRootSub] = pgNew; 750 } 751 PwGroup pg = dRootGroups[strRootSub]; 752 753 if(l.Count > 1) 754 { 755 char chSep = StrUtil.GetUnusedChar(strSpec); 756 string strSubPath = AssembleGroupPath(l, 1, chSep); 757 758 pg = pg.FindCreateSubTree(strSubPath, new char[1] { chSep }); 759 } 760 761 return pg; 762 } 763 OnBtnTabBack(object sender, EventArgs e)764 private void OnBtnTabBack(object sender, EventArgs e) 765 { 766 int i = m_tabMain.SelectedIndex; 767 if(i > 0) m_tabMain.SelectedIndex = i - 1; 768 } 769 OnBtnTabNext(object sender, EventArgs e)770 private void OnBtnTabNext(object sender, EventArgs e) 771 { 772 int i = m_tabMain.SelectedIndex; 773 if(i < (m_tabMain.TabCount - 1)) m_tabMain.SelectedIndex = i + 1; 774 } 775 OnBtnOK(object sender, EventArgs e)776 private void OnBtnOK(object sender, EventArgs e) 777 { 778 try { PerformImport(m_pwDatabase.RootGroup, false); } 779 catch(Exception ex) { MessageService.ShowWarning(ex); } 780 } 781 OnFieldSepSelectedIndexChanged(object sender, EventArgs e)782 private void OnFieldSepSelectedIndexChanged(object sender, EventArgs e) 783 { 784 EnableControlsEx(); 785 } 786 OnRecSepSelectedIndexChanged(object sender, EventArgs e)787 private void OnRecSepSelectedIndexChanged(object sender, EventArgs e) 788 { 789 EnableControlsEx(); 790 } 791 OnTextQualSelectedIndexChanged(object sender, EventArgs e)792 private void OnTextQualSelectedIndexChanged(object sender, EventArgs e) 793 { 794 EnableControlsEx(); 795 } 796 GuessCsvStructure()797 private void GuessCsvStructure() 798 { 799 bool bFieldsGuessed = GuessFieldTypes(); 800 m_cbIgnoreFirst.Checked = bFieldsGuessed; 801 } 802 GuessFieldTypes()803 private bool GuessFieldTypes() 804 { 805 CsvOptions opt = GetCsvOptions(); 806 if(opt == null) { Debug.Assert(false); return false; } 807 808 string strData = GetDecodedText(); 809 CsvStreamReaderEx csv = new CsvStreamReaderEx(strData, opt); 810 811 string[] v; 812 while(true) 813 { 814 v = csv.ReadLine(); 815 if(v == null) return false; 816 if(v.Length == 0) continue; 817 if((v.Length == 1) && (v[0].Length == 0)) continue; 818 break; 819 } 820 if(v.Length <= 3) return false; 821 822 CsvFieldInfo[] vFields = new CsvFieldInfo[v.Length]; 823 int nDetermined = 0; 824 for(int i = 0; i < v.Length; ++i) 825 { 826 CsvFieldInfo fi = GuessFieldType(v[i]); 827 if(fi != null) ++nDetermined; 828 else fi = new CsvFieldInfo(CsvFieldType.Ignore, null, null); 829 830 vFields[i] = fi; 831 } 832 833 // Accept the guesses only if at least half of them are 834 // probably correct 835 if(nDetermined < (v.Length + 1) / 2) return false; 836 837 m_lvFields.Items.Clear(); 838 foreach(CsvFieldInfo fi in vFields) 839 AddCsvField(fi.Type, fi.Name, fi.Format); 840 841 return true; 842 } 843 GuessFieldType(string strRawName)844 private static CsvFieldInfo GuessFieldType(string strRawName) 845 { 846 if(strRawName == null) return null; 847 string strName = strRawName.Trim(); 848 if(strName.Length == 0) return null; 849 850 string str = ImportUtil.MapNameToStandardField(strName, false); 851 if(str == PwDefs.TitleField) 852 return new CsvFieldInfo(CsvFieldType.Title, null, null); 853 if(str == PwDefs.UserNameField) 854 return new CsvFieldInfo(CsvFieldType.UserName, null, null); 855 if(str == PwDefs.PasswordField) 856 return new CsvFieldInfo(CsvFieldType.Password, null, null); 857 if(str == PwDefs.UrlField) 858 return new CsvFieldInfo(CsvFieldType.Url, null, null); 859 if(str == PwDefs.NotesField) 860 return new CsvFieldInfo(CsvFieldType.Notes, null, null); 861 862 string[] vGroupNames = new string[] { 863 "Password Groups", "Group", "Groups", "Group Tree", KPRes.Group 864 }; 865 foreach(string strGroupName in vGroupNames) 866 { 867 if(strName.Equals(strGroupName, StrUtil.CaseIgnoreCmp)) 868 return new CsvFieldInfo(CsvFieldType.Group, null, null); 869 } 870 871 string[] vCreationNames = new string[] { 872 "Creation", "Creation Time", 873 KPRes.CreationTime 874 }; 875 foreach(string strCreation in vCreationNames) 876 { 877 if(strName.Equals(strCreation, StrUtil.CaseIgnoreCmp)) 878 return new CsvFieldInfo(CsvFieldType.CreationTime, null, null); 879 } 880 881 // string[] vLastAccess = new string[] { 882 // "Last Access", "Last Access Time", 883 // KPRes.LastAccessTime 884 // }; 885 // foreach(string strLastAccess in vLastAccess) 886 // { 887 // if(strName.Equals(strLastAccess, StrUtil.CaseIgnoreCmp)) 888 // return new CsvFieldInfo(CsvFieldType.LastAccessTime, null, null); 889 // } 890 891 string[] vLastMod = new string[] { 892 "Last Modification", "Last Mod", "Last Modification Time", 893 "Last Mod Time", 894 KPRes.LastModificationTime 895 }; 896 foreach(string strLastMod in vLastMod) 897 { 898 if(strName.Equals(strLastMod, StrUtil.CaseIgnoreCmp)) 899 return new CsvFieldInfo(CsvFieldType.LastModTime, null, null); 900 } 901 902 string[] vExpiry = new string[] { 903 "Expires", "Expire", "Expiry", "Expiry Time", 904 KPRes.ExpiryTime, KPRes.ExpiryTimeDateOnly 905 }; 906 foreach(string strExpire in vExpiry) 907 { 908 if(strName.Equals(strExpire, StrUtil.CaseIgnoreCmp)) 909 return new CsvFieldInfo(CsvFieldType.ExpiryTime, null, null); 910 } 911 912 string[] vTags = new string[] { 913 "Tags", "Tag", KPRes.Tags, KPRes.Tag 914 }; 915 foreach(string strTag in vTags) 916 { 917 if(strName.Equals(strTag, StrUtil.CaseIgnoreCmp)) 918 return new CsvFieldInfo(CsvFieldType.Tags, null, null); 919 } 920 921 return null; 922 } 923 IsTimeField(CsvFieldType t)924 private static bool IsTimeField(CsvFieldType t) 925 { 926 return ((t == CsvFieldType.CreationTime) || 927 // (t == CsvFieldType.LastAccessTime) || 928 (t == CsvFieldType.LastModTime) || (t == CsvFieldType.ExpiryTime)); 929 } 930 OnBtnHelp(object sender, EventArgs e)931 private void OnBtnHelp(object sender, EventArgs e) 932 { 933 AppHelp.ShowHelp(AppDefs.HelpTopics.ImportExport, 934 AppDefs.HelpTopics.ImportExportGenericCsv); 935 } 936 } 937 } 938