1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "content/browser/renderer_host/database_message_filter.h"
6
7#include <string>
8
9#include "base/bind.h"
10#include "base/platform_file.h"
11#include "base/strings/string_util.h"
12#include "base/strings/utf_string_conversions.h"
13#include "base/threading/thread.h"
14#include "content/common/database_messages.h"
15#include "content/public/browser/user_metrics.h"
16#include "content/public/common/result_codes.h"
17#include "third_party/sqlite/sqlite3.h"
18#include "webkit/browser/database/database_util.h"
19#include "webkit/browser/database/vfs_backend.h"
20#include "webkit/browser/quota/quota_manager.h"
21#include "webkit/common/database/database_identifier.h"
22
23#if defined(OS_POSIX)
24#include "base/file_descriptor_posix.h"
25#endif
26
27using quota::QuotaManager;
28using quota::QuotaManagerProxy;
29using quota::QuotaStatusCode;
30using webkit_database::DatabaseTracker;
31using webkit_database::DatabaseUtil;
32using webkit_database::VfsBackend;
33
34namespace content {
35namespace {
36
37const int kNumDeleteRetries = 2;
38const int kDelayDeleteRetryMs = 100;
39
40}  // namespace
41
42DatabaseMessageFilter::DatabaseMessageFilter(
43    webkit_database::DatabaseTracker* db_tracker)
44    : db_tracker_(db_tracker),
45      observer_added_(false) {
46  DCHECK(db_tracker_.get());
47}
48
49void DatabaseMessageFilter::OnChannelClosing() {
50  BrowserMessageFilter::OnChannelClosing();
51  if (observer_added_) {
52    observer_added_ = false;
53    BrowserThread::PostTask(
54        BrowserThread::FILE, FROM_HERE,
55        base::Bind(&DatabaseMessageFilter::RemoveObserver, this));
56  }
57}
58
59void DatabaseMessageFilter::AddObserver() {
60  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
61  db_tracker_->AddObserver(this);
62}
63
64void DatabaseMessageFilter::RemoveObserver() {
65  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
66  db_tracker_->RemoveObserver(this);
67
68  // If the renderer process died without closing all databases,
69  // then we need to manually close those connections
70  db_tracker_->CloseDatabases(database_connections_);
71  database_connections_.RemoveAllConnections();
72}
73
74void DatabaseMessageFilter::OverrideThreadForMessage(
75    const IPC::Message& message,
76    BrowserThread::ID* thread) {
77  if (message.type() == DatabaseHostMsg_GetSpaceAvailable::ID)
78    *thread = BrowserThread::IO;
79  else if (IPC_MESSAGE_CLASS(message) == DatabaseMsgStart)
80    *thread = BrowserThread::FILE;
81
82  if (message.type() == DatabaseHostMsg_Opened::ID && !observer_added_) {
83    observer_added_ = true;
84    BrowserThread::PostTask(
85        BrowserThread::FILE, FROM_HERE,
86        base::Bind(&DatabaseMessageFilter::AddObserver, this));
87  }
88}
89
90bool DatabaseMessageFilter::OnMessageReceived(
91    const IPC::Message& message,
92    bool* message_was_ok) {
93  bool handled = true;
94  IPC_BEGIN_MESSAGE_MAP_EX(DatabaseMessageFilter, message, *message_was_ok)
95    IPC_MESSAGE_HANDLER_DELAY_REPLY(DatabaseHostMsg_OpenFile,
96                                    OnDatabaseOpenFile)
97    IPC_MESSAGE_HANDLER_DELAY_REPLY(DatabaseHostMsg_DeleteFile,
98                                    OnDatabaseDeleteFile)
99    IPC_MESSAGE_HANDLER_DELAY_REPLY(DatabaseHostMsg_GetFileAttributes,
100                                    OnDatabaseGetFileAttributes)
101    IPC_MESSAGE_HANDLER_DELAY_REPLY(DatabaseHostMsg_GetFileSize,
102                                    OnDatabaseGetFileSize)
103    IPC_MESSAGE_HANDLER_DELAY_REPLY(DatabaseHostMsg_GetSpaceAvailable,
104                                    OnDatabaseGetSpaceAvailable)
105    IPC_MESSAGE_HANDLER(DatabaseHostMsg_Opened, OnDatabaseOpened)
106    IPC_MESSAGE_HANDLER(DatabaseHostMsg_Modified, OnDatabaseModified)
107    IPC_MESSAGE_HANDLER(DatabaseHostMsg_Closed, OnDatabaseClosed)
108    IPC_MESSAGE_HANDLER(DatabaseHostMsg_HandleSqliteError, OnHandleSqliteError)
109    IPC_MESSAGE_UNHANDLED(handled = false)
110  IPC_END_MESSAGE_MAP_EX()
111  return handled;
112}
113
114DatabaseMessageFilter::~DatabaseMessageFilter() {
115}
116
117void DatabaseMessageFilter::OnDatabaseOpenFile(const string16& vfs_file_name,
118                                               int desired_flags,
119                                               IPC::Message* reply_msg) {
120  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
121  base::PlatformFile file_handle = base::kInvalidPlatformFileValue;
122  std::string origin_identifier;
123  string16 database_name;
124
125  // When in incognito mode, we want to make sure that all DB files are
126  // removed when the incognito browser context goes away, so we add the
127  // SQLITE_OPEN_DELETEONCLOSE flag when opening all files, and keep
128  // open handles to them in the database tracker to make sure they're
129  // around for as long as needed.
130  if (vfs_file_name.empty()) {
131    VfsBackend::OpenTempFileInDirectory(db_tracker_->DatabaseDirectory(),
132                                        desired_flags, &file_handle);
133  } else if (DatabaseUtil::CrackVfsFileName(vfs_file_name, &origin_identifier,
134                                            &database_name, NULL) &&
135             !db_tracker_->IsDatabaseScheduledForDeletion(origin_identifier,
136                                                          database_name)) {
137    base::FilePath db_file = DatabaseUtil::GetFullFilePathForVfsFile(
138        db_tracker_.get(), vfs_file_name);
139    if (!db_file.empty()) {
140        if (db_tracker_->IsIncognitoProfile()) {
141          db_tracker_->GetIncognitoFileHandle(vfs_file_name, &file_handle);
142          if (file_handle == base::kInvalidPlatformFileValue) {
143            VfsBackend::OpenFile(db_file,
144                                 desired_flags | SQLITE_OPEN_DELETEONCLOSE,
145                                 &file_handle);
146            if (!(desired_flags & SQLITE_OPEN_DELETEONCLOSE))
147              db_tracker_->SaveIncognitoFileHandle(vfs_file_name, file_handle);
148          }
149        } else {
150          VfsBackend::OpenFile(db_file, desired_flags, &file_handle);
151        }
152      }
153  }
154
155  // Then we duplicate the file handle to make it useable in the renderer
156  // process. The original handle is closed, unless we saved it in the
157  // database tracker.
158  bool auto_close = !db_tracker_->HasSavedIncognitoFileHandle(vfs_file_name);
159  IPC::PlatformFileForTransit target_handle =
160      IPC::GetFileHandleForProcess(file_handle, PeerHandle(), auto_close);
161
162  DatabaseHostMsg_OpenFile::WriteReplyParams(reply_msg, target_handle);
163  Send(reply_msg);
164}
165
166void DatabaseMessageFilter::OnDatabaseDeleteFile(const string16& vfs_file_name,
167                                                 const bool& sync_dir,
168                                                 IPC::Message* reply_msg) {
169  DatabaseDeleteFile(vfs_file_name, sync_dir, reply_msg, kNumDeleteRetries);
170}
171
172void DatabaseMessageFilter::DatabaseDeleteFile(const string16& vfs_file_name,
173                                               bool sync_dir,
174                                               IPC::Message* reply_msg,
175                                               int reschedule_count) {
176  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
177
178  // Return an error if the file name is invalid or if the file could not
179  // be deleted after kNumDeleteRetries attempts.
180  int error_code = SQLITE_IOERR_DELETE;
181  base::FilePath db_file =
182      DatabaseUtil::GetFullFilePathForVfsFile(db_tracker_.get(), vfs_file_name);
183  if (!db_file.empty()) {
184    // In order to delete a journal file in incognito mode, we only need to
185    // close the open handle to it that's stored in the database tracker.
186    if (db_tracker_->IsIncognitoProfile()) {
187      const string16 wal_suffix(ASCIIToUTF16("-wal"));
188      string16 sqlite_suffix;
189
190      // WAL files can be deleted without having previously been opened.
191      if (!db_tracker_->HasSavedIncognitoFileHandle(vfs_file_name) &&
192          DatabaseUtil::CrackVfsFileName(vfs_file_name,
193                                         NULL, NULL, &sqlite_suffix) &&
194          sqlite_suffix == wal_suffix) {
195        error_code = SQLITE_OK;
196      } else if (db_tracker_->CloseIncognitoFileHandle(vfs_file_name)) {
197        error_code = SQLITE_OK;
198      }
199    } else {
200      error_code = VfsBackend::DeleteFile(db_file, sync_dir);
201    }
202
203    if ((error_code == SQLITE_IOERR_DELETE) && reschedule_count) {
204      // If the file could not be deleted, try again.
205      BrowserThread::PostDelayedTask(
206          BrowserThread::FILE, FROM_HERE,
207          base::Bind(&DatabaseMessageFilter::DatabaseDeleteFile, this,
208                     vfs_file_name, sync_dir, reply_msg, reschedule_count - 1),
209          base::TimeDelta::FromMilliseconds(kDelayDeleteRetryMs));
210      return;
211    }
212  }
213
214  DatabaseHostMsg_DeleteFile::WriteReplyParams(reply_msg, error_code);
215  Send(reply_msg);
216}
217
218void DatabaseMessageFilter::OnDatabaseGetFileAttributes(
219    const string16& vfs_file_name,
220    IPC::Message* reply_msg) {
221  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
222  int32 attributes = -1;
223  base::FilePath db_file =
224      DatabaseUtil::GetFullFilePathForVfsFile(db_tracker_.get(), vfs_file_name);
225  if (!db_file.empty())
226    attributes = VfsBackend::GetFileAttributes(db_file);
227
228  DatabaseHostMsg_GetFileAttributes::WriteReplyParams(
229      reply_msg, attributes);
230  Send(reply_msg);
231}
232
233void DatabaseMessageFilter::OnDatabaseGetFileSize(
234    const string16& vfs_file_name, IPC::Message* reply_msg) {
235  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
236  int64 size = 0;
237  base::FilePath db_file =
238      DatabaseUtil::GetFullFilePathForVfsFile(db_tracker_.get(), vfs_file_name);
239  if (!db_file.empty())
240    size = VfsBackend::GetFileSize(db_file);
241
242  DatabaseHostMsg_GetFileSize::WriteReplyParams(reply_msg, size);
243  Send(reply_msg);
244}
245
246void DatabaseMessageFilter::OnDatabaseGetSpaceAvailable(
247    const std::string& origin_identifier, IPC::Message* reply_msg) {
248  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
249  DCHECK(db_tracker_->quota_manager_proxy());
250
251  QuotaManager* quota_manager =
252      db_tracker_->quota_manager_proxy()->quota_manager();
253  if (!quota_manager) {
254    NOTREACHED();  // The system is shutting down, messages are unexpected.
255    DatabaseHostMsg_GetSpaceAvailable::WriteReplyParams(
256        reply_msg, static_cast<int64>(0));
257    Send(reply_msg);
258    return;
259  }
260
261  quota_manager->GetUsageAndQuota(
262      webkit_database::GetOriginFromIdentifier(origin_identifier),
263      quota::kStorageTypeTemporary,
264      base::Bind(&DatabaseMessageFilter::OnDatabaseGetUsageAndQuota,
265                 this, reply_msg));
266}
267
268void DatabaseMessageFilter::OnDatabaseGetUsageAndQuota(
269    IPC::Message* reply_msg,
270    quota::QuotaStatusCode status,
271    int64 usage,
272    int64 quota) {
273  int64 available = 0;
274  if ((status == quota::kQuotaStatusOk) && (usage < quota))
275    available = quota - usage;
276  DatabaseHostMsg_GetSpaceAvailable::WriteReplyParams(reply_msg, available);
277  Send(reply_msg);
278}
279
280void DatabaseMessageFilter::OnDatabaseOpened(
281    const std::string& origin_identifier,
282    const string16& database_name,
283    const string16& description,
284    int64 estimated_size) {
285  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
286
287  if (!DatabaseUtil::IsValidOriginIdentifier(origin_identifier)) {
288    RecordAction(UserMetricsAction("BadMessageTerminate_DBMF"));
289    BadMessageReceived();
290    return;
291  }
292
293  int64 database_size = 0;
294  db_tracker_->DatabaseOpened(origin_identifier, database_name, description,
295                              estimated_size, &database_size);
296  database_connections_.AddConnection(origin_identifier, database_name);
297  Send(new DatabaseMsg_UpdateSize(origin_identifier, database_name,
298                                  database_size));
299}
300
301void DatabaseMessageFilter::OnDatabaseModified(
302    const std::string& origin_identifier,
303    const string16& database_name) {
304  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
305  if (!database_connections_.IsDatabaseOpened(
306          origin_identifier, database_name)) {
307    RecordAction(UserMetricsAction("BadMessageTerminate_DBMF"));
308    BadMessageReceived();
309    return;
310  }
311
312  db_tracker_->DatabaseModified(origin_identifier, database_name);
313}
314
315void DatabaseMessageFilter::OnDatabaseClosed(
316    const std::string& origin_identifier,
317    const string16& database_name) {
318  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
319  if (!database_connections_.IsDatabaseOpened(
320          origin_identifier, database_name)) {
321    RecordAction(UserMetricsAction("BadMessageTerminate_DBMF"));
322    BadMessageReceived();
323    return;
324  }
325
326  database_connections_.RemoveConnection(origin_identifier, database_name);
327  db_tracker_->DatabaseClosed(origin_identifier, database_name);
328}
329
330void DatabaseMessageFilter::OnHandleSqliteError(
331    const std::string& origin_identifier,
332    const string16& database_name,
333    int error) {
334  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
335  if (!DatabaseUtil::IsValidOriginIdentifier(origin_identifier)) {
336    RecordAction(UserMetricsAction("BadMessageTerminate_DBMF"));
337    BadMessageReceived();
338    return;
339  }
340
341  db_tracker_->HandleSqliteError(origin_identifier, database_name, error);
342}
343
344void DatabaseMessageFilter::OnDatabaseSizeChanged(
345    const std::string& origin_identifier,
346    const string16& database_name,
347    int64 database_size) {
348  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
349  if (database_connections_.IsOriginUsed(origin_identifier)) {
350    Send(new DatabaseMsg_UpdateSize(origin_identifier, database_name,
351                                    database_size));
352  }
353}
354
355void DatabaseMessageFilter::OnDatabaseScheduledForDeletion(
356    const std::string& origin_identifier,
357    const string16& database_name) {
358  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
359  Send(new DatabaseMsg_CloseImmediately(origin_identifier, database_name));
360}
361
362}  // namespace content
363