blob: 92bfd3713b4a90d93103e982f3e017474bcb80b3 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/modules/webdatabase/sqlite/sandboxed_vfs.h"
#include <algorithm>
#include <cstring>
#include <string>
#include <utility>
#include "base/files/file.h"
#include "base/logging.h"
#include "base/threading/platform_thread.h"
#include "build/build_config.h"
#include "sql/initialization.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/renderer/modules/webdatabase/sqlite/sandboxed_vfs_file.h"
#include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
#include "third_party/sqlite/sqlite3.h"
#if OS_WIN
#include <windows.h>
#else
#include <unistd.h>
#endif
namespace blink {
namespace {
// Name used to register the VFS with SQLite. Must be unique within Chrome.
constexpr char kSqliteVfsName[] = "renderer_sandboxed_vfs";
// Extracts the SandboxedVfs* stashed in a SQLite VFS structure.
SandboxedVfs* SandboxedVfsFromSqliteVfs(sqlite3_vfs* vfs) {
DCHECK(vfs);
return reinterpret_cast<SandboxedVfs*>(vfs->pAppData);
}
int SandboxedVfsOpen(sqlite3_vfs* vfs,
const char* full_path,
sqlite3_file* result_file,
int requested_flags,
int* granted_flags) {
return SandboxedVfsFromSqliteVfs(vfs)->Open(full_path, result_file,
requested_flags, granted_flags);
}
int SandboxedVfsDelete(sqlite3_vfs* vfs, const char* full_path, int sync_dir) {
return SandboxedVfsFromSqliteVfs(vfs)->Delete(full_path, sync_dir);
}
int SandboxedVfsAccess(sqlite3_vfs* vfs,
const char* full_path,
int flags,
int* result) {
return SandboxedVfsFromSqliteVfs(vfs)->Access(full_path, flags, result);
}
int SandboxedVfsFullPathname(sqlite3_vfs* vfs,
const char* file_path,
int result_size,
char* result) {
return SandboxedVfsFromSqliteVfs(vfs)->FullPathname(file_path, result_size,
result);
}
int SandboxedVfsRandomness(sqlite3_vfs* vfs, int result_size, char* result) {
return SandboxedVfsFromSqliteVfs(vfs)->Randomness(result_size, result);
}
int SandboxedVfsSleep(sqlite3_vfs* vfs, int microseconds) {
return SandboxedVfsFromSqliteVfs(vfs)->Sleep(microseconds);
}
int SandboxedVfsGetLastError(sqlite3_vfs* vfs,
int message_size,
char* message) {
return SandboxedVfsFromSqliteVfs(vfs)->GetLastError(message_size, message);
}
int SandboxedVfsCurrentTimeInt64(sqlite3_vfs* vfs, sqlite3_int64* result_ms) {
return SandboxedVfsFromSqliteVfs(vfs)->CurrentTimeInt64(result_ms);
}
sqlite3_vfs SqliteVfsFor(SandboxedVfs* sandboxed_vfs) {
DCHECK_EQ(sandboxed_vfs, reinterpret_cast<SandboxedVfs*>(
reinterpret_cast<void*>(sandboxed_vfs)))
<< "This implementation round-trips SandboxedVfs* via void*";
// VFS API entry points are listed at https://www.sqlite.org/c3ref/vfs.html
static constexpr int kSqliteVfsApiVersion = 3;
// Maximum file path size.
// TODO(pwnall): Obtain this from //base or some other good place.
static constexpr int kSqliteMaxPathSize = 512;
return {
kSqliteVfsApiVersion,
sizeof(SandboxedVfsFileSqliteBridge),
kSqliteMaxPathSize,
/*pNext=*/nullptr,
kSqliteVfsName,
/*pAppData=*/reinterpret_cast<void*>(sandboxed_vfs),
SandboxedVfsOpen,
SandboxedVfsDelete,
SandboxedVfsAccess,
SandboxedVfsFullPathname,
/*xDlOpen=*/nullptr,
/*xDlError=*/nullptr,
/*xDlSym=*/nullptr,
/*xDlClose=*/nullptr,
SandboxedVfsRandomness,
SandboxedVfsSleep,
/*xCurrentTime=*/nullptr, // Deprecated in API versions 2 and above.
SandboxedVfsGetLastError,
SandboxedVfsCurrentTimeInt64,
/*xSetSystemCall=*/nullptr,
/*xGetSystemCall=*/nullptr,
/*xNextSystemCall=*/nullptr,
};
}
// Converts a SQLite full file path to a Blink string.
//
// The argument is guaranteed to be the result of a FullPathname() call, with
// an optional suffix. The suffix always starts with "-".
String StringFromFullPath(const char* full_path) {
DCHECK(full_path);
return String::FromUTF8(full_path);
}
// SQLite measures time according to the Julian calendar.
WTF::Time SqliteEpoch() {
constexpr const double kMicroSecondsPerDay = 24 * 60 * 60 * 1000;
// The ".5" is intentional -- days in the Julian calendar start at noon.
// The offset is in the SQLite source code (os_unix.c) multiplied by 10.
constexpr const double kUnixEpochAsJulianDay = 2440587.5;
return WTF::Time::FromJsTime(-kUnixEpochAsJulianDay * kMicroSecondsPerDay);
}
} // namespace
// static
SandboxedVfs& SandboxedVfs::GetInstance() {
DEFINE_THREAD_SAFE_STATIC_LOCAL(SandboxedVfs, instance, ());
return instance;
}
SandboxedVfs::SandboxedVfs()
: sandboxed_vfs_(SqliteVfsFor(this)),
sqlite_epoch_(SqliteEpoch()),
platform_(Platform::Current()),
last_error_(base::File::FILE_OK) {
sql::EnsureSqliteInitialized();
// The register function returns a SQLite status as an int. The status is
// ignored here. If registration fails, we'd want to report the error while
// attempting to open a database. This is exactly what will happen, because
// SQLite won't find the VFS we're asking for.
sqlite3_vfs_register(&sandboxed_vfs_, /*make_default=*/0);
}
std::tuple<int, sqlite3*> SandboxedVfs::OpenDatabase(const String& filename) {
sqlite3* connection;
constexpr int open_flags =
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_PRIVATECACHE;
int status = sqlite3_open_v2(filename.Utf8().data(), &connection, open_flags,
kSqliteVfsName);
if (status != SQLITE_OK) {
// SQLite creates a connection handle in most cases where open fails.
if (connection) {
sqlite3_close(connection);
connection = nullptr;
}
}
return {status, connection};
}
int SandboxedVfs::Open(const char* full_path,
sqlite3_file* result_file,
int requested_flags,
int* granted_flags) {
DCHECK(full_path) << "WebSQL does not support creating temporary file names";
DCHECK(result_file);
DCHECK_EQ(0, requested_flags & SQLITE_OPEN_DELETEONCLOSE)
<< "SQLITE_OPEN_DELETEONCLOSE should not be used by WebSQL";
DCHECK_EQ(0, requested_flags & SQLITE_OPEN_EXCLUSIVE)
<< "SQLITE_OPEN_EXCLUSIVE should not be used by WebSQL";
String file_name = StringFromFullPath(full_path);
// TODO(pwnall): This doesn't have to be synchronous. WebSQL's open sequence
// is asynchronous, so we could open all the needed files (DB,
// journal, etc.) asynchronously, and store them in a hash table
// that would be used here.
base::File file = platform_->DatabaseOpenFile(file_name, requested_flags);
if (!file.IsValid()) {
// TODO(pwnall): Figure out if we can remove the fallback to read-only.
if (!(requested_flags & SQLITE_OPEN_READWRITE)) {
// The SQLite API requires that pMethods is set to null even if the open
// call returns a failure status.
result_file->pMethods = nullptr;
return SQLITE_CANTOPEN;
}
int new_flags =
(requested_flags & ~SQLITE_OPEN_READWRITE) | SQLITE_OPEN_READONLY;
return Open(full_path, result_file, new_flags, granted_flags);
}
SandboxedVfsFile::Create(std::move(file), std::move(file_name), this,
result_file);
if (granted_flags)
*granted_flags = requested_flags;
return SQLITE_OK;
}
int SandboxedVfs::Delete(const char* full_path, int sync_dir) {
DCHECK(full_path);
return platform_->DatabaseDeleteFile(StringFromFullPath(full_path), sync_dir);
}
int SandboxedVfs::Access(const char* full_path, int flags, int* result) {
DCHECK(full_path);
DCHECK(result);
long attributes =
platform_->DatabaseGetFileAttributes(StringFromFullPath(full_path));
// TODO(pwnall): Make the mojo interface portable across OSes, instead of
// messing around with OS-dependent constants here.
#if defined(OS_WIN)
const bool file_exists =
static_cast<DWORD>(attributes) != INVALID_FILE_ATTRIBUTES;
#else
const bool file_exists = attributes >= 0;
#endif // defined(OS_WIN)
if (!file_exists) {
*result = 0;
return SQLITE_OK;
}
#if defined(OS_WIN)
const bool can_read = true;
const bool can_write = (attributes & FILE_ATTRIBUTE_READONLY) == 0;
#else
const bool can_read = (attributes & R_OK) != 0;
const bool can_write = (attributes & W_OK) != 0;
#endif // defined(OS_WIN)
switch (flags) {
case SQLITE_ACCESS_EXISTS:
*result = 1;
break;
case SQLITE_ACCESS_READ:
*result = can_read ? 1 : 0;
break;
case SQLITE_ACCESS_READWRITE:
*result = (can_read && can_write) ? 1 : 0;
break;
default:
NOTREACHED() << "Unsupported xAccess flags: " << flags;
return SQLITE_ERROR;
}
return SQLITE_OK;
}
int SandboxedVfs::FullPathname(const char* file_path,
int result_size,
char* result) {
DCHECK(file_path);
DCHECK_GT(result_size, 0);
DCHECK(result);
// Renderer processes cannot access files directly, so it doesn't make sense
// to expose full paths here.
size_t file_path_size = std::strlen(file_path) + 1;
if (static_cast<size_t>(result_size) < file_path_size)
return SQLITE_CANTOPEN;
std::memcpy(result, file_path, file_path_size);
return SQLITE_OK;
}
int SandboxedVfs::Randomness(int result_size, char* result) {
DCHECK_GT(result_size, 0);
DCHECK(result);
// TODO(pwnall): Figure out if we need a real implementation.
std::memset(result, 0, result_size);
return result_size;
}
int SandboxedVfs::Sleep(int microseconds) {
DCHECK_GE(microseconds, 0);
base::PlatformThread::Sleep(WTF::TimeDelta::FromMicroseconds(microseconds));
return SQLITE_OK;
}
int SandboxedVfs::GetLastError(int message_size, char* message) const {
DCHECK_GE(message_size, 0);
DCHECK(message_size == 0 || message);
std::string error_string = base::File::ErrorToString(last_error_);
size_t error_string_size = error_string.length() + 1;
size_t copy_length =
std::min(static_cast<size_t>(message_size), error_string_size);
std::memcpy(message, error_string.c_str(), copy_length);
// The return value is zero if the message fits in the buffer, and non-zero if
// it does not fit.
return copy_length != error_string_size;
}
int SandboxedVfs::CurrentTimeInt64(sqlite3_int64* result_ms) {
DCHECK(result_ms);
WTF::TimeDelta delta = WTF::Time::Now() - sqlite_epoch_;
*result_ms = delta.InMilliseconds();
return SQLITE_OK;
}
} // namespace blink