Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 41 additions & 1 deletion include/SQLiteCpp/Database.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@

#include <SQLiteCpp/Column.h>
#include <SQLiteCpp/Utils.h> // definition of nullptr for C++98/C++03 compilers

#include <string.h>

// Forward declarations to avoid inclusion of <sqlite3.h> in a header
Expand Down Expand Up @@ -53,6 +52,32 @@ const char* getLibVersion() noexcept; // nothrow
/// Return SQLite version number using runtime call to the compiled library
int getLibVersionNumber() noexcept; // nothrow

// Public structure for representing all fields contained within the SQLite header.
// Official documentation for fields: https://www.sqlite.org/fileformat.html#the_database_header
struct Header {
unsigned char headerStr[16];
unsigned int pageSizeBytes;
unsigned char fileFormatWriteVersion;
unsigned char fileFormatReadVersion;
unsigned char reservedSpaceBytes;
unsigned char maxEmbeddedPayloadFrac;
unsigned char minEmbeddedPayloadFrac;
unsigned char leafPayloadFrac;
unsigned long fileChangeCounter;
unsigned long databaseSizePages;
unsigned long firstFreelistTrunkPage;
unsigned long totalFreelistPages;
unsigned long schemaCookie;
unsigned long schemaFormatNumber;
unsigned long defaultPageCacheSizeBytes;
unsigned long largestBTreePageNumber;
unsigned long databaseTextEncoding;
unsigned long userVersion;
unsigned long incrementalVaccumMode;
unsigned long applicationId;
unsigned long versionValidFor;
unsigned long sqliteVersion;
};

/**
* @brief RAII management of a SQLite Database Connection.
Expand Down Expand Up @@ -434,6 +459,21 @@ class Database
*/
static bool isUnencrypted(const std::string& aFilename);

/**
* @brief Parse SQLite header data from a database file.
*
* This function reads the first 100 bytes of a SQLite database file
* and reconstructs groups of individual bytes into the associated fields
* in a Header object.
*
* @param[in] aFilename path/uri to a file
*
* @return Header object containing file data
*
* @throw SQLite::Exception in case of error
*/
static Header getHeaderInfo(const std::string& aFilename);

/**
* @brief BackupType for the backup() method
*/
Expand Down
130 changes: 130 additions & 0 deletions src/Database.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,136 @@ bool Database::isUnencrypted(const std::string& aFilename)
throw exception;
}

// Parse header data from a database.
Header Database::getHeaderInfo(const std::string& aFilename)
{
Header h;
unsigned char buf[100];
char* pBuf = reinterpret_cast<char*>(&buf[0]);
char* pHeaderStr = reinterpret_cast<char*>(&h.headerStr[0]);

if (aFilename.empty())
{
throw SQLite::Exception("Could not open database, the aFilename parameter was empty.");
}

std::ifstream fileBuffer(aFilename.c_str(), std::ios::in | std::ios::binary);

if (fileBuffer.is_open())
{
fileBuffer.seekg(0, std::ios::beg);
fileBuffer.read(pBuf, 100);
fileBuffer.close();
strncpy(pHeaderStr, pBuf, 16);
}

else
{
throw SQLite::Exception("Error opening file: " + aFilename);
}

// If the "magic string" can't be found then header is invalid, corrupt or unreadable
if (!strncmp(pHeaderStr, "SQLite format 3", 15) == 0)
{
throw SQLite::Exception("Invalid or encrypted SQLite header");
}

h.pageSizeBytes = (buf[16] << 8) | buf[17];
h.fileFormatWriteVersion = buf[18];
h.fileFormatReadVersion = buf[19];
h.reservedSpaceBytes = buf[20];
h.maxEmbeddedPayloadFrac = buf[21];
h.minEmbeddedPayloadFrac = buf[22];
h.leafPayloadFrac = buf[23];

h.fileChangeCounter =
(buf[24] << 24) |
(buf[25] << 16) |
(buf[26] << 8) |
(buf[27] << 0);

h.databaseSizePages =
(buf[28] << 24) |
(buf[29] << 16) |
(buf[30] << 8) |
(buf[31] << 0);

h.firstFreelistTrunkPage =
(buf[32] << 24) |
(buf[33] << 16) |
(buf[34] << 8) |
(buf[35] << 0);

h.totalFreelistPages =
(buf[36] << 24) |
(buf[37] << 16) |
(buf[38] << 8) |
(buf[39] << 0);

h.schemaCookie =
(buf[40] << 24) |
(buf[41] << 16) |
(buf[42] << 8) |
(buf[43] << 0);

h.schemaFormatNumber =
(buf[44] << 24) |
(buf[45] << 16) |
(buf[46] << 8) |
(buf[47] << 0);

h.defaultPageCacheSizeBytes =
(buf[48] << 24) |
(buf[49] << 16) |
(buf[50] << 8) |
(buf[51] << 0);

h.largestBTreePageNumber =
(buf[52] << 24) |
(buf[53] << 16) |
(buf[54] << 8) |
(buf[55] << 0);

h.databaseTextEncoding =
(buf[56] << 24) |
(buf[57] << 16) |
(buf[58] << 8) |
(buf[59] << 0);

h.userVersion =
(buf[60] << 24) |
(buf[61] << 16) |
(buf[62] << 8) |
(buf[63] << 0);

h.incrementalVaccumMode =
(buf[64] << 24) |
(buf[65] << 16) |
(buf[66] << 8) |
(buf[67] << 0);

h.applicationId =
(buf[68] << 24) |
(buf[69] << 16) |
(buf[70] << 8) |
(buf[71] << 0);

h.versionValidFor =
(buf[92] << 24) |
(buf[93] << 16) |
(buf[94] << 8) |
(buf[95] << 0);

h.sqliteVersion =
(buf[96] << 24) |
(buf[97] << 16) |
(buf[98] << 8) |
(buf[99] << 0);

return h;
}


