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