[APPHELP][APPHELP_APITEST] Add SdbGetFileAttributes + tests, based on the work of...
[reactos.git] / reactos / dll / appcompat / apphelp / sdbfileattr.c
1 /*
2 * Copyright 2011 André Hentschel
3 * Copyright 2013 Mislav Bla\9eevic
4 * Copyright 2015 Mark Jansen
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19 */
20
21 #include "windef.h"
22 #include "winbase.h"
23 #include "apphelp.h"
24 #include "imagehlp.h"
25 #include "winver.h"
26
27 #include "wine/unicode.h"
28
29 #define NUM_ATTRIBUTES 28
30
31 static void WINAPI SdbpSetDWORDAttr(PATTRINFO attr, TAG tag, DWORD value)
32 {
33 attr->type = tag;
34 attr->flags = ATTRIBUTE_AVAILABLE;
35 attr->dwattr = value;
36 }
37
38 static void WINAPI SdbpSetQWORDAttr(PATTRINFO attr, TAG tag, QWORD value)
39 {
40 attr->type = tag;
41 attr->flags = ATTRIBUTE_AVAILABLE;
42 attr->qwattr = value;
43 }
44
45 static void WINAPI SdbpSetStringAttr(PATTRINFO attr, TAG tag, WCHAR *string)
46 {
47 if (!string)
48 {
49 attr->flags = ATTRIBUTE_FAILED;
50 return;
51 }
52
53 attr->type = tag;
54 attr->flags = ATTRIBUTE_AVAILABLE;
55 attr->lpattr = SdbpStrDup(string);
56 }
57
58 static void WINAPI SdbpSetAttrFail(PATTRINFO attr)
59 {
60 attr->flags = ATTRIBUTE_FAILED;
61 }
62
63 static WCHAR* WINAPI SdbpGetStringAttr(LPWSTR translation, LPCWSTR attr, PVOID file_info)
64 {
65 UINT size = 0;
66 PVOID buffer;
67 WCHAR value[128] = {0};
68
69 if (!file_info)
70 return NULL;
71
72 snprintfW(value, 128, translation, attr);
73 if (VerQueryValueW(file_info, value, &buffer, &size) && size != 0)
74 return (WCHAR*)buffer;
75
76 return NULL;
77 }
78
79 static void WINAPI SdbpSetStringAttrFromAnsiString(PATTRINFO attr, TAG tag, PBYTE string, BYTE len)
80 {
81 WCHAR* dest;
82 if (!string)
83 {
84 attr->flags = ATTRIBUTE_FAILED;
85 return;
86 }
87
88 attr->type = tag;
89 attr->flags = ATTRIBUTE_AVAILABLE;
90 dest = attr->lpattr = SdbpAlloc((len+1) * sizeof(WCHAR));
91 while (len--)
92 *(dest++) = *(string++);
93 *dest = 0;
94 }
95
96 static void WINAPI SdbpSetStringAttrFromPascalString(PATTRINFO attr, TAG tag, PBYTE string)
97 {
98 if (!string)
99 {
100 attr->flags = ATTRIBUTE_FAILED;
101 return;
102 }
103
104 SdbpSetStringAttrFromAnsiString(attr, tag, string + 1, *string);
105 }
106
107 static void SdbpReadFileVersion(PATTRINFO attr_info, PVOID file_info)
108 {
109 static const WCHAR str_root[] = {'\\',0};
110
111 VS_FIXEDFILEINFO* fixed_info;
112 UINT size;
113 if (file_info && VerQueryValueW(file_info, str_root, (LPVOID*)&fixed_info, &size) && size)
114 {
115 if (fixed_info->dwSignature == VS_FFI_SIGNATURE)
116 {
117 LARGE_INTEGER version;
118 version.HighPart = fixed_info->dwFileVersionMS;
119 version.LowPart = fixed_info->dwFileVersionLS;
120 SdbpSetQWORDAttr(&attr_info[2], TAG_BIN_FILE_VERSION, version.QuadPart);
121 SdbpSetQWORDAttr(&attr_info[21], TAG_UPTO_BIN_FILE_VERSION, version.QuadPart);
122 version.HighPart = fixed_info->dwProductVersionMS;
123 version.LowPart = fixed_info->dwProductVersionLS;
124 SdbpSetQWORDAttr(&attr_info[3], TAG_BIN_PRODUCT_VERSION, version.QuadPart);
125 SdbpSetQWORDAttr(&attr_info[22], TAG_UPTO_BIN_PRODUCT_VERSION, version.QuadPart);
126
127 SdbpSetDWORDAttr(&attr_info[12], TAG_VERDATEHI, fixed_info->dwFileDateMS);
128 SdbpSetDWORDAttr(&attr_info[13], TAG_VERDATELO, fixed_info->dwFileDateLS);
129 SdbpSetDWORDAttr(&attr_info[14], TAG_VERFILEOS, fixed_info->dwFileOS); /* 0x000, 0x4, 0x40004, 0x40000, 0x10004, 0x10001*/
130 SdbpSetDWORDAttr(&attr_info[15], TAG_VERFILETYPE, fixed_info->dwFileType); /* VFT_APP, VFT_DLL, .... */
131 return;
132 }
133 }
134
135 SdbpSetAttrFail(&attr_info[2]);
136 SdbpSetAttrFail(&attr_info[3]);
137 SdbpSetAttrFail(&attr_info[12]);
138 SdbpSetAttrFail(&attr_info[13]);
139 SdbpSetAttrFail(&attr_info[14]);
140 SdbpSetAttrFail(&attr_info[15]);
141 SdbpSetAttrFail(&attr_info[21]);
142 SdbpSetAttrFail(&attr_info[22]);
143 }
144
145 static DWORD WINAPI SdbpCalculateFileChecksum(PMEMMAPPED mapping)
146 {
147 size_t n, size;
148 PDWORD data;
149 DWORD checks = 0, carry = 0;
150
151 if (mapping->size < 4)
152 return 0;
153
154 if (mapping->size >= 0x1000)
155 {
156 size = 0x1000;
157 if (mapping->size < 0x1200)
158 data = (PDWORD)(mapping->view + mapping->size - size);
159 else
160 data = (PDWORD)mapping->view + (0x200 / 4);
161 }
162 else
163 {
164 data = (PDWORD)mapping->view;
165 size = mapping->size;
166 }
167
168 for (n = 0; n < size / 4; ++n)
169 {
170 checks += *data;
171 carry = (checks & 1) ? 0x80000000 : 0;
172 checks >>= 1;
173 checks |= carry;
174 ++data;
175 }
176 return checks;
177 }
178
179 static DWORD WINAPI SdbpGetModuleType(PMEMMAPPED mapping)
180 {
181 PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)mapping->view;
182 PIMAGE_OS2_HEADER os2;
183
184 if (mapping->size < 2 || dos->e_magic != IMAGE_DOS_SIGNATURE)
185 return 0;
186
187 if (mapping->size < sizeof(IMAGE_DOS_HEADER) || mapping->size < (dos->e_lfanew+2))
188 return 1;
189
190 os2 = (PIMAGE_OS2_HEADER)((PBYTE)dos + dos->e_lfanew);
191 if (os2->ne_magic == IMAGE_OS2_SIGNATURE || os2->ne_magic == IMAGE_OS2_SIGNATURE_LE)
192 return 2;
193
194 if (mapping->size >= (dos->e_lfanew + 4) && ((PIMAGE_NT_HEADERS)os2)->Signature == IMAGE_NT_SIGNATURE)
195 return 3;
196
197 return 1;
198 }
199
200 /**
201 * Frees attribute data allocated by SdbGetFileAttributes.
202 *
203 * @note Unlike Windows, this implementation will not crash if attr_info is NULL.
204 *
205 * @param [in] attr_info Pointer to array of ATTRINFO which will be freed.
206 *
207 * @return TRUE if it succeeds, FALSE if it fails.
208 */
209 BOOL WINAPI SdbFreeFileAttributes(PATTRINFO attr_info)
210 {
211 WORD i;
212
213 if (!attr_info)
214 return FALSE;
215
216 for (i = 0; i < NUM_ATTRIBUTES; i++)
217 if ((attr_info[i].type & TAG_TYPE_MASK) == TAG_TYPE_STRINGREF)
218 SdbFree(attr_info[i].lpattr);
219 SdbFree(attr_info);
220 return TRUE;
221 }
222
223 /**
224 * Retrieves attribute data shim database requires to match a file with database entry
225 *
226 * @note You must free the attr_info allocated by this function by calling SdbFreeFileAttributes.
227 *
228 * @param [in] path Path to the file.
229 * @param [out] attr_info_ret Pointer to array of ATTRINFO. Contains attribute data.
230 * @param [out] attr_count Number of attributes in attr_info.
231 *
232 * @return TRUE if it succeeds, FALSE if it fails.
233 */
234 BOOL WINAPI SdbGetFileAttributes(LPCWSTR path, PATTRINFO *attr_info_ret, LPDWORD attr_count)
235 {
236 static const WCHAR str_tinfo[] = {'\\','V','a','r','F','i','l','e','I','n','f','o','\\','T','r','a','n','s','l','a','t','i','o','n',0};
237 static const WCHAR str_trans[] = {'\\','S','t','r','i','n','g','F','i','l','e','I','n','f','o','\\','%','0','4','x','%','0','4','x','\\','%','%','s',0};
238 static const WCHAR str_CompanyName[] = {'C','o','m','p','a','n','y','N','a','m','e',0};
239 static const WCHAR str_FileDescription[] = {'F','i','l','e','D','e','s','c','r','i','p','t','i','o','n',0};
240 static const WCHAR str_FileVersion[] = {'F','i','l','e','V','e','r','s','i','o','n',0};
241 static const WCHAR str_InternalName[] = {'I','n','t','e','r','n','a','l','N','a','m','e',0};
242 static const WCHAR str_LegalCopyright[] = {'L','e','g','a','l','C','o','p','y','r','i','g','h','t',0};
243 static const WCHAR str_OriginalFilename[] = {'O','r','i','g','i','n','a','l','F','i','l','e','n','a','m','e',0};
244 static const WCHAR str_ProductName[] = {'P','r','o','d','u','c','t','N','a','m','e',0};
245 static const WCHAR str_ProductVersion[] = {'P','r','o','d','u','c','t','V','e','r','s','i','o','n',0};
246
247 PIMAGE_NT_HEADERS headers;
248 MEMMAPPED mapped;
249 PVOID file_info = 0;
250 DWORD headersum, checksum, module_type;
251 WCHAR translation[128] = {0};
252 PATTRINFO attr_info;
253
254 struct LANGANDCODEPAGE {
255 WORD language;
256 WORD code_page;
257 } *lang_page;
258
259 if (!SdbpOpenMemMappedFile(path, &mapped))
260 {
261 SHIM_ERR("Error retrieving FILEINFO structure\n");
262 return FALSE;
263 }
264
265 attr_info = (PATTRINFO)SdbAlloc(NUM_ATTRIBUTES * sizeof(ATTRINFO));
266
267 SdbpSetDWORDAttr(&attr_info[0], TAG_SIZE, mapped.size);
268 if (mapped.size)
269 SdbpSetDWORDAttr(&attr_info[1], TAG_CHECKSUM, SdbpCalculateFileChecksum(&mapped));
270 else
271 SdbpSetAttrFail(&attr_info[1]);
272 module_type = SdbpGetModuleType(&mapped);
273
274 if (module_type)
275 SdbpSetDWORDAttr(&attr_info[16], TAG_MODULE_TYPE, module_type);
276 else
277 SdbpSetAttrFail(&attr_info[16]); /* TAG_MODULE_TYPE */
278
279 headers = CheckSumMappedFile(mapped.view, mapped.size, &headersum, &checksum);
280 if (headers)
281 {
282 DWORD info_size;
283 SIZE_T export_dir_size;
284 PIMAGE_EXPORT_DIRECTORY export_dir;
285
286 info_size = GetFileVersionInfoSizeW(path, NULL);
287 if (info_size != 0)
288 {
289 UINT page_size = 0;
290 file_info = SdbAlloc(info_size);
291 GetFileVersionInfoW(path, 0, info_size, file_info);
292 VerQueryValueW(file_info, str_tinfo, (LPVOID)&lang_page, &page_size);
293 snprintfW(translation, 128, str_trans, lang_page->language, lang_page->code_page);
294 }
295
296 /* Handles 2, 3, 12, 13, 14, 15, 21, 22 */
297 SdbpReadFileVersion(attr_info, file_info);
298
299 SdbpSetStringAttr(&attr_info[4], TAG_PRODUCT_VERSION, SdbpGetStringAttr(translation, str_ProductVersion, file_info));
300 SdbpSetStringAttr(&attr_info[5], TAG_FILE_DESCRIPTION, SdbpGetStringAttr(translation, str_FileDescription, file_info));
301 SdbpSetStringAttr(&attr_info[6], TAG_COMPANY_NAME, SdbpGetStringAttr(translation, str_CompanyName, file_info));
302 SdbpSetStringAttr(&attr_info[7], TAG_PRODUCT_NAME, SdbpGetStringAttr(translation, str_ProductName, file_info));
303 SdbpSetStringAttr(&attr_info[8], TAG_FILE_VERSION, SdbpGetStringAttr(translation, str_FileVersion, file_info));
304 SdbpSetStringAttr(&attr_info[9], TAG_ORIGINAL_FILENAME, SdbpGetStringAttr(translation, str_OriginalFilename, file_info));
305 SdbpSetStringAttr(&attr_info[10], TAG_INTERNAL_NAME, SdbpGetStringAttr(translation, str_InternalName, file_info));
306 SdbpSetStringAttr(&attr_info[11], TAG_LEGAL_COPYRIGHT, SdbpGetStringAttr(translation, str_LegalCopyright, file_info));
307
308 /* http://msdn.microsoft.com/en-us/library/windows/desktop/ms680339(v=vs.85).aspx */
309
310 SdbpSetDWORDAttr(&attr_info[17], TAG_PE_CHECKSUM, headers->OptionalHeader.CheckSum);
311
312 SdbpSetDWORDAttr(&attr_info[18], TAG_LINKER_VERSION, /* mislabeled! */
313 ((DWORD)headers->OptionalHeader.MajorImageVersion) << 16 | headers->OptionalHeader.MinorImageVersion);
314 SdbpSetAttrFail(&attr_info[19]); /* TAG_16BIT_DESCRIPTION */
315 SdbpSetAttrFail(&attr_info[20]); /* TAG_16BIT_MODULE_NAME */
316
317 SdbpSetDWORDAttr(&attr_info[23], TAG_LINK_DATE, headers->FileHeader.TimeDateStamp);
318 SdbpSetDWORDAttr(&attr_info[24], TAG_UPTO_LINK_DATE, headers->FileHeader.TimeDateStamp);
319
320 export_dir = (PIMAGE_EXPORT_DIRECTORY)ImageDirectoryEntryToData(mapped.view, FALSE, IMAGE_DIRECTORY_ENTRY_EXPORT, &export_dir_size);
321 if (export_dir)
322 {
323 PIMAGE_SECTION_HEADER section = NULL;
324 PBYTE export_name = ImageRvaToVa(headers, mapped.view, export_dir->Name, &section);
325 if (export_name)
326 SdbpSetStringAttrFromAnsiString(&attr_info[25], TAG_EXPORT_NAME, export_name, strlen((char*)export_name));
327 else
328 SdbpSetAttrFail(&attr_info[25]); /* TAG_EXPORT_NAME */
329 }
330 else
331 {
332 SdbpSetAttrFail(&attr_info[25]); /* TAG_EXPORT_NAME */
333 }
334
335 if (info_size)
336 SdbpSetDWORDAttr(&attr_info[26], TAG_VER_LANGUAGE, lang_page->language);
337
338 SdbpSetDWORDAttr(&attr_info[27], TAG_EXE_WRAPPER, 0); /* boolean */
339 }
340 else
341 {
342 int n;
343 for (n = 2; n < NUM_ATTRIBUTES; ++n)
344 {
345 if (n != 16 && n != 26)
346 SdbpSetAttrFail(&attr_info[n]);
347 }
348 if (module_type == 2)
349 {
350 PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)mapped.view;
351 PBYTE end = mapped.view + mapped.size, ptr;
352 PIMAGE_OS2_HEADER os2 = (PIMAGE_OS2_HEADER)((PBYTE)dos + dos->e_lfanew);
353 if ((PBYTE)(os2 + 1) <= end)
354 {
355 ptr = (PBYTE)dos + os2->ne_nrestab;
356 if (ptr <= end && (ptr + 1 + *ptr) <= end)
357 SdbpSetStringAttrFromPascalString(&attr_info[19], TAG_16BIT_DESCRIPTION, ptr);
358 ptr = (PBYTE)os2 + os2->ne_restab;
359 if (ptr <= end && (ptr + 1 + *ptr) <= end)
360 SdbpSetStringAttrFromPascalString(&attr_info[20], TAG_16BIT_MODULE_NAME, ptr);
361 }
362 }
363 }
364
365 *attr_info_ret = attr_info;
366 *attr_count = NUM_ATTRIBUTES; /* As far as I know, this one is always 28 */
367
368 SdbFree(file_info);
369 SdbpCloseMemMappedFile(&mapped);
370 return TRUE;
371 }