Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / Win32

Displaying recent browsing history

5.00/5 (9 votes)
29 Sep 2024CPOL3 min read 8.5K   134  
This article explains how and where browsing history is stored and how to fetch it.
This article is about browsing history and focuses on the commonly used browsers: Edge and Chrome. In this article I will explain how to get the recent browsing history, in this case, the history of the last 10 minutes, along with the dates and times in UTC.

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. 

C++
// 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);
}

 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)