Displaying recent browsing history





5.00/5 (9 votes)
This article explains how and where browsing history is stored and how to fetch it.
Github repo: https://github.com/securedglobe/browsinghistory
Introduction
When trying to get the browsing history, there are several questions:
- Where is it stored?
- How frequent is it updated?
- In what format is it stored?
- What is the best method to access that information?
Microsoft Edge
Microsoft Edge stores its history in "<User profile>\AppData\Local\Microsoft\Edge\User Data\Default\History". It uses a table named urls to store each visit, excluding Incognito mode.
From the programming view, if you have found the user's profile path and stored it in userProfilePath, the database path will be:
userProfilePath + "\\AppData\\Local\\Microsoft\\Edge\\User Data\\Default\\History"
Chrome
Google Chrome stores its history in "<User profile>\AppData\Local\Google\Chrome\User Data\Default\History". It uses a table named urls to store each visit, excluding Incognito mode.
From the programming view, if you have found the user's profile path and stored it in userProfilePath, the database path will be:
userProfilePath + "\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\History"
The building blocks
For the purpose of this article, I created a Console application using Visual Studio 2022.
Database
Since the browsing history is stored in an sqlite3 database, we need to include the sqlite3 library or source code files.
To be able to read the browsing history database, we need to be able to handle sqlite3 databases. You can find further information about sqlite3 here. We then need to add sqlite3.c and sqlite3.h to the project.
User's Profile Folder
All locations are relative to the current user's profile path in the c:\users folder. We first need to find that path. Here is a function that does that:
// Get the current user's profile path (e.g., C:\Users\<username>\) std::wstring GetUserProfilePath() { WCHAR path[MAX_PATH]; if (SUCCEEDED(SHGetFolderPathW(NULL, CSIDL_PROFILE, NULL, 0, path))) { return std::wstring(path); } return L""; }
Using the database while being locked
Since the databases may be locked, and they will be locked if Edge or Chrome are running, we first need to copy them to another location and access them from there.
// Function to copy the locked database to a temporary file for querying bool CopyDatabaseToTemp(const std::wstring& dbPath, std::wstring& tempDbPath) { wchar_t tempPath[MAX_PATH]; if (GetTempPathW(MAX_PATH, tempPath) == 0) { return false; } wchar_t tempFileName[MAX_PATH]; if (GetTempFileNameW(tempPath, L"dbcopy", 0, tempFileName) == 0) { return false; } tempDbPath = std::wstring(tempFileName); try { std::filesystem::copy_file(dbPath, tempDbPath, std::filesystem::copy_options::overwrite_existing); return true; } catch (const std::filesystem::filesystem_error& e) { wprintf(L"Failed to copy database: %s\n", ConvertUtf8ToWide(e.what()).c_str()); return false; } }
Handling Unix dates and times
There are several ways to store dates and times.
WebKit Epoch
The WebKit timestamp starts from January 1, 1601 (UTC).
Unix Epoch
The Unix timestamp starts from January 1, 1970 (UTC).
Converting between WebKit Epoch to Unix Epoch
We use the following function for the conversion. ConvertWebKitToUnixTime()
Here is how it works:
Parameters:
webkitTime: This is an int64_t value representing a timestamp in microseconds since the WebKit epoch (January 1, 1601).
Return Type:
The function returns a time_t value, which represents the Unix timestamp in seconds since the Unix epoch (January 1, 1970).
The Logic:
The WebKit timestamp is in microseconds, while the Unix timestamp is in seconds. By dividing webkitTime by 1,000,000, we convert microseconds to seconds.
Adjusting for the Epoch Difference:
The difference between the two epochs (January 1, 1601, and January 1, 1970) is 369 years.
This difference translates to 11644473600 seconds.
The - 11644473600LL in the calculation adjusts the timestamp from the WebKit epoch to the Unix epoch.
Final Computation:
static_cast<time_t>(webkitTime / 1000000 - 11644473600LL);
It takes the WebKit timestamp in microseconds, converts it to seconds, and then subtracts the difference in seconds between the two epochs to yield the correct Unix timestamp.
Here is the function:
// Convert WebKit timestamp (microseconds) to Unix timestamp (seconds) time_t ConvertWebKitToUnixTime(int64_t webkitTime) { return static_cast<time_t>(webkitTime / 1000000 - 11644473600LL); // Adjusting for WebKit epoch }
There is also some code for printing the results in human readable format, in my case, I display them in UTC.
// Convert time_t to human-readable UTC time string std::wstring FormatUnixTimeToUTC(time_t unixTime) { struct tm timeInfo; if (gmtime_s(&timeInfo, &unixTime) != 0) // Safe version of gmtime { return L"Invalid time"; } wchar_t buffer[80]; wcsftime(buffer, sizeof(buffer), L"%Y-%m-%d %H:%M:%S", &timeInfo); // Format time return std::wstring(buffer); }
Printing the recent browsing history
In the source code below, we print both Edge and Chrome's recent browsing history.
// Function to read browsing history from a given database path
void PrintUrlsFromDatabase(const std::wstring& dbPath, const time_t currentTime, const time_t timeRangeInSeconds)
{
std::wstring tempDbPath;
if (!CopyDatabaseToTemp(dbPath, tempDbPath))
{
wprintf(L"Failed to copy database to temporary file: %s\n", dbPath.c_str());
return;
}
sqlite3* db;
if (sqlite3_open16(tempDbPath.c_str(), &db) != SQLITE_OK)
{
wprintf(L"Failed to open database: %s\n", tempDbPath.c_str());
return;
}
// Query to get URLs and visit times
const char* query = "SELECT u.url, v.visit_time FROM urls u JOIN visits v ON u.id = v.url ORDER BY v.visit_time DESC;";
sqlite3_stmt* stmt;
if (sqlite3_prepare_v2(db, query, -1, &stmt, nullptr) != SQLITE_OK)
{
wprintf(L"Failed to prepare statement: %S\n", sqlite3_errmsg(db));
sqlite3_close(db);
return;
}
// Execute the query and process the results
while (sqlite3_step(stmt) == SQLITE_ROW)
{
const char* foundUrlUtf8 = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 0));
int64_t visitTimeWebKit = sqlite3_column_int64(stmt, 1);
time_t visitTimeUnix = ConvertWebKitToUnixTime(visitTimeWebKit);
// Check if the URL was visited within the last 10 minutes
if (difftime(currentTime, visitTimeUnix) <= timeRangeInSeconds)
{
// Convert the URL from UTF-8 to wide string using the Windows API function
std::wstring foundUrl = ConvertUtf8ToWide(foundUrlUtf8);
// Format the visit time to a human-readable UTC string
std::wstring visitTimeStr = FormatUnixTimeToUTC(visitTimeUnix);
wprintf(L"URL: %s, Visit Time (UTC): %s\n", foundUrl.c_str(), visitTimeStr.c_str());
}
}
sqlite3_finalize(stmt);
sqlite3_close(db);
// Remove the temporary file after use
std::filesystem::remove(tempDbPath);
}