Skip to content

Commit 4bda7cd

Browse files
authored
Merge pull request sleuthkit#3385 from singhpratik5/develop
Adeed test_haskeeper.cpp for tsk/hashdb/hashkeeper.c
2 parents 18c3da1 + 1a08dc1 commit 4bda7cd

2 files changed

Lines changed: 359 additions & 0 deletions

File tree

Makefile.am

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -776,6 +776,7 @@ test_catch_runner_SOURCES = \
776776
test/tsk/hashdb/test_binsrch_index.cpp \
777777
test/tsk/hashdb/test_idxonly.cpp \
778778
test/tsk/hashdb/test_incase.cpp \
779+
test/tsk/hashdb/test_hashkeeper.cpp \
779780
test/tsk/fs/test_fs_name.cpp \
780781
test/tsk/fs/test_fs_types.cpp \
781782
test/tsk/fs/test_ntfs.cpp \
Lines changed: 358 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,358 @@
1+
/*
2+
File Name: test_hashkeeper.cpp
3+
Author: Pratik Singh (@singhpratik5)
4+
Testing: tsk/hashdb/hashkeeper.c (HK db)
5+
*/
6+
7+
#include "tsk/base/tsk_os.h"
8+
#include "tsk/hashdb/tsk_hashdb_i.h"
9+
#include "catch.hpp"
10+
11+
#include <string>
12+
#include <cstdio>
13+
#include <memory>
14+
#include <cstring>
15+
#include <vector>
16+
#include <stdexcept>
17+
18+
#include "test/tools/tsk_tempfile.h"
19+
20+
#ifdef TSK_WIN32
21+
#include <windows.h>
22+
#endif
23+
24+
namespace {
25+
26+
#ifdef TSK_WIN32
27+
// Custom deleter for unique_ptr to close the FILE handle and remove the file.
28+
struct SimpleFileDeleter {
29+
std::string path;
30+
void operator()(FILE* f) const {
31+
if (f) {
32+
fclose(f);
33+
}
34+
remove(path.c_str());
35+
}
36+
};
37+
38+
// Creates a temporary file with a simple, pure-ASCII name in the current
39+
// directory. This works around a known bug in the TSK library where it cannot
40+
// handle standard Windows temporary paths that contain Unicode characters.
41+
static std::unique_ptr<FILE, SimpleFileDeleter>
42+
tsk_make_simple_tempfile(std::string& path_out) {
43+
path_out = "./hashkeeper_test_temp.db";
44+
remove(path_out.c_str()); // Clean up from any previous failed run
45+
FILE* f = fopen(path_out.c_str(), "w+b");
46+
if (!f) {
47+
return std::unique_ptr<FILE, SimpleFileDeleter>(nullptr, {path_out});
48+
}
49+
return std::unique_ptr<FILE, SimpleFileDeleter>(f, {path_out});
50+
}
51+
52+
// Helper to convert a std::string path to the std::wstring that
53+
// the hk_open function signature expects on Windows.
54+
static std::wstring string_to_wstring(const std::string& str) {
55+
if (str.empty()) {
56+
return std::wstring();
57+
}
58+
int size_needed = MultiByteToWideChar(CP_UTF8, 0, &str[0], (int)str.size(), NULL, 0);
59+
std::wstring wstrTo(size_needed, 0);
60+
MultiByteToWideChar(CP_UTF8, 0, &str[0], (int)str.size(), &wstrTo[0], size_needed);
61+
return wstrTo;
62+
}
63+
#endif
64+
65+
66+
// Helper: write a correct HashKeeper header
67+
static void write_hk_header(FILE *f) {
68+
fputs("\"file_id\",\"hashset_id\",\"file_name\",\"directory\",\"hash\",\"file_size\",\"date_modified\",\"time_modified\",\"time_zone\",\"comments\",\"date_accessed\",\"time_accessed\"\n", f);
69+
}
70+
71+
// Helper: create a valid HashKeeper database file with 2-3 rows
72+
static void create_hashkeeper_db_file(FILE *f) {
73+
write_hk_header(f);
74+
fputs("1,1,\"test1.txt\",\"C:\\Windows\\System32\",\"0123456789ABCDEF0123456789ABCDEF\",1024,\"2023-01-01\",\"12:00:00\",\"UTC\",\"Test file 1\",\"2023-01-01\",\"12:00:00\"\n", f);
75+
fputs("2,1,\"test2.txt\",\"C:\\Windows\",\"FEDCBA9876543210FEDCBA9876543210\",2048,\"2023-01-02\",\"13:00:00\",\"UTC\",\"Test file 2\",\"2023-01-02\",\"13:00:00\"\n", f);
76+
fflush(f);
77+
}
78+
79+
// Helper: create malformed HK db (one good row, one malformed)
80+
static void create_malformed_hashkeeper_db_file(FILE *f) {
81+
write_hk_header(f);
82+
fputs("1,1,\"test1.txt\",\"C:\\Windows\\System32\",\"0123456789ABCDEF0123456789ABCDEF\",1024,\"2023-01-01\",\"12:00:00\",\"UTC\",\"Test file 1\",\"2023-01-01\",\"12:00:00\"\n", f);
83+
fputs("malformed_entry\n", f);
84+
fflush(f);
85+
}
86+
87+
// Helper: duplicate-hash HK db (same hash, different names)
88+
static void create_same_hash_different_names_hashkeeper_db_file(FILE *f) {
89+
write_hk_header(f);
90+
fputs("1,1,\"test1.txt\",\"C:\\Windows\\System32\",\"0123456789ABCDEF0123456789ABCDEF\",1024,\"2023-01-01\",\"12:00:00\",\"UTC\",\"Test file 1\",\"2023-01-01\",\"12:00:00\"\n", f);
91+
fputs("2,1,\"test1_renamed.txt\",\"C:\\Windows\\System32\",\"0123456789ABCDEF0123456789ABCDEF\",1024,\"2023-01-01\",\"12:00:00\",\"UTC\",\"Test file 1 renamed\",\"2023-01-01\",\"12:00:00\"\n", f);
92+
fflush(f);
93+
}
94+
95+
// Helper: header-only HK db
96+
static void create_empty_hashkeeper_db_file(FILE *f) {
97+
write_hk_header(f);
98+
fflush(f);
99+
}
100+
101+
// Helper: invalid (non-HK) file
102+
static void create_invalid_hashkeeper_db_file(FILE *f) {
103+
fputs("Invalid header\n", f);
104+
fputs("1,1,test1.txt,C:\\\\Windows\\\\System32,0123456789ABCDEF0123456789ABCDEF,1024,2023-01-01,12:00:00,UTC,Test file 1,2023-01-01,12:00:00\n", f);
105+
fflush(f);
106+
}
107+
108+
// Helper: find file offset of the line containing target hash (start-of-line offset)
109+
static bool find_line_offset_for_hash(FILE *f, const char *hash, TSK_OFF_T *out_off) {
110+
if (!f || !hash || !out_off) return false;
111+
if (0 != fseeko(f, 0, SEEK_SET)) return false;
112+
char buf[TSK_HDB_MAXLEN];
113+
TSK_OFF_T offset = 0;
114+
if (nullptr == fgets(buf, sizeof(buf), f)) return false;
115+
offset += (TSK_OFF_T)strlen(buf);
116+
while (nullptr != fgets(buf, sizeof(buf), f)) {
117+
size_t len = strlen(buf);
118+
if (strstr(buf, hash) != nullptr) {
119+
*out_off = offset;
120+
return true;
121+
}
122+
offset += (TSK_OFF_T)len;
123+
}
124+
return false;
125+
}
126+
127+
// Callbacks
128+
static TSK_WALK_RET_ENUM test_lookup_callback(TSK_HDB_INFO *hdb_info, const char *hash, const char *name, void *ptr) {
129+
(void)hdb_info; (void)hash;
130+
std::vector<std::string> *found_names = static_cast<std::vector<std::string>*>(ptr);
131+
if (found_names) found_names->push_back(std::string(name));
132+
return TSK_WALK_CONT;
133+
}
134+
135+
static TSK_WALK_RET_ENUM test_lookup_callback_stop(TSK_HDB_INFO *hdb_info, const char *hash, const char *name, void *ptr) {
136+
(void)hdb_info; (void)hash; (void)name; (void)ptr;
137+
return TSK_WALK_STOP;
138+
}
139+
140+
static TSK_WALK_RET_ENUM test_lookup_callback_error(TSK_HDB_INFO *hdb_info, const char *hash, const char *name, void *ptr) {
141+
(void)hdb_info; (void)hash; (void)name; (void)ptr;
142+
return TSK_WALK_ERROR;
143+
}
144+
145+
} // namespace
146+
147+
TEST_CASE("hk_test with valid HK db")
148+
{
149+
std::unique_ptr<FILE, int (*)(FILE*)> f(tsk_make_tempfile(), &fclose);
150+
REQUIRE(f != nullptr);
151+
create_hashkeeper_db_file(f.get());
152+
CHECK(hk_test(f.get()) == 1);
153+
}
154+
155+
TEST_CASE("hk_test with invalid db")
156+
{
157+
std::unique_ptr<FILE, int (*)(FILE*)> f(tsk_make_tempfile(), &fclose);
158+
REQUIRE(f != nullptr);
159+
create_invalid_hashkeeper_db_file(f.get());
160+
CHECK(hk_test(f.get()) == 0);
161+
}
162+
163+
TEST_CASE("hk_test with short header")
164+
{
165+
std::unique_ptr<FILE, int (*)(FILE*)> f(tsk_make_tempfile(), &fclose);
166+
REQUIRE(f != nullptr);
167+
fputs("\"file_id\",\"hashset_id\"\n", f.get());
168+
fputs("1,1,\"test1.txt\",\"C:\\\\Windows\\\\System32\",\"0123456789ABCDEF0123456789ABCDEF\",1024,\"2023-01-01\",\"12:00:00\",\"UTC\",\"Test file 1\",\"2023-01-01\",\"12:00:00\"\n", f.get());
169+
fflush(f.get());
170+
CHECK(hk_test(f.get()) == 0);
171+
}
172+
173+
TEST_CASE("hk_open basic")
174+
{
175+
#ifdef TSK_WIN32
176+
std::string path_s;
177+
auto f = tsk_make_simple_tempfile(path_s);
178+
std::wstring path_w = string_to_wstring(path_s);
179+
const TSK_TCHAR* path = path_w.c_str();
180+
#else
181+
std::string path_s;
182+
std::unique_ptr<FILE, int (*)(FILE*)> f(tsk_make_named_tempfile(&path_s), &fclose);
183+
const TSK_TCHAR* path = path_s.c_str();
184+
#endif
185+
REQUIRE(f != nullptr);
186+
create_hashkeeper_db_file(f.get());
187+
TSK_HDB_INFO *hdb = hk_open(f.get(), path);
188+
REQUIRE(hdb != nullptr);
189+
f.release(); // hdb now owns the file handle
190+
CHECK(hdb->db_type == TSK_HDB_DBTYPE_HK_ID);
191+
hdb->close_db(hdb);
192+
}
193+
194+
// The following tests for hk_makeindex and hk_getentry will fail on MinGW
195+
// because the TSK library has a hardcoded path to "C:\WINDOWS\System32\sort.exe"
196+
// which is not present in the MSYS2/MinGW environment.
197+
#if !(defined(__MINGW32__) || defined(__MINGW64__))
198+
199+
TEST_CASE("hk_makeindex ok / empty / malformed")
200+
{
201+
// ok
202+
{
203+
#ifdef TSK_WIN32
204+
std::string path_s;
205+
auto f = tsk_make_simple_tempfile(path_s);
206+
std::wstring path_w = string_to_wstring(path_s);
207+
const TSK_TCHAR* path = path_w.c_str();
208+
#else
209+
std::string path_s;
210+
std::unique_ptr<FILE, int (*)(FILE*)> f(tsk_make_named_tempfile(&path_s), &fclose);
211+
const TSK_TCHAR* path = path_s.c_str();
212+
#endif
213+
REQUIRE(f != nullptr);
214+
create_hashkeeper_db_file(f.get());
215+
TSK_HDB_INFO* hdb = hk_open(f.get(), path);
216+
REQUIRE(hdb != nullptr);
217+
f.release();
218+
TSK_TCHAR htype[] = _TSK_T("hk");
219+
CHECK(hk_makeindex(hdb, htype) == 0);
220+
hdb->close_db(hdb);
221+
}
222+
// empty → fail
223+
{
224+
#ifdef TSK_WIN32
225+
std::string path_s;
226+
auto f = tsk_make_simple_tempfile(path_s);
227+
std::wstring path_w = string_to_wstring(path_s);
228+
const TSK_TCHAR* path = path_w.c_str();
229+
#else
230+
std::string path_s;
231+
std::unique_ptr<FILE, int (*)(FILE*)> f(tsk_make_named_tempfile(&path_s), &fclose);
232+
const TSK_TCHAR* path = path_s.c_str();
233+
#endif
234+
REQUIRE(f != nullptr);
235+
create_empty_hashkeeper_db_file(f.get());
236+
TSK_HDB_INFO* hdb = hk_open(f.get(), path);
237+
REQUIRE(hdb != nullptr);
238+
f.release();
239+
TSK_TCHAR htype[] = _TSK_T("hk");
240+
CHECK(hk_makeindex(hdb, htype) == 1);
241+
hdb->close_db(hdb);
242+
}
243+
// malformed but with at least one valid row → still ok
244+
{
245+
#ifdef TSK_WIN32
246+
std::string path_s;
247+
auto f = tsk_make_simple_tempfile(path_s);
248+
std::wstring path_w = string_to_wstring(path_s);
249+
const TSK_TCHAR* path = path_w.c_str();
250+
#else
251+
std::string path_s;
252+
std::unique_ptr<FILE, int (*)(FILE*)> f(tsk_make_named_tempfile(&path_s), &fclose);
253+
const TSK_TCHAR* path = path_s.c_str();
254+
#endif
255+
REQUIRE(f != nullptr);
256+
create_malformed_hashkeeper_db_file(f.get());
257+
TSK_HDB_INFO* hdb = hk_open(f.get(), path);
258+
REQUIRE(hdb != nullptr);
259+
f.release();
260+
TSK_TCHAR htype[] = _TSK_T("hk");
261+
CHECK(hk_makeindex(hdb, htype) == 0);
262+
hdb->close_db(hdb);
263+
}
264+
}
265+
266+
TEST_CASE("hk_getentry success and variations")
267+
{
268+
#ifdef TSK_WIN32
269+
std::string path_s;
270+
auto f = tsk_make_simple_tempfile(path_s);
271+
std::wstring path_w = string_to_wstring(path_s);
272+
const TSK_TCHAR* path = path_w.c_str();
273+
#else
274+
std::string path_s;
275+
std::unique_ptr<FILE, int (*)(FILE*)> f(tsk_make_named_tempfile(&path_s), &fclose);
276+
const TSK_TCHAR* path = path_s.c_str();
277+
#endif
278+
REQUIRE(f != nullptr);
279+
create_hashkeeper_db_file(f.get());
280+
281+
TSK_OFF_T off = 0;
282+
REQUIRE(find_line_offset_for_hash(f.get(), "0123456789ABCDEF0123456789ABCDEF", &off));
283+
284+
TSK_HDB_INFO *hdb = hk_open(f.get(), path);
285+
REQUIRE(hdb != nullptr);
286+
f.release();
287+
288+
TSK_TCHAR htype[] = _TSK_T("hk");
289+
REQUIRE(hk_makeindex(hdb, htype) == 0);
290+
291+
// normal callback
292+
{
293+
std::vector<std::string> names;
294+
CHECK(hk_getentry(hdb, "0123456789ABCDEF0123456789ABCDEF", off, TSK_HDB_FLAG_QUICK, test_lookup_callback, &names) == 0);
295+
REQUIRE(names.size() == 1);
296+
CHECK(names[0] == "C:\\Windows\\System32\\test1.txt");
297+
}
298+
299+
// stop callback
300+
{
301+
std::vector<std::string> names;
302+
CHECK(hk_getentry(hdb, "0123456789ABCDEF0123456789ABCDEF", off, TSK_HDB_FLAG_QUICK, test_lookup_callback_stop, &names) == 0);
303+
}
304+
305+
// error callback
306+
{
307+
std::vector<std::string> names;
308+
CHECK(hk_getentry(hdb, "0123456789ABCDEF0123456789ABCDEF", off, TSK_HDB_FLAG_QUICK, test_lookup_callback_error, &names) == 1);
309+
}
310+
311+
// invalid hash length
312+
{
313+
std::vector<std::string> names;
314+
CHECK(hk_getentry(hdb, "0123456789ABCDEF", off, TSK_HDB_FLAG_QUICK, test_lookup_callback, &names) == 1);
315+
}
316+
317+
// invalid offset
318+
{
319+
std::vector<std::string> names;
320+
CHECK(hk_getentry(hdb, "0123456789ABCDEF0123456789ABCDEF", 999999, TSK_HDB_FLAG_QUICK, test_lookup_callback, &names) == 1);
321+
}
322+
hdb->close_db(hdb);
323+
}
324+
325+
TEST_CASE("hk_getentry same-hash different-names yields two callbacks")
326+
{
327+
#ifdef TSK_WIN32
328+
std::string path_s;
329+
auto f = tsk_make_simple_tempfile(path_s);
330+
std::wstring path_w = string_to_wstring(path_s);
331+
const TSK_TCHAR* path = path_w.c_str();
332+
#else
333+
std::string path_s;
334+
std::unique_ptr<FILE, int (*)(FILE*)> f(tsk_make_named_tempfile(&path_s), &fclose);
335+
const TSK_TCHAR* path = path_s.c_str();
336+
#endif
337+
REQUIRE(f != nullptr);
338+
create_same_hash_different_names_hashkeeper_db_file(f.get());
339+
340+
TSK_OFF_T off = 0;
341+
REQUIRE(find_line_offset_for_hash(f.get(), "0123456789ABCDEF0123456789ABCDEF", &off));
342+
343+
TSK_HDB_INFO *hdb = hk_open(f.get(), path);
344+
REQUIRE(hdb != nullptr);
345+
f.release();
346+
347+
TSK_TCHAR htype[] = _TSK_T("hk");
348+
REQUIRE(hk_makeindex(hdb, htype) == 0);
349+
350+
351+
std::vector<std::string> names;
352+
CHECK(hk_getentry(hdb, "0123456789ABCDEF0123456789ABCDEF", off, TSK_HDB_FLAG_QUICK, test_lookup_callback, &names) == 0);
353+
CHECK(names.size() == 2);
354+
hdb->close_db(hdb);
355+
}
356+
357+
#endif // #if !(defined(__MINGW32__) || defined(__MINGW64__))
358+

0 commit comments

Comments
 (0)