// This is a reference implementation of live backup taken from the official sit:
// https://www.sqlite.org/backup.html

Expand Down
62 changes: 62 additions & 0 deletions tests/Database_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include <gtest/gtest.h>

#include <cstdio>
#include <fstream>

#ifdef SQLITECPP_ENABLE_ASSERT_HANDLER
namespace SQLite
Expand Down Expand Up @@ -314,6 +315,67 @@ TEST(Database, execException)
// TODO: test Database::createFunction()
// TODO: test Database::loadExtension()

TEST(Database, getHeaderInfo)
{
remove("test.db3");
{
//Call without passing a database file name
EXPECT_THROW(SQLite::Database::getHeaderInfo(""),SQLite::Exception);

//Call with a non existant database
EXPECT_THROW(SQLite::Database::getHeaderInfo("test.db3"), SQLite::Exception);

//Simulate a corrupt header by writing garbage to a file
unsigned char badData[100];
char* pBadData = reinterpret_cast<char*>(&badData[0]);

std::ofstream corruptDb;
corruptDb.open("corrupt.db3", std::ios::app | std::ios::binary);
corruptDb.write(pBadData, 100);

EXPECT_THROW(SQLite::Database::getHeaderInfo("corrupt.db3"), SQLite::Exception);

remove("corrupt.db3");

// Create a new database
SQLite::Database db("test.db3", SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE);
db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)");

// Set assorted SQLite header values using associated PRAGMA
db.exec("PRAGMA main.user_version = 12345");
db.exec("PRAGMA main.application_id = 2468");

// Parse header fields from test database
SQLite::Header h = SQLite::Database::getHeaderInfo("test.db3");

//Test header values expliticly set via PRAGMA statements
EXPECT_EQ(h.userVersion, 12345);
EXPECT_EQ(h.applicationId, 2468);

//Test header values with expected default values
EXPECT_EQ(h.pageSizeBytes, 4096);
EXPECT_EQ(h.fileFormatWriteVersion,1);
EXPECT_EQ(h.fileFormatReadVersion,1);
EXPECT_EQ(h.reservedSpaceBytes,0);
EXPECT_EQ(h.maxEmbeddedPayloadFrac, 64);
EXPECT_EQ(h.minEmbeddedPayloadFrac, 32);
EXPECT_EQ(h.leafPayloadFrac, 32);
EXPECT_EQ(h.fileChangeCounter, 3);
EXPECT_EQ(h.databaseSizePages, 2);
EXPECT_EQ(h.firstFreelistTrunkPage, 0);
EXPECT_EQ(h.totalFreelistPages, 0);
EXPECT_EQ(h.schemaCookie, 1);
EXPECT_EQ(h.schemaFormatNumber, 4);
EXPECT_EQ(h.defaultPageCacheSizeBytes, 0);
EXPECT_EQ(h.largestBTreePageNumber, 0);
EXPECT_EQ(h.databaseTextEncoding, 1);
EXPECT_EQ(h.incrementalVaccumMode, 0);
EXPECT_EQ(h.versionValidFor, 3);
EXPECT_EQ(h.sqliteVersion, SQLITE_VERSION_NUMBER);
}
remove("test.db3");
}

#ifdef SQLITE_HAS_CODEC
TEST(Database, encryptAndDecrypt)
{
Expand Down