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.Globalization;
25 using System.IO;
26 using System.Text;
27 using System.Text.RegularExpressions;
28 
29 using KeePass.Resources;
30 
31 using KeePassLib;
32 using KeePassLib.Interfaces;
33 using KeePassLib.Utility;
34 
35 namespace KeePass.DataExchange.Formats
36 {
37 	// 6.2039.0+
38 	internal sealed class DashlaneJson6 : FileFormatProvider
39 	{
40 		public override bool SupportsImport { get { return true; } }
41 		public override bool SupportsExport { get { return false; } }
42 
43 		public override string FormatName { get { return "Dashlane JSON (\u2265 6)"; } }
44 		public override string DefaultExtension { get { return "json"; } }
45 		public override string ApplicationGroup { get { return KPRes.PasswordManagers; } }
46 
47 		public override bool ImportAppendsToRootGroupOnly { get { return true; } }
48 
49 		public override Image SmallIcon
50 		{
51 			get { return KeePass.Properties.Resources.B16x16_Imp_Dashlane; }
52 		}
53 
Import(PwDatabase pwStorage, Stream sInput, IStatusLogger slLogger)54 		public override void Import(PwDatabase pwStorage, Stream sInput,
55 			IStatusLogger slLogger)
56 		{
57 			using(StreamReader sr = new StreamReader(sInput, StrUtil.Utf8, true))
58 			{
59 				string str = sr.ReadToEnd();
60 				if(!string.IsNullOrEmpty(str))
61 				{
62 					CharStream cs = new CharStream(str);
63 					ImportRoot(new JsonObject(cs), pwStorage);
64 				}
65 			}
66 		}
67 
ImportRoot(JsonObject jo, PwDatabase pd)68 		private static void ImportRoot(JsonObject jo, PwDatabase pd)
69 		{
70 			foreach(string strType in jo.Items.Keys)
71 			{
72 				if(strType == null) { Debug.Assert(false); continue; }
73 				string strTypeNorm = strType.Trim().ToLower();
74 
75 				JsonObject[] vEntries = jo.GetValueArray<JsonObject>(strType);
76 				if(vEntries == null) { Debug.Assert(false); continue; }
77 
78 				foreach(JsonObject joEntry in vEntries)
79 				{
80 					if(joEntry == null) { Debug.Assert(false); continue; }
81 					ImportEntry(joEntry, pd, strTypeNorm);
82 				}
83 			}
84 		}
85 
ImportEntry(JsonObject jo, PwDatabase pd, string strTypeNorm)86 		private static void ImportEntry(JsonObject jo, PwDatabase pd, string strTypeNorm)
87 		{
88 			PwEntry pe = new PwEntry(true, true);
89 			pd.RootGroup.AddEntry(pe, true);
90 
91 			if(strTypeNorm.StartsWith("paymentmean"))
92 				strTypeNorm = "paymentmean";
93 
94 			switch(strTypeNorm)
95 			{
96 				case "bankstatement":
97 				case "fiscalstatement":
98 					pe.IconId = PwIcon.Homebanking;
99 					break;
100 
101 				case "driverlicence":
102 				case "idcard":
103 				case "passport":
104 				case "socialsecuritystatement":
105 					pe.IconId = PwIcon.Identity;
106 					break;
107 
108 				case "email":
109 					pe.IconId = PwIcon.EMail;
110 					break;
111 
112 				case "identity":
113 					pe.IconId = PwIcon.UserCommunication;
114 					break;
115 
116 				case "paymentmean":
117 					pe.IconId = PwIcon.Money;
118 					break;
119 
120 				default:
121 					Debug.Assert(strTypeNorm == "authentifiant");
122 					break;
123 			}
124 
125 			foreach(KeyValuePair<string, object> kvp in jo.Items)
126 			{
127 				string strValue = (kvp.Value as string);
128 				if(strValue == null)
129 				{
130 					Debug.Assert(false);
131 					if(kvp.Value != null) strValue = kvp.Value.ToString();
132 				}
133 				if(strValue == null) { Debug.Assert(false); continue; }
134 
135 				// Ignore GUIDs
136 				if(Regex.IsMatch(strValue, "^\\{\\w{8}-\\w{4}-\\w{4}-\\w{4}-\\w{12}\\}$",
137 					RegexOptions.Singleline))
138 					continue;
139 
140 				string strKey = kvp.Key;
141 				if(strKey == null) { Debug.Assert(false); continue; }
142 				strKey = strKey.Trim();
143 				if(string.IsNullOrEmpty(strKey)) { Debug.Assert(false); continue; }
144 				if(strKey.StartsWith("BankAccount", StrUtil.CaseIgnoreCmp) &&
145 					(strKey.Length > 11))
146 					strKey = strKey.Substring(11);
147 				if(strKey.IndexOf("Number", StrUtil.CaseIgnoreCmp) >= 0)
148 					strKey = PwDefs.UserNameField;
149 				strKey = (new string(char.ToUpper(strKey[0]), 1)) +
150 					strKey.Substring(1);
151 
152 				string strNorm = strKey.ToLower();
153 				switch(strNorm)
154 				{
155 					case "fullname":
156 					case "name":
157 					case "socialsecurityfullname":
158 						strKey = PwDefs.TitleField;
159 						break;
160 
161 					case "domain":
162 						strKey = PwDefs.UrlField;
163 						break;
164 
165 					case "expiredate":
166 						DateTime? odt = ParseDate(strValue);
167 						if(odt.HasValue)
168 						{
169 							pe.Expires = true;
170 							pe.ExpiryTime = odt.Value;
171 							strValue = string.Empty;
172 						}
173 						break;
174 
175 					case "secondarylogin":
176 						strKey = KPRes.UserName + " 2";
177 						break;
178 
179 					default:
180 						if(!strNorm.Contains("date") && !strNorm.Contains("time"))
181 						{
182 							string strStd = ImportUtil.MapNameToStandardField(strKey, true);
183 							if(!string.IsNullOrEmpty(strStd)) strKey = strStd;
184 						}
185 						break;
186 				}
187 
188 				if(strKey == PwDefs.UrlField)
189 					strValue = ImportUtil.FixUrl(strValue);
190 				else if(strNorm.Contains("time"))
191 					strValue = TryConvertTime(strValue);
192 
193 				if(!string.IsNullOrEmpty(strValue))
194 					ImportUtil.AppendToField(pe, strKey, strValue, pd);
195 			}
196 		}
197 
ParseDate(string str)198 		private static DateTime? ParseDate(string str)
199 		{
200 			if(string.IsNullOrEmpty(str)) { Debug.Assert(false); return null; }
201 
202 			DateTime dt;
203 			if(DateTime.TryParseExact(str.Trim(), "yyyy'-'M'-'d",
204 				NumberFormatInfo.InvariantInfo, DateTimeStyles.AssumeLocal, out dt))
205 				return TimeUtil.ToUtc(dt, false);
206 
207 			Debug.Assert(false);
208 			return null;
209 		}
210 
TryConvertTime(string str)211 		private static string TryConvertTime(string str)
212 		{
213 			if(string.IsNullOrEmpty(str)) return string.Empty;
214 
215 			try
216 			{
217 				if(Regex.IsMatch(str, "^\\d+$", RegexOptions.Singleline))
218 				{
219 					ulong u;
220 					if(ulong.TryParse(str, out u))
221 					{
222 						DateTime dt = TimeUtil.ConvertUnixTime(u);
223 						if(dt > TimeUtil.UnixRoot)
224 							return TimeUtil.ToDisplayString(dt);
225 					}
226 					else { Debug.Assert(false); }
227 				}
228 			}
229 			catch(Exception) { Debug.Assert(false); }
230 
231 			return str;
232 		}
233 	}
234 }
235