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.Security;
30 using KeePassLib.Utility;
31 
32 namespace KeePass.Util
33 {
34 	public enum BinaryDataClass
35 	{
36 		Unknown = 0,
37 		Text,
38 		RichText,
39 		Image,
40 		WebDocument
41 	}
42 
43 	public static class BinaryDataClassifier
44 	{
45 		private static readonly string[] g_vTextExts = new string[] {
46 			".asc", ".bat", ".c", ".cpp", ".css", ".csv", ".h", ".hpp",
47 			".js", ".ps1", ".tab", ".tsv", ".txt"
48 		};
49 
50 		private static readonly string[] g_vRichTextExts = new string[] {
51 			".rtf"
52 		};
53 
54 		private static readonly string[] g_vImageExts = new string[] {
55 			".bmp", ".emf", ".exif", ".gif", ".ico", ".jpe", ".jpeg",
56 			".jpg", ".png", ".tif", ".tiff", ".wmf"
57 		};
58 
59 		private static readonly string[] g_vWebExts = new string[] {
60 			".htm", ".html"
61 
62 			// The following types can be displayed by Internet Explorer,
63 			// but not by the WebBrowser control
64 			// ".mht", ".xml", ".xsl", ".xslt"
65 		};
66 
UrlHasExt(string strUrl, string[] vExts)67 		private static bool UrlHasExt(string strUrl, string[] vExts)
68 		{
69 			Debug.Assert(strUrl.Trim().ToLowerInvariant() == strUrl);
70 
71 			foreach(string strExt in vExts)
72 			{
73 				Debug.Assert(strExt.StartsWith("."));
74 				Debug.Assert(strExt.Trim().ToLower() == strExt);
75 
76 				if(strUrl.EndsWith(strExt, StringComparison.Ordinal))
77 					return true;
78 			}
79 
80 			return false;
81 		}
82 
ClassifyUrl(string strUrl)83 		public static BinaryDataClass ClassifyUrl(string strUrl)
84 		{
85 			if(strUrl == null) { Debug.Assert(false); throw new ArgumentNullException("strUrl"); }
86 
87 			string str = strUrl.Trim().ToLowerInvariant();
88 
89 			if(UrlHasExt(str, g_vTextExts))
90 				return BinaryDataClass.Text;
91 			if(UrlHasExt(str, g_vRichTextExts))
92 				return BinaryDataClass.RichText;
93 			if(UrlHasExt(str, g_vImageExts))
94 				return BinaryDataClass.Image;
95 			if(UrlHasExt(str, g_vWebExts))
96 				return BinaryDataClass.WebDocument;
97 
98 			return BinaryDataClass.Unknown;
99 		}
100 
ClassifyData(byte[] pbData)101 		public static BinaryDataClass ClassifyData(byte[] pbData)
102 		{
103 			if(pbData == null) { Debug.Assert(false); throw new ArgumentNullException("pbData"); }
104 
105 			try
106 			{
107 				Image img = GfxUtil.LoadImage(pbData);
108 				if(img != null)
109 				{
110 					img.Dispose();
111 					return BinaryDataClass.Image;
112 				}
113 			}
114 			catch(Exception) { }
115 
116 			return BinaryDataClass.Unknown;
117 		}
118 
119 		// Cf. other overload
Classify(string strUrl, byte[] pbData)120 		public static BinaryDataClass Classify(string strUrl, byte[] pbData)
121 		{
122 			BinaryDataClass bdc = ClassifyUrl(strUrl);
123 			if(bdc != BinaryDataClass.Unknown) return bdc;
124 
125 			return ClassifyData(pbData);
126 		}
127 
128 		// Cf. other overload
Classify(string strUrl, ProtectedBinary pb)129 		public static BinaryDataClass Classify(string strUrl, ProtectedBinary pb)
130 		{
131 			BinaryDataClass bdc = ClassifyUrl(strUrl);
132 			if(bdc != BinaryDataClass.Unknown) return bdc;
133 
134 			if(pb == null) throw new ArgumentNullException("pb");
135 			byte[] pbData = pb.ReadData();
136 			try { bdc = ClassifyData(pbData); }
137 			finally { if(pb.IsProtected) MemUtil.ZeroByteArray(pbData); }
138 
139 			return bdc;
140 		}
141 
GetStringEncoding(byte[] pbData, out uint uStartOffset)142 		public static StrEncodingInfo GetStringEncoding(byte[] pbData,
143 			out uint uStartOffset)
144 		{
145 			if(pbData == null) { Debug.Assert(false); throw new ArgumentNullException("pbData"); }
146 
147 			uStartOffset = 0;
148 
149 			List<StrEncodingInfo> lEncs = new List<StrEncodingInfo>(StrUtil.Encodings);
150 			lEncs.Sort(BinaryDataClassifier.CompareBySigLengthRev);
151 
152 			foreach(StrEncodingInfo sei in lEncs)
153 			{
154 				byte[] pbSig = sei.StartSignature;
155 				if((pbSig == null) || (pbSig.Length == 0)) continue;
156 				if(pbSig.Length > pbData.Length) continue;
157 
158 				byte[] pbStart = MemUtil.Mid<byte>(pbData, 0, pbSig.Length);
159 				if(MemUtil.ArraysEqual(pbStart, pbSig))
160 				{
161 					uStartOffset = (uint)pbSig.Length;
162 					return sei;
163 				}
164 			}
165 
166 			if((pbData.Length % 4) == 0)
167 			{
168 				byte[] z3 = new byte[] { 0, 0, 0 };
169 				int i = MemUtil.IndexOf<byte>(pbData, z3);
170 				if((i >= 0) && (i < (pbData.Length - 4))) // Ignore last zero char
171 				{
172 					if((i % 4) == 0) return StrUtil.GetEncoding(StrEncodingType.Utf32BE);
173 					if((i % 4) == 1) return StrUtil.GetEncoding(StrEncodingType.Utf32LE);
174 					// Don't assume UTF-32 for other offsets
175 				}
176 			}
177 
178 			if((pbData.Length % 2) == 0)
179 			{
180 				int i = Array.IndexOf<byte>(pbData, 0);
181 				if((i >= 0) && (i < (pbData.Length - 2))) // Ignore last zero char
182 				{
183 					if((i % 2) == 0) return StrUtil.GetEncoding(StrEncodingType.Utf16BE);
184 					return StrUtil.GetEncoding(StrEncodingType.Utf16LE);
185 				}
186 			}
187 
188 			try
189 			{
190 				UTF8Encoding utf8Throw = new UTF8Encoding(false, true);
191 				utf8Throw.GetString(pbData);
192 				return StrUtil.GetEncoding(StrEncodingType.Utf8);
193 			}
194 			catch(Exception) { }
195 
196 			return StrUtil.GetEncoding(StrEncodingType.Default);
197 		}
198 
CompareBySigLengthRev(StrEncodingInfo a, StrEncodingInfo b)199 		private static int CompareBySigLengthRev(StrEncodingInfo a, StrEncodingInfo b)
200 		{
201 			Debug.Assert((a != null) && (b != null));
202 
203 			int na = 0, nb = 0;
204 			if((a != null) && (a.StartSignature != null))
205 				na = a.StartSignature.Length;
206 			if((b != null) && (b.StartSignature != null))
207 				nb = b.StartSignature.Length;
208 
209 			return -(na.CompareTo(nb));
210 		}
211 	}
212 }
213