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.Drawing;
24 using System.IO;
25 using System.Text;
26 
27 using KeePass.Resources;
28 
29 using KeePassLib;
30 using KeePassLib.Interfaces;
31 using KeePassLib.Utility;
32 
33 namespace KeePass.DataExchange.Formats
34 {
35 	// 2.0.2-4.65.0.5+
36 	internal sealed class LastPassCsv2 : FileFormatProvider
37 	{
38 		public override bool SupportsImport { get { return true; } }
39 		public override bool SupportsExport { get { return false; } }
40 
41 		public override string FormatName { get { return "LastPass CSV"; } }
42 		public override string DefaultExtension { get { return "csv"; } }
43 		public override string ApplicationGroup { get { return KPRes.PasswordManagers; } }
44 
45 		public override bool ImportAppendsToRootGroupOnly { get { return false; } }
46 
47 		public override Image SmallIcon
48 		{
49 			get { return KeePass.Properties.Resources.B16x16_Imp_LastPass; }
50 		}
51 
Import(PwDatabase pwStorage, Stream sInput, IStatusLogger slLogger)52 		public override void Import(PwDatabase pwStorage, Stream sInput,
53 			IStatusLogger slLogger)
54 		{
55 			StreamReader sr = new StreamReader(sInput, StrUtil.Utf8, true);
56 			string strData = sr.ReadToEnd();
57 			sr.Close();
58 
59 			// The Chrome extension of LastPass 4.1.35 encodes some
60 			// special characters as XML entities; the web version and
61 			// the Firefox extension do not do this
62 			strData = strData.Replace(@"&lt;", @"<");
63 			strData = strData.Replace(@"&gt;", @">");
64 			strData = strData.Replace(@"&amp;", @"&");
65 
66 			CsvOptions opt = new CsvOptions();
67 			opt.BackslashIsEscape = false;
68 
69 			CsvStreamReaderEx csr = new CsvStreamReaderEx(strData, opt);
70 			ImportCsv(csr, pwStorage);
71 		}
72 
ImportCsv(CsvStreamReaderEx csr, PwDatabase pd)73 		private static void ImportCsv(CsvStreamReaderEx csr, PwDatabase pd)
74 		{
75 			CsvTableEntryReader ctr = new CsvTableEntryReader(pd);
76 
77 			const string strColUrl = "url";
78 
79 			Predicate<string[]> fIsSecNote = delegate(string[] vRow)
80 			{
81 				string str = ctr.GetData(vRow, strColUrl, string.Empty);
82 				return str.Equals("http://sn", StrUtil.CaseIgnoreCmp);
83 			};
84 
85 			ctr.SetDataAppend("name", PwDefs.TitleField);
86 			ctr.SetDataAppend("username", PwDefs.UserNameField);
87 			ctr.SetDataAppend("password", PwDefs.PasswordField);
88 
89 			ctr.SetDataHandler(strColUrl, delegate(string str, PwEntry pe,
90 				string[] vContextRow)
91 			{
92 				if(!fIsSecNote(vContextRow))
93 					ImportUtil.AppendToField(pe, PwDefs.UrlField, str, pd);
94 			});
95 
96 			ctr.SetDataHandler("extra", delegate(string str, PwEntry pe,
97 				string[] vContextRow)
98 			{
99 				if(fIsSecNote(vContextRow) && str.StartsWith("NoteType:",
100 					StrUtil.CaseIgnoreCmp))
101 				{
102 					AddNoteFields(pe, str, pd);
103 					return;
104 				}
105 
106 				ImportUtil.AppendToField(pe, PwDefs.NotesField, str, pd);
107 			});
108 
109 			ctr.SetDataHandler("grouping", delegate(string str, PwEntry pe,
110 				string[] vContextRow)
111 			{
112 				if(str.Length == 0) return;
113 
114 				PwGroup pg = pd.RootGroup.FindCreateSubTree(str,
115 					new string[1] { "\\" }, true);
116 				pg.AddEntry(pe, true);
117 			});
118 
119 			ctr.SetDataHandler("fav", delegate(string str, PwEntry pe,
120 				string[] vContextRow)
121 			{
122 				if(StrUtil.StringToBool(str)) pe.AddTag(PwDefs.FavoriteTag);
123 			});
124 
125 			ctr.Read(csr);
126 		}
127 
AddNoteFields(PwEntry pe, string strNotes, PwDatabase pd)128 		private static void AddNoteFields(PwEntry pe, string strNotes,
129 			PwDatabase pd)
130 		{
131 			string strData = StrUtil.NormalizeNewLines(strNotes, false);
132 			string[] vLines = strData.Split('\n');
133 
134 			string strFieldName = PwDefs.NotesField;
135 			bool bNotesFound = false;
136 			foreach(string strLine in vLines)
137 			{
138 				int iFieldLen = strLine.IndexOf(':');
139 				int iDataOffset = 0;
140 				if((iFieldLen > 0) && !bNotesFound)
141 				{
142 					string strRaw = strLine.Substring(0, iFieldLen).Trim();
143 					string strField = ImportUtil.MapNameToStandardField(strRaw, false);
144 					if(string.IsNullOrEmpty(strField)) strField = strRaw;
145 
146 					if(strField.Length != 0)
147 					{
148 						strFieldName = strField;
149 						iDataOffset = iFieldLen + 1;
150 
151 						bNotesFound |= (strRaw == "Notes"); // Not PwDefs.NotesField
152 					}
153 				}
154 
155 				ImportUtil.AppendToField(pe, strFieldName, strLine.Substring(
156 					iDataOffset), pd);
157 			}
158 		}
159 	}
160 }
161