diff options
author | initial.commit <initial.commit@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-07-26 23:55:29 +0000 |
---|---|---|
committer | initial.commit <initial.commit@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-07-26 23:55:29 +0000 |
commit | 09911bf300f1a419907a9412154760efd0b7abc3 (patch) | |
tree | f131325fb4e2ad12c6d3504ab75b16dd92facfed /chrome/browser/webdata | |
parent | 586acc5fe142f498261f52c66862fa417c3d52d2 (diff) | |
download | chromium_src-09911bf300f1a419907a9412154760efd0b7abc3.zip chromium_src-09911bf300f1a419907a9412154760efd0b7abc3.tar.gz chromium_src-09911bf300f1a419907a9412154760efd0b7abc3.tar.bz2 |
Add chrome to the repository.
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@15 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/webdata')
-rw-r--r-- | chrome/browser/webdata/web_data_service.cc | 687 | ||||
-rw-r--r-- | chrome/browser/webdata/web_data_service.h | 508 | ||||
-rw-r--r-- | chrome/browser/webdata/web_database.cc | 873 | ||||
-rw-r--r-- | chrome/browser/webdata/web_database.h | 167 | ||||
-rw-r--r-- | chrome/browser/webdata/web_database_unittest.cc | 587 |
5 files changed, 2822 insertions, 0 deletions
diff --git a/chrome/browser/webdata/web_data_service.cc b/chrome/browser/webdata/web_data_service.cc new file mode 100644 index 0000000..350e80c --- /dev/null +++ b/chrome/browser/webdata/web_data_service.cc @@ -0,0 +1,687 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <windows.h> + +#include "chrome/browser/webdata/web_data_service.h" + +#include "base/file_util.h" +#include "base/path_service.h" +#include "base/scoped_ptr.h" +#include "chrome/browser/ie7_password.h" +#include "chrome/browser/template_url.h" +#include "chrome/common/chrome_constants.h" +#include "webkit/glue/password_form.h" + +//////////////////////////////////////////////////////////////////////////////// +// +// WebDataService implementation. +// +//////////////////////////////////////////////////////////////////////////////// + +WebDataService::WebDataService() : should_commit_(false), + next_request_handle_(1), + thread_(NULL), + db_(NULL) { +} + +WebDataService::~WebDataService() { + if (thread_) { + Shutdown(); + } +} + +bool WebDataService::Init(const std::wstring& profile_path) { + std::wstring path = profile_path; + file_util::AppendToPath(&path, chrome::kWebDataFilename); + return InitWithPath(path); +} + +bool WebDataService::InitWithPath(const std::wstring& path) { + thread_ = new Thread("Chrome_WebDataThread"); + + if (!thread_->Start()) { + delete thread_; + thread_ = NULL; + return false; + } + + ScheduleTask(NewRunnableMethod(this, + &WebDataService::InitializeDatabase, + path)); + return true; +} + +class ShutdownTask : public Task { + public: + explicit ShutdownTask(WebDataService* wds) : service_(wds) { + } + virtual void Run() { + service_->ShutdownDatabase(); + } + + private: + + WebDataService* service_; +}; + +void WebDataService::Shutdown() { + if (thread_) { + // We cannot use NewRunnableMethod() because this can be called from our + // destructor. NewRunnableMethod() would AddRef() this instance. + ScheduleTask(new ShutdownTask(this)); + + // The thread destructor sends a message to terminate the thread and waits + // until the thread has exited. + delete thread_; + thread_ = NULL; + } +} + +bool WebDataService::IsRunning() { + return thread_ != NULL; +} + +void WebDataService::ScheduleCommit() { + if (should_commit_ == false) { + should_commit_ = true; + ScheduleTask(NewRunnableMethod(this, &WebDataService::Commit)); + } +} + +void WebDataService::ScheduleTask(Task* t) { + if (thread_) + thread_->message_loop()->PostTask(FROM_HERE, t); + else + NOTREACHED() << "Task scheduled after Shutdown()"; +} + +void WebDataService::RegisterRequest(WebDataRequest* request) { + AutoLock l(pending_lock_); + pending_requests_[request->GetHandle()] = request; +} + +void WebDataService::CancelRequest(Handle h) { + AutoLock l(pending_lock_); + RequestMap::iterator i = pending_requests_.find(h); + if (i == pending_requests_.end()) { + NOTREACHED() << "Canceling a nonexistant web data service request"; + return; + } + i->second->Cancel(); +} + +void WebDataService::RequestCompleted(Handle h) { + pending_lock_.Acquire(); + RequestMap::iterator i = pending_requests_.find(h); + if (i == pending_requests_.end()) { + NOTREACHED() << "Request completed called for an unknown request"; + pending_lock_.Release(); + return; + } + + // Take ownership of the request object and remove it from the map. + scoped_ptr<WebDataRequest> request(i->second); + pending_requests_.erase(i); + pending_lock_.Release(); + + // Notify the consumer if needed. + WebDataServiceConsumer* consumer; + if (!request->IsCancelled() && (consumer = request->GetConsumer())) { + consumer->OnWebDataServiceRequestDone(request->GetHandle(), + request->GetResult()); + } +} + +////////////////////////////////////////////////////////////////////////////// +// +// Keywords. +// +////////////////////////////////////////////////////////////////////////////// + +void WebDataService::AddKeyword(const TemplateURL& url) { + GenericRequest<TemplateURL>* request = + new GenericRequest<TemplateURL>(this, GetNextRequestHandle(), NULL, url); + RegisterRequest(request); + ScheduleTask(NewRunnableMethod(this, &WebDataService::AddKeywordImpl, + request)); +} + +void WebDataService::RemoveKeyword(const TemplateURL& url) { + GenericRequest<TemplateURL::IDType>* request = + new GenericRequest<TemplateURL::IDType>(this, GetNextRequestHandle(), + NULL, url.id()); + RegisterRequest(request); + ScheduleTask( + NewRunnableMethod(this, &WebDataService::RemoveKeywordImpl, request)); +} + +void WebDataService::UpdateKeyword(const TemplateURL& url) { + GenericRequest<TemplateURL>* request = + new GenericRequest<TemplateURL>(this, GetNextRequestHandle(), NULL, url); + RegisterRequest(request); + ScheduleTask( + NewRunnableMethod(this, &WebDataService::UpdateKeywordImpl, request)); +} + +WebDataService::Handle WebDataService::GetKeywords( + WebDataServiceConsumer* consumer) { + WebDataRequest* request = + new WebDataRequest(this, GetNextRequestHandle(), consumer); + RegisterRequest(request); + ScheduleTask( + NewRunnableMethod(this, + &WebDataService::GetKeywordsImpl, + request)); + return request->GetHandle(); +} + +void WebDataService::SetDefaultSearchProvider(const TemplateURL* url) { + GenericRequest<TemplateURL::IDType>* request = + new GenericRequest<TemplateURL::IDType>(this, + GetNextRequestHandle(), + NULL, + url ? url->id() : 0); + RegisterRequest(request); + ScheduleTask( + NewRunnableMethod(this, &WebDataService::SetDefaultSearchProviderImpl, + request)); +} + +void WebDataService::SetBuiltinKeywordVersion(int version) { + GenericRequest<int>* request = + new GenericRequest<int>(this, GetNextRequestHandle(), NULL, version); + RegisterRequest(request); + ScheduleTask( + NewRunnableMethod(this, &WebDataService::SetBuiltinKeywordVersionImpl, + request)); +} + +////////////////////////////////////////////////////////////////////////////// +// +// Web Apps +// +////////////////////////////////////////////////////////////////////////////// + +void WebDataService::SetWebAppImage(const GURL& app_url, + const SkBitmap& image) { + GenericRequest2<GURL, SkBitmap>* request = + new GenericRequest2<GURL, SkBitmap>(this, GetNextRequestHandle(), + NULL, app_url, image); + RegisterRequest(request); + ScheduleTask(NewRunnableMethod(this, &WebDataService::SetWebAppImageImpl, + request)); +} + +void WebDataService::SetWebAppHasAllImages(const GURL& app_url, + bool has_all_images) { + GenericRequest2<GURL, bool>* request = + new GenericRequest2<GURL, bool>(this, GetNextRequestHandle(), + NULL, app_url, has_all_images); + RegisterRequest(request); + ScheduleTask(NewRunnableMethod(this, + &WebDataService::SetWebAppHasAllImagesImpl, + request)); +} + +void WebDataService::RemoveWebApp(const GURL& app_url) { + GenericRequest<GURL>* request = + new GenericRequest<GURL>(this, GetNextRequestHandle(), NULL, app_url); + RegisterRequest(request); + ScheduleTask(NewRunnableMethod(this, &WebDataService::RemoveWebAppImpl, + request)); +} + +WebDataService::Handle WebDataService::GetWebAppImages( + const GURL& app_url, + WebDataServiceConsumer* consumer) { + GenericRequest<GURL>* request = + new GenericRequest<GURL>(this, GetNextRequestHandle(), consumer, app_url); + RegisterRequest(request); + ScheduleTask(NewRunnableMethod(this, &WebDataService::GetWebAppImagesImpl, + request)); + return request->GetHandle(); +} + +//////////////////////////////////////////////////////////////////////////////// +// +// Password manager. +// +//////////////////////////////////////////////////////////////////////////////// + +void WebDataService::AddLogin(const PasswordForm& form) { + GenericRequest<PasswordForm>* request = + new GenericRequest<PasswordForm>(this, GetNextRequestHandle(), NULL, + form); + RegisterRequest(request); + ScheduleTask(NewRunnableMethod(this, &WebDataService::AddLoginImpl, + request)); +} + +void WebDataService::AddIE7Login(const IE7PasswordInfo& info) { + GenericRequest<IE7PasswordInfo>* request = + new GenericRequest<IE7PasswordInfo>(this, GetNextRequestHandle(), NULL, + info); + RegisterRequest(request); + ScheduleTask(NewRunnableMethod(this, &WebDataService::AddIE7LoginImpl, + request)); +} + +void WebDataService::UpdateLogin(const PasswordForm& form) { + GenericRequest<PasswordForm>* request = + new GenericRequest<PasswordForm>(this, GetNextRequestHandle(), + NULL, form); + RegisterRequest(request); + ScheduleTask(NewRunnableMethod(this, &WebDataService::UpdateLoginImpl, + request)); +} + +void WebDataService::RemoveLogin(const PasswordForm& form) { + GenericRequest<PasswordForm>* request = + new GenericRequest<PasswordForm>(this, GetNextRequestHandle(), NULL, + form); + RegisterRequest(request); + ScheduleTask(NewRunnableMethod(this, &WebDataService::RemoveLoginImpl, + request)); +} + +void WebDataService::RemoveIE7Login(const IE7PasswordInfo& info) { + GenericRequest<IE7PasswordInfo>* request = + new GenericRequest<IE7PasswordInfo>(this, GetNextRequestHandle(), NULL, + info); + RegisterRequest(request); + ScheduleTask(NewRunnableMethod(this, &WebDataService::RemoveIE7LoginImpl, + request)); +} + +void WebDataService::RemoveLoginsCreatedBetween(const Time delete_begin, + const Time delete_end) { + GenericRequest2<Time, Time>* request = + new GenericRequest2<Time, Time>(this, + GetNextRequestHandle(), + NULL, + delete_begin, + delete_end); + RegisterRequest(request); + ScheduleTask(NewRunnableMethod(this, + &WebDataService::RemoveLoginsCreatedBetweenImpl, request)); +} + +void WebDataService::RemoveLoginsCreatedAfter(const Time delete_begin) { + RemoveLoginsCreatedBetween(delete_begin, Time()); +} + +WebDataService::Handle WebDataService::GetLogins( + const PasswordForm& form, + WebDataServiceConsumer* consumer) { + GenericRequest<PasswordForm>* request = + new GenericRequest<PasswordForm>(this, GetNextRequestHandle(), + consumer, form); + RegisterRequest(request); + ScheduleTask(NewRunnableMethod(this, &WebDataService::GetLoginsImpl, + request)); + return request->GetHandle(); +} + +WebDataService::Handle WebDataService::GetIE7Login( + const IE7PasswordInfo& info, + WebDataServiceConsumer* consumer) { + GenericRequest<IE7PasswordInfo>* request = + new GenericRequest<IE7PasswordInfo>(this, GetNextRequestHandle(), + consumer, info); + RegisterRequest(request); + ScheduleTask(NewRunnableMethod(this, &WebDataService::GetIE7LoginImpl, + request)); + return request->GetHandle(); +} + +WebDataService::Handle WebDataService::GetAllAutofillableLogins( + WebDataServiceConsumer* consumer) { + WebDataRequest* request = + new WebDataRequest(this, GetNextRequestHandle(), consumer); + RegisterRequest(request); + ScheduleTask(NewRunnableMethod(this, + &WebDataService::GetAllAutofillableLoginsImpl, + request)); + return request->GetHandle(); +} + +WebDataService::Handle WebDataService::GetAllLogins( + WebDataServiceConsumer* consumer) { + WebDataRequest* request = + new WebDataRequest(this, GetNextRequestHandle(), consumer); + RegisterRequest(request); + ScheduleTask(NewRunnableMethod(this, &WebDataService::GetAllLoginsImpl, + request)); + return request->GetHandle(); +} + +//////////////////////////////////////////////////////////////////////////////// +// +// The following methods are executed in Chrome_WebDataThread. +// +//////////////////////////////////////////////////////////////////////////////// + +void WebDataService::Commit() { + if (should_commit_) { + should_commit_ = false; + + if (db_) { + db_->CommitTransaction(); + db_->BeginTransaction(); + } + } +} + +void WebDataService::InitializeDatabase(const std::wstring& path) { + DCHECK(!db_); + // In the rare case where the db fails to initialize a dialog may get shown + // the blocks the caller, yet allows other messages through. For this reason + // we only set db_ to the created database if creation is successful. That + // way other methods won't do anything as db_ is still NULL. + WebDatabase* db = new WebDatabase(); + if (!db->Init(path)) { + NOTREACHED() << "Cannot initialize the web database"; + delete db; + return; + } + + db_ = db; + + db_->BeginTransaction(); +} + +void WebDataService::ShutdownDatabase() { + if (db_) { + db_->CommitTransaction(); + delete db_; + db_ = NULL; + } +} + +// +// Keywords. +// +void WebDataService::AddKeywordImpl(GenericRequest<TemplateURL>* request) { + if (db_ && !request->IsCancelled()) { + db_->AddKeyword(request->GetArgument()); + ScheduleCommit(); + } + request->RequestComplete(); +} + +void WebDataService::RemoveKeywordImpl( + GenericRequest<TemplateURL::IDType>* request) { + if (db_ && !request->IsCancelled()) { + DCHECK(request->GetArgument()); + db_->RemoveKeyword(request->GetArgument()); + ScheduleCommit(); + } + request->RequestComplete(); +} + +void WebDataService::UpdateKeywordImpl( + GenericRequest<TemplateURL>* request) { + if (db_ && !request->IsCancelled()) { + if (!db_->UpdateKeyword(request->GetArgument())) + NOTREACHED(); + ScheduleCommit(); + } + request->RequestComplete(); +} + +void WebDataService::GetKeywordsImpl(WebDataRequest* request) { + if (db_ && !request->IsCancelled()) { + WDKeywordsResult result; + db_->GetKeywords(&result.keywords); + result.default_search_provider_id = db_->GetDefaulSearchProviderID(); + result.builtin_keyword_version = db_->GetBuitinKeywordVersion(); + request->SetResult( + new WDResult<WDKeywordsResult>(KEYWORDS_RESULT, result)); + } + request->RequestComplete(); +} + +void WebDataService::SetDefaultSearchProviderImpl( + GenericRequest<TemplateURL::IDType>* request) { + if (db_ && !request->IsCancelled()) { + if (!db_->SetDefaultSearchProviderID(request->GetArgument())) + NOTREACHED(); + ScheduleCommit(); + } + request->RequestComplete(); +} + +void WebDataService::SetBuiltinKeywordVersionImpl( + GenericRequest<int>* request) { + if (db_ && !request->IsCancelled()) { + if (!db_->SetBuitinKeywordVersion(request->GetArgument())) + NOTREACHED(); + ScheduleCommit(); + } + request->RequestComplete(); +} + +// +// Password manager support. +// +void WebDataService::AddLoginImpl(GenericRequest<PasswordForm>* request) { + if (db_ && !request->IsCancelled()) { + if (db_->AddLogin(request->GetArgument())) + ScheduleCommit(); + } + request->RequestComplete(); +} + +void WebDataService::AddIE7LoginImpl(GenericRequest<IE7PasswordInfo>* request) { + if (db_ && !request->IsCancelled()) { + if (db_->AddIE7Login(request->GetArgument())) + ScheduleCommit(); + } + request->RequestComplete(); +} + +void WebDataService::UpdateLoginImpl(GenericRequest<PasswordForm>* request) { + if (db_ && !request->IsCancelled()) { + if (db_->UpdateLogin(request->GetArgument())) + ScheduleCommit(); + } + request->RequestComplete(); +} + +void WebDataService::RemoveLoginImpl(GenericRequest<PasswordForm>* request) { + if (db_ && !request->IsCancelled()) { + if (db_->RemoveLogin(request->GetArgument())) + ScheduleCommit(); + } + request->RequestComplete(); +} + +void WebDataService::RemoveIE7LoginImpl( + GenericRequest<IE7PasswordInfo>* request) { + if (db_ && !request->IsCancelled()) { + if (db_->RemoveIE7Login(request->GetArgument())) + ScheduleCommit(); + } + request->RequestComplete(); +} + +void WebDataService::RemoveLoginsCreatedBetweenImpl( + GenericRequest2<Time, Time>* request) { + if (db_ && !request->IsCancelled()) { + if (db_->RemoveLoginsCreatedBetween(request->GetArgument1(), + request->GetArgument2())) + ScheduleCommit(); + } + request->RequestComplete(); +} + +void WebDataService::GetLoginsImpl(GenericRequest<PasswordForm>* request) { + if (db_ && !request->IsCancelled()) { + std::vector<PasswordForm*> forms; + db_->GetLogins(request->GetArgument(), &forms); + request->SetResult( + new WDResult<std::vector<PasswordForm*> >(PASSWORD_RESULT, forms)); + } + request->RequestComplete(); +} + +void WebDataService::GetIE7LoginImpl( + GenericRequest<IE7PasswordInfo>* request) { + if (db_ && !request->IsCancelled()) { + IE7PasswordInfo result; + db_->GetIE7Login(request->GetArgument(), &result); + request->SetResult( + new WDResult<IE7PasswordInfo>(PASSWORD_IE7_RESULT, result)); + } + request->RequestComplete(); +} + +void WebDataService::GetAllAutofillableLoginsImpl(WebDataRequest* request) { + if (db_ && !request->IsCancelled()) { + std::vector<PasswordForm*> forms; + db_->GetAllLogins(&forms, false); + request->SetResult( + new WDResult<std::vector<PasswordForm*> >(PASSWORD_RESULT, forms)); + } + request->RequestComplete(); +} + +void WebDataService::GetAllLoginsImpl(WebDataRequest* request) { + if (db_ && !request->IsCancelled()) { + std::vector<PasswordForm*> forms; + db_->GetAllLogins(&forms, true); + request->SetResult( + new WDResult<std::vector<PasswordForm*> >(PASSWORD_RESULT, forms)); + } + request->RequestComplete(); +} + +//////////////////////////////////////////////////////////////////////////////// +// +// Web Apps implementation. +// +//////////////////////////////////////////////////////////////////////////////// + +void WebDataService::SetWebAppImageImpl( + GenericRequest2<GURL, SkBitmap>* request) { + if (db_ && !request->IsCancelled()) { + db_->SetWebAppImage(request->GetArgument1(), request->GetArgument2()); + ScheduleCommit(); + } + request->RequestComplete(); +} + +void WebDataService::SetWebAppHasAllImagesImpl( + GenericRequest2<GURL, bool>* request) { + if (db_ && !request->IsCancelled()) { + db_->SetWebAppHasAllImages(request->GetArgument1(), + request->GetArgument2()); + ScheduleCommit(); + } + request->RequestComplete(); +} + +void WebDataService::RemoveWebAppImpl(GenericRequest<GURL>* request) { + if (db_ && !request->IsCancelled()) { + db_->RemoveWebApp(request->GetArgument()); + ScheduleCommit(); + } + request->RequestComplete(); +} + +void WebDataService::GetWebAppImagesImpl(GenericRequest<GURL>* request) { + if (db_ && !request->IsCancelled()) { + WDAppImagesResult result; + result.has_all_images = db_->GetWebAppHasAllImages(request->GetArgument()); + db_->GetWebAppImages(request->GetArgument(), &result.images); + request->SetResult( + new WDResult<WDAppImagesResult>(WEB_APP_IMAGES, result)); + } + request->RequestComplete(); +} + +//////////////////////////////////////////////////////////////////////////////// +// +// WebDataRequest implementation. +// +//////////////////////////////////////////////////////////////////////////////// + +WebDataService::WebDataRequest::WebDataRequest(WebDataService* service, + Handle handle, + WebDataServiceConsumer* consumer) + : service_(service), + handle_(handle), + canceled_(false), + consumer_(consumer), + result_(NULL) { + message_loop_ = MessageLoop::current(); +} + +WebDataService::WebDataRequest::~WebDataRequest() { + delete result_; +} + +WebDataService::Handle WebDataService::WebDataRequest::GetHandle() const { + return handle_; +} + +WebDataServiceConsumer* WebDataService::WebDataRequest::GetConsumer() const { + return consumer_; +} + +bool WebDataService::WebDataRequest::IsCancelled() const { + return canceled_; +} + +void WebDataService::WebDataRequest::Cancel() { + canceled_ = true; + consumer_ = NULL; +} + +void WebDataService::WebDataRequest::SetResult(WDTypedResult* r) { + result_ = r; +} + +const WDTypedResult* WebDataService::WebDataRequest::GetResult() const { + return result_; +} + +void WebDataService::WebDataRequest::RequestComplete() { + WebDataService* s = service_; + Task* t = NewRunnableMethod(s, + &WebDataService::RequestCompleted, + handle_); + message_loop_->PostTask(FROM_HERE, t); +} + +int WebDataService::GetNextRequestHandle() { + AutoLock l(pending_lock_); + return ++next_request_handle_; +} diff --git a/chrome/browser/webdata/web_data_service.h b/chrome/browser/webdata/web_data_service.h new file mode 100644 index 0000000..a233338 --- /dev/null +++ b/chrome/browser/webdata/web_data_service.h @@ -0,0 +1,508 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_WEBDATA_WEB_DATA_SERVICE_H__ +#define CHROME_BROWSER_WEBDATA_WEB_DATA_SERVICE_H__ + +#include "base/basictypes.h" +#include "base/lock.h" +#include "base/message_loop.h" +#include "base/ref_counted.h" +#include "base/thread.h" +#include "chrome/browser/webdata/web_database.h" +#include "chrome/common/scoped_vector.h" +#include <map> + +class GURL; +struct PasswordForm; +struct IE7PasswordInfo; +class ShutdownTask; +class TemplateURL; + +//////////////////////////////////////////////////////////////////////////////// +// +// WebDataService is a generic data repository for meta data associated with +// web pages. All data is retrieved and archived in an asynchronous way. +// +// All requests return a handle. The handle can be used to cancel the request. +// +//////////////////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////////////////// +// +// WebDataService results +// +//////////////////////////////////////////////////////////////////////////////// + +// +// Result types +// +typedef enum { + BOOL_RESULT = 1, // WDResult<bool> + KEYWORDS_RESULT, // WDResult<WDKeywordsResult> + INT64_RESULT, // WDResult<int64> + PASSWORD_RESULT, // WDResult<std::vector<PasswordForm*>> + PASSWORD_IE7_RESULT, // WDResult<IE7PasswordInfo> + WEB_APP_IMAGES, // WDResult<WDAppImagesResult> +} WDResultType; + +// Result from GetWebAppImages. +struct WDAppImagesResult { + WDAppImagesResult() : has_all_images(false) {} + + // True if SetWebAppHasAllImages(true) was invoked. + bool has_all_images; + + // The images, may be empty. + std::vector<SkBitmap> images; +}; + +struct WDKeywordsResult { + std::vector<TemplateURL*> keywords; + // Identifies the ID of the TemplateURL that is the default search. A value of + // 0 indicates there is no default search provider. + int64 default_search_provider_id; + // Version of the builin keywords. A value of 0 indicates a first run. + int builtin_keyword_version; +}; + +// +// The top level class for a result. +// +class WDTypedResult { + public: + virtual ~WDTypedResult() {} + + // Return the result type. + WDResultType GetType() const { + return type_; + } + + protected: + WDTypedResult(WDResultType type) : type_(type) { + } + + private: + WDResultType type_; + DISALLOW_EVIL_CONSTRUCTORS(WDTypedResult); +}; + +// A result containing one specific pointer or literal value. +template <class T> class WDResult : public WDTypedResult { + public: + + WDResult(WDResultType type, T v) : WDTypedResult(type), value_(v) { + } + + virtual ~WDResult() { + } + + // Return a single value result. + T GetValue() const { + return value_; + } + + private: + T value_; + + DISALLOW_EVIL_CONSTRUCTORS(WDResult); +}; + +template <class T> class WDObjectResult : public WDTypedResult { + public: + explicit WDObjectResult(WDResultType type) : WDTypedResult(type) { + } + + T* GetValue() const { + return &value_; + } + + private: + // mutable to keep GetValue() const. + mutable T value_; + DISALLOW_EVIL_CONSTRUCTORS(WDObjectResult); +}; + +class WebDataServiceConsumer; + +class WebDataService : public base::RefCountedThreadSafe<WebDataService> { + public: + + // All requests return an opaque handle of the following type. + typedef int Handle; + + WebDataService(); + ~WebDataService(); + + // Initializes the web data service. Returns false on failure + // Takes the path of the profile directory as its argument. + bool Init(const std::wstring& profile_path); + + // Shutdown the web data service. The service can no longer be used after this + // call. + void Shutdown(); + + // Returns false if Shutdown() has been called. + bool IsRunning(); + + ////////////////////////////////////////////////////////////////////////////// + // + // Internal requests + // + // Every request is processed using a request object. The object contains + // both the request parameters and the results. + ////////////////////////////////////////////////////////////////////////////// + class WebDataRequest { + public: + WebDataRequest(WebDataService* service, + Handle handle, + WebDataServiceConsumer* consumer); + + virtual ~WebDataRequest(); + + Handle GetHandle() const; + WebDataServiceConsumer* GetConsumer() const; + bool IsCancelled() const; + + // This can be invoked from any thread. From this point we assume that + // our consumer_ reference is invalid. + void Cancel(); + + // Invoked by the service when this request has been completed. + // This will notify the service in whatever thread was used to create this + // request. + void RequestComplete(); + + // The result is owned by the request. + void SetResult(WDTypedResult* r); + const WDTypedResult* GetResult() const; + + private: + scoped_refptr<WebDataService> service_; + MessageLoop* message_loop_; + Handle handle_; + bool canceled_; + WebDataServiceConsumer* consumer_; + WDTypedResult* result_; + + DISALLOW_EVIL_CONSTRUCTORS(WebDataRequest); + }; + + // + // Internally we use instances of the following template to represent + // requests. + // + template <class T> + class GenericRequest : public WebDataRequest { + public: + GenericRequest(WebDataService* service, + Handle handle, + WebDataServiceConsumer* consumer, + T arg) : WebDataRequest(service, handle, consumer), + arg_(arg) { + } + + virtual ~GenericRequest() { + } + + T GetArgument() { + return arg_; + } + + private: + T arg_; + }; + + template <class T, class U> + class GenericRequest2 : public WebDataRequest { + public: + GenericRequest2(WebDataService* service, + Handle handle, + WebDataServiceConsumer* consumer, + T arg1, + U arg2) + : WebDataRequest(service, handle, consumer), + arg1_(arg1), + arg2_(arg2) { + } + + virtual ~GenericRequest2() { } + + T GetArgument1() { + return arg1_; + } + + U GetArgument2() { + return arg2_; + } + + private: + T arg1_; + U arg2_; + }; + + ////////////////////////////////////////////////////////////////////////////// + // + // Keywords + // + ////////////////////////////////////////////////////////////////////////////// + + // As the database processes requests at a later date, all deletion is + // done on the background thread. + // + // Many of the keyword related methods do not return a handle. This is because + // the caller (TemplateURLModel) does not need to know when the request is + // done. + void AddKeyword(const TemplateURL& url); + + void RemoveKeyword(const TemplateURL& url); + + void UpdateKeyword(const TemplateURL& url); + + // Fetches the keywords. + // On success, consumer is notified with WDResult<std::vector<TemplateURL*>. + Handle GetKeywords(WebDataServiceConsumer* consumer); + + // Sets the keywords used for the default search provider. + void SetDefaultSearchProvider(const TemplateURL* url); + + // Sets the version of the builtin keywords. + void SetBuiltinKeywordVersion(int version); + + ////////////////////////////////////////////////////////////////////////////// + // + // Web Apps + // + ////////////////////////////////////////////////////////////////////////////// + + // Sets the image for the specified web app. A web app can have any number of + // images, but only one at a particular size. If there was an image for the + // web app at the size of the given image it is replaced. + void SetWebAppImage(const GURL& app_url, const SkBitmap& image); + + // Sets whether all the images have been downloaded for the specified web app. + void SetWebAppHasAllImages(const GURL& app_url, bool has_all_images); + + // Removes all images for the specified web app. + void RemoveWebApp(const GURL& app_url); + + // Fetches the images and whether all images have been downloaded for the + // specified web app. + Handle GetWebAppImages(const GURL& app_url, WebDataServiceConsumer* consumer); + + ////////////////////////////////////////////////////////////////////////////// + // + // Password manager + // + ////////////////////////////////////////////////////////////////////////////// + + // Updates the remembered password form. + void UpdateLogin(const PasswordForm& form); + + // Adds |form| to the list of remembered password forms. + void AddLogin(const PasswordForm& form); + + // Adds |info| to the list of imported passwords from ie7/ie8. + void AddIE7Login(const IE7PasswordInfo& info); + + // Removes |form| from the list of remembered password forms. + void RemoveLogin(const PasswordForm& form); + + // Removes |info| from the list of imported passwords from ie7/ie8. + void RemoveIE7Login(const IE7PasswordInfo& info); + + // Removes all logins created in the specified daterange + void RemoveLoginsCreatedBetween(const Time delete_begin, + const Time delete_end); + + // Removes all logins created on or after the date passed in. + void RemoveLoginsCreatedAfter(const Time delete_begin); + + // Gets a list of password forms that match |form|. + // |consumer| will be notified when the request is done. The result is of + // type WDResult<std::vector<PasswordForm*>>. + // The result will be null on failure. The |consumer| owns all PasswordForm's. + Handle GetLogins(const PasswordForm& form, WebDataServiceConsumer* consumer); + + // Get the login matching the information in |info|. |consumer| will be + // notified when the request is done. The result is of type + // WDResult<IE7PasswordInfo>. + // If there is no match, the fields of the IE7PasswordInfo will be empty. + Handle GetIE7Login(const IE7PasswordInfo& info, + WebDataServiceConsumer* consumer); + + // Gets the complete list of password forms that have not been blacklisted and + // are thus auto-fillable. + // |consumer| will be notified when the request is done. The result is of + // type WDResult<std::vector<PasswordForm*>>. + // The result will be null on failure. The |consumer| owns all PasswordForms. + Handle GetAllAutofillableLogins(WebDataServiceConsumer* consumer); + + // Gets the complete list of password forms. + // |consumer| will be notified when the request is done. The result is of + // type WDResult<std::vector<PasswordForm*>>. + // The result will be null on failure. The |consumer| owns all PasswordForm's. + Handle GetAllLogins(WebDataServiceConsumer* consumer); + + // Cancel any pending request. You need to call this method if your + // WebDataServiceConsumer is about to be deleted. + void CancelRequest(Handle h); + + protected: + friend class TemplateURLModelTest; + friend class TemplateURLModelTestingProfile; + friend class WebDataServiceTest; + friend class WebDataRequest; + + // This is invoked by the unit test; path is the path of the Web Data file. + bool WebDataService::InitWithPath(const std::wstring& path); + + // Invoked by request implementations when a request has been processed. + void RequestCompleted(Handle h); + + // Register the request as a pending request. + void RegisterRequest(WebDataRequest* request); + + ////////////////////////////////////////////////////////////////////////////// + // + // The following methods are only invoked in the web data service thread. + // + ////////////////////////////////////////////////////////////////////////////// + private: + friend class ShutdownTask; + + typedef GenericRequest2<std::vector<const TemplateURL*>, + std::vector<TemplateURL*>> SetKeywordsRequest; + + // Initialize the database with the provided path. + void InitializeDatabase(const std::wstring& path); + + // Commit any pending transaction and deletes the database. + void WebDataService::ShutdownDatabase(); + + // Commit the current transaction and creates a new one. + void Commit(); + + ////////////////////////////////////////////////////////////////////////////// + // + // Keywords. + // + ////////////////////////////////////////////////////////////////////////////// + void AddKeywordImpl(GenericRequest<TemplateURL>* request); + void RemoveKeywordImpl(GenericRequest<TemplateURL::IDType>* request); + void UpdateKeywordImpl(GenericRequest<TemplateURL>* request); + void GetKeywordsImpl(WebDataRequest* request); + void SetDefaultSearchProviderImpl(GenericRequest<TemplateURL::IDType>* r); + void SetBuiltinKeywordVersionImpl(GenericRequest<int>* r); + + ////////////////////////////////////////////////////////////////////////////// + // + // Password manager. + // + ////////////////////////////////////////////////////////////////////////////// + void AddLoginImpl(GenericRequest<PasswordForm>* request); + void AddIE7LoginImpl(GenericRequest<IE7PasswordInfo>* request); + void UpdateLoginImpl(GenericRequest<PasswordForm>* request); + void RemoveLoginImpl(GenericRequest<PasswordForm>* request); + void RemoveIE7LoginImpl(GenericRequest<IE7PasswordInfo>* request); + void RemoveLoginsCreatedBetweenImpl(GenericRequest2<Time, Time>* request); + void GetLoginsImpl(GenericRequest<PasswordForm>* request); + void GetIE7LoginImpl(GenericRequest<IE7PasswordInfo>* request); + void GetAllAutofillableLoginsImpl(WebDataRequest* request); + void GetAllLoginsImpl(WebDataRequest* request); + + ////////////////////////////////////////////////////////////////////////////// + // + // Web Apps. + // + ////////////////////////////////////////////////////////////////////////////// + + void SetWebAppImageImpl(GenericRequest2<GURL,SkBitmap>* request); + + void SetWebAppHasAllImagesImpl(GenericRequest2<GURL,bool>* request); + + void RemoveWebAppImpl(GenericRequest<GURL>* request); + + void GetWebAppImagesImpl(GenericRequest<GURL>* request); + + Thread* thread() { return thread_; } + + private: + + // Schedule a task on our worker thread. + void ScheduleTask(Task* t); + + // Schedule a commit if one is not already pending. + void ScheduleCommit(); + + // Return the next request handle. + int GetNextRequestHandle(); + + // Our worker thread. All requests are processed from that thread. + Thread* thread_; + + // Our database. + WebDatabase* db_; + + // Whether we should commit the database. + bool should_commit_; + + // A lock to protect pending requests and next request handle. + Lock pending_lock_; + + // Next handle to be used for requests. Incremented for each use. + Handle next_request_handle_; + + typedef std::map<Handle, WebDataRequest*> RequestMap; + RequestMap pending_requests_; + + DISALLOW_EVIL_CONSTRUCTORS(WebDataService); +}; + +//////////////////////////////////////////////////////////////////////////////// +// +// WebDataServiceConsumer. +// +// All requests to the web data service are asynchronous. When the request has +// been performed, the data consumer is notified using the following interface. +// +//////////////////////////////////////////////////////////////////////////////// + +class WebDataServiceConsumer { + public: + + // Called when a request is done. h uniquely identifies the request. + // result can be NULL, if no result is expected or if the database could + // not be opened. The result object is destroyed after this call. + virtual void OnWebDataServiceRequestDone(WebDataService::Handle h, + const WDTypedResult* result) = 0; +}; + +#endif // CHROME_BROWSER_WEBDATA_WEB_DATA_SERVICE_H__ diff --git a/chrome/browser/webdata/web_database.cc b/chrome/browser/webdata/web_database.cc new file mode 100644 index 0000000..60b6b82 --- /dev/null +++ b/chrome/browser/webdata/web_database.cc @@ -0,0 +1,873 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <algorithm> +#include <limits> + +#include "chrome/browser/webdata/web_database.h" + +#include "base/gfx/png_decoder.h" +#include "base/gfx/png_encoder.h" +#include "base/string_util.h" +#include "base/time.h" +#include "base/values.h" +#include "chrome/browser/history/history_database.h" +#include "chrome/browser/ie7_password.h" +#include "chrome/browser/template_url.h" +#include "chrome/browser/encryptor.h" +#include "chrome/common/scoped_vector.h" +#include "webkit/glue/password_form.h" + +//////////////////////////////////////////////////////////////////////////////// +// +// Schema +// +// keywords Most of the columns mirror that of a field in +// TemplateURL. See TemplateURL for more details. +// id +// short_name +// keyword +// favicon_url +// url +// show_in_default_list +// safe_for_autoreplace +// originating_url +// date_created This column was added after we allowed keywords. +// Keywords created before we started tracking +// creation date have a value of 0 for this. +// usage_count +// input_encodings Semicolon separated list of supported input +// encodings, may be empty. +// suggest_url +// prepopulate_id See TemplateURL::prepoulate_id. +// +// logins +// origin_url +// action_url +// username_element +// username_value +// password_element +// password_value +// submit_element +// signon_realm The authority (scheme, host, port). +// ssl_valid SSL status of page containing the form at first +// impression. +// preferred MRU bit. +// date_created This column was added after logins support. "Legacy" +// entries have a value of 0. +// blacklisted_by_user Tracks whether or not the user opted to 'never +// remember' +// passwords for this site. +// +// web_app_icons +// url URL of the web app. +// width Width of the image. +// height Height of the image. +// image PNG encoded image data. +// +// web_apps +// url URL of the web app. +// has_all_images Do we have all the images? +// +//////////////////////////////////////////////////////////////////////////////// + +// Current version number. +static const int kCurrentVersionNumber = 20; + +// Keys used in the meta table. +static const char* kDefaultSearchProviderKey = "Default Search Provider ID"; +static const char* kBuiltinKeywordVersion = "Builtin Keyword Version"; + +std::string JoinStrings(const std::string& separator, + const std::vector<std::string>& strings) { + if (strings.empty()) + return std::string(); + std::vector<std::string>::const_iterator i(strings.begin()); + std::string result(*i); + while(++i != strings.end()) + result += separator + *i; + return result; +} + +WebDatabase::WebDatabase() : db_(NULL), transaction_nesting_(0) { +} + +WebDatabase::~WebDatabase() { + if (db_) { + DCHECK(transaction_nesting_ == 0) << + "Forgot to close the transaction on shutdown"; + sqlite3_close(db_); + db_ = NULL; + } +} + +void WebDatabase::BeginTransaction() { + DCHECK(db_); + if (transaction_nesting_ == 0) { + int rv = sqlite3_exec(db_, "BEGIN TRANSACTION", NULL, NULL, NULL); + DCHECK(rv == SQLITE_OK) << "Failed to begin transaction"; + } + transaction_nesting_++; +} + +void WebDatabase::CommitTransaction() { + DCHECK(db_); + DCHECK(transaction_nesting_ > 0) << "Committing too many transaction"; + transaction_nesting_--; + if (transaction_nesting_ == 0) { + int rv = sqlite3_exec(db_, "COMMIT", NULL, NULL, NULL); + DCHECK(rv == SQLITE_OK) << "Failed to commit transaction"; + } +} + +bool WebDatabase::Init(const std::wstring& db_name) { + // Open the database, using the narrow version of open so that + // the DB is in UTF-8. + if (sqlite3_open(WideToUTF8(db_name).c_str(), &db_) != SQLITE_OK) { + LOG(WARNING) << "Unable to open the web database."; + return false; + } + + // We don't store that much data in the tables so use a small page size. + // This provides a large benefit for empty tables (which is very likely with + // the tables we create). + sqlite3_exec(db_, "PRAGMA page_size=2048", NULL, NULL, NULL); + + // We shouldn't have much data and what access we currently have is quite + // infrequent. So we go with a small cache size. + sqlite3_exec(db_, "PRAGMA cache_size=32", NULL, NULL, NULL); + + // Run the database in exclusive mode. Nobody else should be accessing the + // database while we're running, and this will give somewhat improved perf. + sqlite3_exec(db_, "PRAGMA locking_mode=EXCLUSIVE", NULL, NULL, NULL); + + // Initialize various tables + SQLTransaction transaction(db_); + transaction.Begin(); + + // Version check. + if (!meta_table_.Init(std::string(), kCurrentVersionNumber, db_)) + return false; + if (meta_table_.GetCompatibleVersionNumber() > kCurrentVersionNumber) { + LOG(WARNING) << "Web database is too new"; + return false; + } + + // Initialize the tables. + if (!InitKeywordsTable() || !InitLoginsTable() || !InitWebAppIconsTable() || + !InitWebAppsTable()) { + LOG(WARNING) << "Unable to initialize the web database."; + return false; + } + int cur_version = meta_table_.GetVersionNumber(); + + // Put migration code here. + + // When the version is too old, we just try to continue anyway, there should + // not be a released product that makes a database too old for us to handle. + LOG_IF(WARNING, cur_version < kCurrentVersionNumber) << + "Web database version " << cur_version << " is too old to handle."; + + return (transaction.Commit() == SQLITE_OK); +} + +bool WebDatabase::SetWebAppImage(const GURL& url, + const SkBitmap& image) { + SQLStatement s; + if (s.prepare(db_, + "INSERT OR REPLACE INTO web_app_icons " + "(url, width, height, image) VALUES (?, ?, ?, ?)") + != SQLITE_OK) { + NOTREACHED() << "Statement prepare failed"; + return false; + } + + std::vector<unsigned char> image_data; + + SkAutoLockPixels pixel_lock(image); + PNGEncoder::Encode(reinterpret_cast<unsigned char*>(image.getPixels()), + PNGEncoder::FORMAT_BGRA, image.width(), + image.height(), image.width() * 4, false, &image_data); + + s.bind_string(0, history::HistoryDatabase::GURLToDatabaseURL(url)); + s.bind_int(1, image.width()); + s.bind_int(2, image.height()); + s.bind_blob(3, &image_data.front(), static_cast<int>(image_data.size())); + return s.step() == SQLITE_DONE; +} + +bool WebDatabase::GetWebAppImages(const GURL& url, + std::vector<SkBitmap>* images) { + SQLStatement s; + if (s.prepare(db_, "SELECT image FROM web_app_icons WHERE url=?") != + SQLITE_OK) { + NOTREACHED() << "Statement prepare failed"; + return false; + } + s.bind_string(0, history::HistoryDatabase::GURLToDatabaseURL(url)); + while (s.step() == SQLITE_ROW) { + SkBitmap image; + std::vector<unsigned char> image_data; + s.column_blob_as_vector(0, &image_data); + if (PNGDecoder::Decode(&image_data, &image)) { + images->push_back(image); + } else { + // Should only have valid image data in the db. + NOTREACHED(); + } + } + return true; +} + +bool WebDatabase::SetWebAppHasAllImages(const GURL& url, + bool has_all_images) { + SQLStatement s; + if (s.prepare(db_, "INSERT OR REPLACE INTO web_apps (url, has_all_images) " + "VALUES (?, ?)") != SQLITE_OK) { + NOTREACHED() << "Statement prepare failed"; + return false; + } + s.bind_string(0, history::HistoryDatabase::GURLToDatabaseURL(url)); + s.bind_int(1, has_all_images ? 1 : 0); + return (s.step() == SQLITE_DONE); +} + +bool WebDatabase::GetWebAppHasAllImages(const GURL& url) { + SQLStatement s; + if (s.prepare(db_, "SELECT has_all_images FROM web_apps " + "WHERE url=?") != SQLITE_OK) { + NOTREACHED() << "Statement prepare failed"; + return false; + } + s.bind_string(0, history::HistoryDatabase::GURLToDatabaseURL(url)); + return (s.step() == SQLITE_ROW && s.column_int(0) == 1); +} + +bool WebDatabase::RemoveWebApp(const GURL& url) { + SQLStatement delete_s; + if (delete_s.prepare(db_, "DELETE FROM web_app_icons WHERE url = ?") != + SQLITE_OK) { + NOTREACHED() << "Statement prepare failed"; + return false; + } + delete_s.bind_string(0, history::HistoryDatabase::GURLToDatabaseURL(url)); + if (delete_s.step() != SQLITE_DONE) + return false; + + SQLStatement delete_s2; + if (delete_s2.prepare(db_, "DELETE FROM web_apps WHERE url = ?") != + SQLITE_OK) { + NOTREACHED() << "Statement prepare failed"; + return false; + } + delete_s2.bind_string(0, history::HistoryDatabase::GURLToDatabaseURL(url)); + return (delete_s2.step() == SQLITE_DONE); +} + +bool WebDatabase::InitKeywordsTable() { + if (!DoesSqliteTableExist(db_, "keywords")) { + if (sqlite3_exec(db_, "CREATE TABLE keywords (" + "id INTEGER PRIMARY KEY," + "short_name VARCHAR NOT NULL," + "keyword VARCHAR NOT NULL," + "favicon_url VARCHAR NOT NULL," + "url VARCHAR NOT NULL," + "show_in_default_list INTEGER," + "safe_for_autoreplace INTEGER," + "originating_url VARCHAR," + "date_created INTEGER DEFAULT 0," + "usage_count INTEGER DEFAULT 0," + "input_encodings VARCHAR," + "suggest_url VARCHAR," + "prepopulate_id INTEGER DEFAULT 0)", + NULL, NULL, NULL) != SQLITE_OK) { + NOTREACHED(); + return false; + } + } + return true; +} + +bool WebDatabase::InitLoginsTable() { + if (!DoesSqliteTableExist(db_, "logins")) { + // First time + if (sqlite3_exec(db_, "CREATE TABLE logins (" + "origin_url VARCHAR NOT NULL, " + "action_url VARCHAR, " + "username_element VARCHAR, " + "username_value VARCHAR, " + "password_element VARCHAR, " + "password_value BLOB, " + "submit_element VARCHAR, " + "signon_realm VARCHAR NOT NULL," + "ssl_valid INTEGER NOT NULL," + "preferred INTEGER NOT NULL," + "date_created INTEGER NOT NULL," + "blacklisted_by_user INTEGER NOT NULL," + "scheme INTEGER NOT NULL," + "UNIQUE " + "(origin_url, username_element, " + "username_value, password_element, " + "submit_element, signon_realm))", + NULL, NULL, NULL) != SQLITE_OK) { + NOTREACHED(); + return false; + } + if (sqlite3_exec(db_, "CREATE INDEX logins_signon ON " + "logins (signon_realm)", + NULL, NULL, NULL) != SQLITE_OK) { + NOTREACHED(); + return false; + } + } + + if (!DoesSqliteTableExist(db_, "ie7_logins")) { + // First time + if (sqlite3_exec(db_, "CREATE TABLE ie7_logins (" + "url_hash VARCHAR NOT NULL, " + "password_value BLOB, " + "date_created INTEGER NOT NULL," + "UNIQUE " + "(url_hash))", + NULL, NULL, NULL) != SQLITE_OK) { + NOTREACHED(); + return false; + } + if (sqlite3_exec(db_, "CREATE INDEX ie7_logins_hash ON " + "ie7_logins (url_hash)", + NULL, NULL, NULL) != SQLITE_OK) { + NOTREACHED(); + return false; + } + } + return true; +} + +bool WebDatabase::InitWebAppIconsTable() { + if (!DoesSqliteTableExist(db_, "web_app_icons")) { + if (sqlite3_exec(db_, "CREATE TABLE web_app_icons (" + "url LONGVARCHAR," + "width int," + "height int," + "image BLOB, UNIQUE (url, width, height))", + NULL, NULL, NULL) != SQLITE_OK) { + NOTREACHED(); + return false; + } + } + return true; +} + +bool WebDatabase::InitWebAppsTable() { + if (!DoesSqliteTableExist(db_, "web_apps")) { + if (sqlite3_exec(db_, "CREATE TABLE web_apps (" + "url LONGVARCHAR UNIQUE," + "has_all_images INTEGER NOT NULL)", + NULL, NULL, NULL) != SQLITE_OK) { + NOTREACHED(); + return false; + } + if (sqlite3_exec(db_, "CREATE INDEX web_apps_url_index ON " + "web_apps (url)", NULL, NULL, NULL) != SQLITE_OK) { + NOTREACHED(); + return false; + } + } + return true; +} + +static void BindURLToStatement(const TemplateURL& url, SQLStatement* s) { + s->bind_wstring(0, url.short_name()); + s->bind_wstring(1, url.keyword()); + GURL favicon_url = url.GetFavIconURL(); + if (!favicon_url.is_valid()) { + s->bind_string(2, ""); + } else { + s->bind_string(2, history::HistoryDatabase::GURLToDatabaseURL( + url.GetFavIconURL())); + } + if (url.url()) + s->bind_wstring(3, url.url()->url()); + else + s->bind_wstring(3, std::wstring()); + s->bind_int(4, url.safe_for_autoreplace() ? 1 : 0); + if (!url.originating_url().is_valid()) { + s->bind_string(5, std::string()); + } else { + s->bind_string(5, history::HistoryDatabase::GURLToDatabaseURL( + url.originating_url())); + } + s->bind_int64(6, url.date_created().ToTimeT()); + s->bind_int(7, url.usage_count()); + s->bind_string(8, JoinStrings(";", url.input_encodings())); + s->bind_int(9, url.show_in_default_list() ? 1 : 0); + if (url.suggestions_url()) + s->bind_wstring(10, url.suggestions_url()->url()); + else + s->bind_wstring(10, std::wstring()); + s->bind_int(11, url.prepopulate_id()); +} + +bool WebDatabase::AddKeyword(const TemplateURL& url) { + DCHECK(url.id()); + SQLStatement s; + if (s.prepare(db_, + "INSERT INTO keywords " + "(short_name, keyword, favicon_url, url, safe_for_autoreplace, " + "originating_url, date_created, usage_count, input_encodings, " + "show_in_default_list, suggest_url, prepopulate_id, id) " + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)") + != SQLITE_OK) { + NOTREACHED() << "Statement prepare failed"; + return false; + } + BindURLToStatement(url, &s); + s.bind_int64(12, url.id()); + if (s.step() != SQLITE_DONE) { + NOTREACHED(); + return false; + } + return true; +} + +bool WebDatabase::RemoveKeyword(TemplateURL::IDType id) { + DCHECK(id); + SQLStatement s; + if (s.prepare(db_, + "DELETE FROM keywords WHERE id = ?") != SQLITE_OK) { + NOTREACHED() << "Statement prepare failed"; + return false; + } + s.bind_int64(0, id); + return s.step() == SQLITE_DONE; +} + +bool WebDatabase::GetKeywords(std::vector<TemplateURL*>* urls) { + SQLStatement s; + if (s.prepare(db_, + "SELECT id, short_name, keyword, favicon_url, url, " + "safe_for_autoreplace, originating_url, date_created, " + "usage_count, input_encodings, show_in_default_list, " + "suggest_url, prepopulate_id " + "FROM keywords ORDER BY id ASC") != SQLITE_OK) { + NOTREACHED() << "Statement prepare failed"; + return false; + } + int64 result; + while ((result = s.step()) == SQLITE_ROW) { + TemplateURL* template_url = new TemplateURL(); + std::wstring tmp; + template_url->set_id(s.column_int64(0)); + + s.column_string16(1, &tmp); + DCHECK(!tmp.empty()); + template_url->set_short_name(tmp); + + s.column_string16(2, &tmp); + template_url->set_keyword(tmp); + + s.column_string16(3, &tmp); + if (!tmp.empty()) + template_url->SetFavIconURL(GURL(tmp)); + + s.column_string16(4, &tmp); + template_url->SetURL(tmp, 0, 0); + + template_url->set_safe_for_autoreplace(s.column_int(5) == 1); + + s.column_string16(6, &tmp); + if (!tmp.empty()) + template_url->set_originating_url(GURL(tmp)); + + template_url->set_date_created(Time::FromTimeT(s.column_int64(7))); + + template_url->set_usage_count(s.column_int(8)); + + std::vector<std::string> encodings; + SplitString(s.column_string(9), ';', &encodings); + template_url->set_input_encodings(encodings); + + template_url->set_show_in_default_list(s.column_int(10) == 1); + + s.column_string16(11, &tmp); + template_url->SetSuggestionsURL(tmp, 0, 0); + + template_url->set_prepopulate_id(s.column_int(12)); + + urls->push_back(template_url); + } + return result == SQLITE_DONE; +} + +bool WebDatabase::UpdateKeyword(const TemplateURL& url) { + DCHECK(url.id()); + SQLStatement s; + if (s.prepare(db_, + "UPDATE keywords " + "SET short_name=?, keyword=?, favicon_url=?, url=?," + "safe_for_autoreplace=?, originating_url=?, " + "date_created=?, usage_count=?, input_encodings=?, " + "show_in_default_list=?, suggest_url=?, prepopulate_id=? " + "WHERE id=?") + != SQLITE_OK) { + NOTREACHED() << "Statement prepare failed"; + return false; + } + BindURLToStatement(url, &s); + s.bind_int64(12, url.id()); + return s.step() == SQLITE_DONE; +} + +bool WebDatabase::SetDefaultSearchProviderID(int64 id) { + return meta_table_.SetValue(kDefaultSearchProviderKey, id); +} + +int64 WebDatabase::GetDefaulSearchProviderID() { + int64 value = 0; + meta_table_.GetValue(kDefaultSearchProviderKey, &value); + return value; +} + +bool WebDatabase::SetBuitinKeywordVersion(int version) { + return meta_table_.SetValue(kBuiltinKeywordVersion, version); +} + +int WebDatabase::GetBuitinKeywordVersion() { + int version = 0; + meta_table_.GetValue(kBuiltinKeywordVersion, &version); + return version; +} + +// Return a new GURL like url, but without any "#foo" bit on the end. +static GURL GURLWithoutRef(const GURL& url) { + url_canon::Replacements<char> replacements; + replacements.ClearRef(); + return url.ReplaceComponents(replacements); +} + +// Convert a list of GUIDs from the in-memory form to the form we keep in +// the database (tab-separated string). +static std::string SerializeGUIDs(const std::vector<std::string>& guids) { + std::string result; + for (size_t i = 0; i < guids.size(); ++i) { + if (!result.empty()) + result.push_back('\t'); + const std::string& guid = guids[i]; + for (size_t j = 0; j < guid.size(); ++j) { + char ch = guid[j]; + // If we have any embedded tabs in the GUID (a pathological case), + // we normalize them to spaces. + if (ch == '\t') + ch = ' '; + result.push_back(ch); + } + } + return result; +} + +// The partner of SerializeGUIDs. Converts a serialized GUIDs string +// back to a vector. +static void DeserializeGUIDs(const std::string& str, + std::vector<std::string>* guids) { + SplitString(str, '\t', guids); +} + +bool WebDatabase::AddLogin(const PasswordForm& form) { + SQLStatement s; + std::string encrypted_password; + if (s.prepare(db_, + "INSERT OR REPLACE INTO logins " + "(origin_url, action_url, username_element, username_value, " + " password_element, password_value, submit_element, " + " signon_realm, ssl_valid, preferred, date_created, " + " blacklisted_by_user, scheme) " + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)") != SQLITE_OK) { + NOTREACHED() << "Statement prepare failed"; + return false; + } + + s.bind_string(0, form.origin.spec()); + s.bind_string(1, form.action.spec()); + s.bind_wstring(2, form.username_element); + s.bind_wstring(3, form.username_value); + s.bind_wstring(4, form.password_element); + Encryptor::EncryptWideString(form.password_value, &encrypted_password); + s.bind_blob(5, encrypted_password.data(), + static_cast<int>(encrypted_password.length())); + s.bind_wstring(6, form.submit_element); + s.bind_string(7, form.signon_realm); + s.bind_int(8, form.ssl_valid); + s.bind_int(9, form.preferred); + s.bind_int64(10, form.date_created.ToTimeT()); + s.bind_int(11, form.blacklisted_by_user); + s.bind_int(12, form.scheme); + if (s.step() != SQLITE_DONE) { + NOTREACHED(); + return false; + } + return true; +} + +bool WebDatabase::AddIE7Login(const IE7PasswordInfo& info) { + SQLStatement s; + if (s.prepare(db_, + "INSERT OR REPLACE INTO ie7_logins " + "(url_hash, password_value, date_created) " + "VALUES (?, ?, ?)") != SQLITE_OK) { + NOTREACHED() << "Statement prepare failed"; + return false; + } + + s.bind_wstring(0, info.url_hash); + s.bind_blob(1, &info.encrypted_data.front(), + static_cast<int>(info.encrypted_data.size())); + s.bind_int64(2, info.date_created.ToTimeT()); + if (s.step() != SQLITE_DONE) { + NOTREACHED(); + return false; + } + return true; +} + +bool WebDatabase::UpdateLogin(const PasswordForm& form) { + SQLStatement s; + std::string encrypted_password; + if (s.prepare(db_, "UPDATE logins SET " + "action_url = ?, " + "password_value = ?, " + "ssl_valid = ?, " + "preferred = ? " + "WHERE origin_url = ? AND " + "username_element = ? AND " + "username_value = ? AND " + "password_element = ? AND " + "signon_realm = ?") != SQLITE_OK) { + NOTREACHED() << "Statement prepare failed"; + return false; + } + + s.bind_string(0, form.action.spec()); + Encryptor::EncryptWideString(form.password_value, &encrypted_password); + s.bind_blob(1, encrypted_password.data(), + static_cast<int>(encrypted_password.length())); + s.bind_int(2, form.ssl_valid); + s.bind_int(3, form.preferred); + s.bind_string(4, form.origin.spec()); + s.bind_wstring(5, form.username_element); + s.bind_wstring(6, form.username_value); + s.bind_wstring(7, form.password_element); + s.bind_string(8, form.signon_realm); + + if (s.step() != SQLITE_DONE) { + NOTREACHED(); + return false; + } + return true; +} + +bool WebDatabase::RemoveLogin(const PasswordForm& form) { + SQLStatement s; + // Remove a login by UNIQUE-constrained fields. + if (s.prepare(db_, + "DELETE FROM logins WHERE " + "origin_url = ? AND " + "username_element = ? AND " + "username_value = ? AND " + "password_element = ? AND " + "submit_element = ? AND " + "signon_realm = ? ") != SQLITE_OK) { + NOTREACHED() << "Statement prepare failed"; + return false; + } + s.bind_string(0, form.origin.spec()); + s.bind_wstring(1, form.username_element); + s.bind_wstring(2, form.username_value); + s.bind_wstring(3, form.password_element); + s.bind_wstring(4, form.submit_element); + s.bind_string(5, form.signon_realm); + + if (s.step() != SQLITE_DONE) { + NOTREACHED(); + return false; + } + return true; +} + +bool WebDatabase::RemoveIE7Login(const IE7PasswordInfo& info) { + SQLStatement s; + // Remove a login by UNIQUE-constrained fields. + if (s.prepare(db_, + "DELETE FROM ie7_logins WHERE " + "url_hash = ?") != SQLITE_OK) { + NOTREACHED() << "Statement prepare failed"; + return false; + } + s.bind_wstring(0, info.url_hash); + + if (s.step() != SQLITE_DONE) { + NOTREACHED(); + return false; + } + return true; +} + +bool WebDatabase::RemoveLoginsCreatedBetween(const Time delete_begin, + const Time delete_end) { + SQLStatement s1; + if (s1.prepare(db_, + "DELETE FROM logins WHERE " + "date_created >= ? AND date_created < ?") != SQLITE_OK) { + NOTREACHED() << "Statement 1 prepare failed"; + return false; + } + s1.bind_int64(0, delete_begin.ToTimeT()); + s1.bind_int64(1, + delete_end.is_null() ? + std::numeric_limits<int64>::max() : + delete_end.ToTimeT()); + + SQLStatement s2; + if (s2.prepare(db_, + "DELETE FROM ie7_logins WHERE " + "date_created >= ? AND date_created < ?") != SQLITE_OK) { + NOTREACHED() << "Statement 2 prepare failed"; + return false; + } + s2.bind_int64(0, delete_begin.ToTimeT()); + s2.bind_int64(1, + delete_end.is_null() ? + std::numeric_limits<int64>::max() : + delete_end.ToTimeT()); + + return s1.step() == SQLITE_DONE && s2.step() == SQLITE_DONE; +} + +static void InitPasswordFormFromStatement(PasswordForm* form, + SQLStatement* s) { + std::string encrypted_password; + std::string tmp; + s->column_string(0, &tmp); + form->origin = GURL(tmp); + s->column_string(1, &tmp); + form->action = GURL(tmp); + s->column_string16(2, &form->username_element); + s->column_string16(3, &form->username_value); + s->column_string16(4, &form->password_element); + s->column_blob_as_string(5, &encrypted_password); + Encryptor::DecryptWideString(encrypted_password, &form->password_value); + s->column_string16(6, &form->submit_element); + s->column_string(7, &tmp); + form->signon_realm = tmp; + form->ssl_valid = (s->column_int(8) > 0); + form->preferred = (s->column_int(9) > 0); + form->date_created = Time::FromTimeT(s->column_int64(10)); + form->blacklisted_by_user = (s->column_int(11) > 0); + int scheme_int = s->column_int(12); + DCHECK((scheme_int >= 0) && (scheme_int <= PasswordForm::SCHEME_OTHER)); + form->scheme = static_cast<PasswordForm::Scheme>(scheme_int); +} + +bool WebDatabase::GetLogins(const PasswordForm& form, + std::vector<PasswordForm*>* forms) { + DCHECK(forms); + SQLStatement s; + if (s.prepare(db_, + "SELECT origin_url, action_url, " + "username_element, username_value, " + "password_element, password_value, " + "submit_element, signon_realm, " + "ssl_valid, preferred, " + "date_created, blacklisted_by_user, scheme FROM logins " + "WHERE signon_realm == ? ") != SQLITE_OK) { + NOTREACHED() << "Statement prepare failed"; + return false; + } + + s.bind_string(0, form.signon_realm); + + int64 result; + while ((result = s.step()) == SQLITE_ROW) { + PasswordForm* new_form = new PasswordForm(); + InitPasswordFormFromStatement(new_form, &s); + + forms->push_back(new_form); + } + return result == SQLITE_DONE; +} + +bool WebDatabase::GetIE7Login(const IE7PasswordInfo& info, + IE7PasswordInfo* result) { + DCHECK(result); + SQLStatement s; + if (s.prepare(db_, + "SELECT password_value, date_created FROM ie7_logins " + "WHERE url_hash == ? ") != SQLITE_OK) { + NOTREACHED() << "Statement prepare failed"; + return false; + } + + s.bind_wstring(0, info.url_hash); + + int64 query_result = s.step(); + if (query_result == SQLITE_ROW) { + s.column_blob_as_vector(0, &result->encrypted_data); + result->date_created = Time::FromTimeT(s.column_int64(1)); + result->url_hash = info.url_hash; + s.step(); + } + return query_result == SQLITE_DONE; +} + +bool WebDatabase::GetAllLogins(std::vector<PasswordForm*>* forms, + bool include_blacklisted) { + DCHECK(forms); + SQLStatement s; + std::string stmt = "SELECT origin_url, action_url, " + "username_element, username_value, " + "password_element, password_value, " + "submit_element, signon_realm, ssl_valid, preferred, " + "date_created, blacklisted_by_user, scheme FROM logins "; + if (!include_blacklisted) + stmt.append("WHERE blacklisted_by_user == 0 "); + stmt.append("ORDER BY origin_url"); + + if (s.prepare(db_, stmt.c_str()) != SQLITE_OK) { + NOTREACHED() << "Statement prepare failed"; + return false; + } + + int64 result; + while ((result = s.step()) == SQLITE_ROW) { + PasswordForm* new_form = new PasswordForm(); + InitPasswordFormFromStatement(new_form, &s); + + forms->push_back(new_form); + } + return result == SQLITE_DONE; +} diff --git a/chrome/browser/webdata/web_database.h b/chrome/browser/webdata/web_database.h new file mode 100644 index 0000000..f34d570 --- /dev/null +++ b/chrome/browser/webdata/web_database.h @@ -0,0 +1,167 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_WEBDATA_WEB_DATABASE_H__ +#define CHROME_BROWSER_WEBDATA_WEB_DATABASE_H__ + +#include <string> +#include <vector> +#include <hash_map> + +#include "base/basictypes.h" +#include "chrome/browser/meta_table_helper.h" +#include "chrome/browser/template_url.h" +#include "chrome/common/sqlite_utils.h" +#include "skia/include/SkBitmap.h" + +class Time; +struct PasswordForm; +struct IE7PasswordInfo; + +//////////////////////////////////////////////////////////////////////////////// +// +// A Sqlite database instance to store all the meta data we have about web pages +// +//////////////////////////////////////////////////////////////////////////////// +class WebDatabase { + public: + WebDatabase(); + ~WebDatabase(); + + // Initialize the database given a name. The name defines where the sqlite + // file is. If false is returned, no other method should be called. + bool Init(const std::wstring& db_name); + + // Transactions management + void BeginTransaction(); + void CommitTransaction(); + + ////////////////////////////////////////////////////////////////////////////// + // + // Keywords + // + ////////////////////////////////////////////////////////////////////////////// + + // Adds a new keyword, updating the id field on success. + // Returns true if successful. + bool AddKeyword(const TemplateURL& url); + + // Removes the specified keyword. + // Returns true if successful. + bool RemoveKeyword(TemplateURL::IDType id); + + // Loads the keywords into the specified vector. It's up to the caller to + // delete the returned objects. + // Returns true on success. + bool GetKeywords(std::vector<TemplateURL*>* urls); + + // Updates the database values for the specified url. + // Returns true on success. + bool UpdateKeyword(const TemplateURL& url); + + // ID (TemplateURL->id) of the default search provider. + bool SetDefaultSearchProviderID(int64 id); + int64 GetDefaulSearchProviderID(); + + // Version of the builtin keywords. + bool SetBuitinKeywordVersion(int version); + int GetBuitinKeywordVersion(); + + ////////////////////////////////////////////////////////////////////////////// + // + // Password manager support + // + ////////////////////////////////////////////////////////////////////////////// + + // Adds |form| to the list of remembered password forms. + bool AddLogin(const PasswordForm& form); + + // Adds |info| to the list of imported passwords from ie7/ie8. + bool AddIE7Login(const IE7PasswordInfo& info); + + // Updates remembered password form. + bool UpdateLogin(const PasswordForm& form); + + // Removes |form| from the list of remembered password forms. + bool RemoveLogin(const PasswordForm& form); + + // Removes |info| from the list of imported passwords from ie7/ie8. + bool RemoveIE7Login(const IE7PasswordInfo& info); + + // Removes all logins created from |delete_begin| onwards (inclusive) and + // before |delete_end|. You may use a null Time value to do an unbounded + // delete in either direction. + bool RemoveLoginsCreatedBetween(const Time delete_begin, + const Time delete_end); + + // Loads a list of matching password forms into the specified vector |forms|. + // The list will contain all possibly relevant entries to the observed |form|, + // including blacklisted matches. + bool GetLogins(const PasswordForm& form, std::vector<PasswordForm*>* forms); + + // Return the ie7/ie8 login matching |info|. + bool GetIE7Login(const IE7PasswordInfo& info, IE7PasswordInfo* result); + + // Loads the complete list of password forms into the specified vector |forms| + // if include_blacklisted is true, otherwise only loads those which are + // actually autofillable; i.e haven't been blacklisted by the user selecting + // the 'Never for this site' button. + bool GetAllLogins(std::vector<PasswordForm*>* forms, + bool include_blacklisted); + + ////////////////////////////////////////////////////////////////////////////// + // + // Web Apps + // + ////////////////////////////////////////////////////////////////////////////// + + bool SetWebAppImage(const GURL& url, const SkBitmap& image); + bool GetWebAppImages(const GURL& url, std::vector<SkBitmap>* images); + + bool SetWebAppHasAllImages(const GURL& url, bool has_all_images); + bool GetWebAppHasAllImages(const GURL& url); + + bool RemoveWebApp(const GURL& url); + + private: + friend class WebDatabaseTest; + + bool InitKeywordsTable(); + bool InitLoginsTable(); + bool InitWebAppIconsTable(); + bool InitWebAppsTable(); + + sqlite3* db_; + int transaction_nesting_; + MetaTableHelper meta_table_; + + DISALLOW_EVIL_CONSTRUCTORS(WebDatabase); +}; + +#endif // CHROME_BROWSER_WEBDATA_WEB_DATABASE_H__ diff --git a/chrome/browser/webdata/web_database_unittest.cc b/chrome/browser/webdata/web_database_unittest.cc new file mode 100644 index 0000000..fd68974 --- /dev/null +++ b/chrome/browser/webdata/web_database_unittest.cc @@ -0,0 +1,587 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <windows.h> + +#include "base/file_util.h" +#include "base/path_service.h" +#include "base/string_util.h" +#include "base/values.h" +#include "chrome/browser/template_url.h" +#include "chrome/browser/webdata/web_database.h" +#include "chrome/common/chrome_paths.h" +#include "chrome/common/stl_util-inl.h" +#include "skia/include/SkBitmap.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/glue/password_form.h" + +class WebDatabaseTest : public testing::Test { + protected: + + virtual void SetUp() { + wchar_t b[32]; + _itow_s(static_cast<int>(GetTickCount()), b, arraysize(b), 10); + + PathService::Get(chrome::DIR_TEST_DATA, &file_); + file_ += file_util::kPathSeparator; + file_ += L"TestWebDatabase"; + file_ += b; + file_ += L".db"; + DeleteFile(file_.c_str()); + } + + virtual void TearDown() { + DeleteFile(file_.c_str()); + } + + static int GetKeyCount(const DictionaryValue& d) { + DictionaryValue::key_iterator i(d.begin_keys()); + DictionaryValue::key_iterator e(d.end_keys()); + + int r = 0; + while (i != e) { + ++i; + ++r; + } + return r; + } + + static bool StringDictionaryValueEquals(const DictionaryValue& a, + const DictionaryValue& b) { + int a_count = 0; + int b_count = GetKeyCount(b); + DictionaryValue::key_iterator i(a.begin_keys()); + DictionaryValue::key_iterator e(a.end_keys()); + std::wstring av, bv; + while (i != e) { + if (!(a.GetString(*i, &av)) || + !(b.GetString(*i, &bv)) || + av != bv) + return false; + + a_count++; + ++i; + } + + return (a_count == b_count); + } + + static int64 GetID(const TemplateURL* url) { + return url->id(); + } + + static void SetID(int64 new_id, TemplateURL* url) { + url->set_id(new_id); + } + + static void set_prepopulate_id(TemplateURL* url, int id) { + url->set_prepopulate_id(id); + } + + std::wstring file_; +}; + +TEST_F(WebDatabaseTest, Keywords) { + WebDatabase db; + + EXPECT_TRUE(db.Init(file_)); + + TemplateURL template_url; + template_url.set_short_name(L"short_name"); + template_url.set_keyword(L"keyword"); + GURL favicon_url("http://favicon.url"); + GURL originating_url("http://google.com"); + template_url.SetFavIconURL(favicon_url); + template_url.SetURL(L"url", 0, 0); + template_url.set_safe_for_autoreplace(true); + template_url.set_show_in_default_list(true); + template_url.set_originating_url(originating_url); + template_url.set_usage_count(32); + template_url.add_input_encoding("UTF-8"); + set_prepopulate_id(&template_url, 10); + SetID(1, &template_url); + + EXPECT_TRUE(db.AddKeyword(template_url)); + + std::vector<TemplateURL*> template_urls; + EXPECT_TRUE(db.GetKeywords(&template_urls)); + + EXPECT_EQ(1, template_urls.size()); + const TemplateURL* restored_url = template_urls.front(); + + EXPECT_EQ(template_url.short_name(), restored_url->short_name()); + + EXPECT_EQ(template_url.keyword(), restored_url->keyword()); + + EXPECT_TRUE(favicon_url == restored_url->GetFavIconURL()); + + EXPECT_TRUE(restored_url->safe_for_autoreplace()); + + EXPECT_TRUE(restored_url->show_in_default_list()); + + EXPECT_EQ(GetID(&template_url), GetID(restored_url)); + + EXPECT_TRUE(originating_url == restored_url->originating_url()); + + EXPECT_EQ(32, restored_url->usage_count()); + + ASSERT_EQ(1, restored_url->input_encodings().size()); + EXPECT_EQ("UTF-8", restored_url->input_encodings()[0]); + + EXPECT_EQ(10, restored_url->prepopulate_id()); + + EXPECT_TRUE(db.RemoveKeyword(restored_url->id())); + + template_urls.clear(); + EXPECT_TRUE(db.GetKeywords(&template_urls)); + + EXPECT_EQ(0, template_urls.size()); + + delete restored_url; +} + +TEST_F(WebDatabaseTest, KeywordMisc) { + WebDatabase db; + + EXPECT_TRUE(db.Init(file_)); + + ASSERT_EQ(0, db.GetDefaulSearchProviderID()); + ASSERT_EQ(0, db.GetBuitinKeywordVersion()); + + db.SetDefaultSearchProviderID(10); + db.SetBuitinKeywordVersion(11); + + ASSERT_EQ(10, db.GetDefaulSearchProviderID()); + ASSERT_EQ(11, db.GetBuitinKeywordVersion()); +} + +TEST_F(WebDatabaseTest, UpdateKeyword) { + WebDatabase db; + + EXPECT_TRUE(db.Init(file_)); + + TemplateURL template_url; + template_url.set_short_name(L"short_name"); + template_url.set_keyword(L"keyword"); + GURL favicon_url("http://favicon.url"); + GURL originating_url("http://originating.url"); + template_url.SetFavIconURL(favicon_url); + template_url.SetURL(L"url", 0, 0); + template_url.set_safe_for_autoreplace(true); + template_url.set_show_in_default_list(true); + template_url.SetSuggestionsURL(L"url2", 0, 0); + SetID(1, &template_url); + + EXPECT_TRUE(db.AddKeyword(template_url)); + + GURL originating_url2("http://originating.url"); + template_url.set_keyword(L"X"); + template_url.set_originating_url(originating_url2); + template_url.add_input_encoding("Shift_JIS"); + set_prepopulate_id(&template_url, 5); + EXPECT_TRUE(db.UpdateKeyword(template_url)); + + std::vector<TemplateURL*> template_urls; + EXPECT_TRUE(db.GetKeywords(&template_urls)); + + EXPECT_EQ(1, template_urls.size()); + const TemplateURL* restored_url = template_urls.front(); + + EXPECT_EQ(template_url.short_name(), restored_url->short_name()); + + EXPECT_EQ(template_url.keyword(), restored_url->keyword()); + + EXPECT_TRUE(favicon_url == restored_url->GetFavIconURL()); + + EXPECT_TRUE(restored_url->safe_for_autoreplace()); + + EXPECT_TRUE(restored_url->show_in_default_list()); + + EXPECT_EQ(GetID(&template_url), GetID(restored_url)); + + EXPECT_TRUE(originating_url2 == restored_url->originating_url()); + + ASSERT_EQ(1, restored_url->input_encodings().size()); + ASSERT_EQ("Shift_JIS", restored_url->input_encodings()[0]); + + EXPECT_EQ(template_url.suggestions_url()->url(), + restored_url->suggestions_url()->url()); + + EXPECT_EQ(template_url.id(), restored_url->id()); + + EXPECT_EQ(template_url.prepopulate_id(), restored_url->prepopulate_id()); + + delete restored_url; +} + +TEST_F(WebDatabaseTest, KeywordWithNoFavicon) { + WebDatabase db; + + EXPECT_TRUE(db.Init(file_)); + + TemplateURL template_url; + template_url.set_short_name(L"short_name"); + template_url.set_keyword(L"keyword"); + template_url.SetURL(L"url", 0, 0); + template_url.set_safe_for_autoreplace(true); + SetID(-100, &template_url); + + EXPECT_TRUE(db.AddKeyword(template_url)); + + std::vector<TemplateURL*> template_urls; + EXPECT_TRUE(db.GetKeywords(&template_urls)); + EXPECT_EQ(1, template_urls.size()); + const TemplateURL* restored_url = template_urls.front(); + + EXPECT_EQ(template_url.short_name(), restored_url->short_name()); + EXPECT_EQ(template_url.keyword(), restored_url->keyword()); + EXPECT_TRUE(!restored_url->GetFavIconURL().is_valid()); + EXPECT_TRUE(restored_url->safe_for_autoreplace()); + EXPECT_EQ(GetID(&template_url), GetID(restored_url)); + delete restored_url; +} + +TEST_F(WebDatabaseTest, Logins) { + WebDatabase db; + + EXPECT_TRUE(db.Init(file_)); + + std::vector<PasswordForm*> result; + + // Verify the database is empty. + EXPECT_TRUE(db.GetAllLogins(&result, true)); + EXPECT_EQ(0, result.size()); + + // Example password form. + PasswordForm form; + form.origin = GURL(L"http://www.google.com/accounts/LoginAuth"); + form.action = GURL(L"http://www.google.com/accounts/Login"); + form.username_element = L"Email"; + form.username_value = L"test@gmail.com"; + form.password_element = L"Passwd"; + form.password_value = L"test"; + form.submit_element = L"signIn"; + form.signon_realm = "http://www.google.com"; + form.ssl_valid = false; + form.preferred = false; + form.scheme = PasswordForm::SCHEME_HTML; + + // Add it and make sure it is there. + EXPECT_TRUE(db.AddLogin(form)); + EXPECT_TRUE(db.GetAllLogins(&result, true)); + EXPECT_EQ(1, result.size()); + delete result[0]; + result.clear(); + + // Match against an exact copy. + EXPECT_TRUE(db.GetLogins(form, &result)); + EXPECT_EQ(1, result.size()); + delete result[0]; + result.clear(); + + // The example site changes... + PasswordForm form2(form); + form2.origin = GURL(L"http://www.google.com/new/accounts/LoginAuth"); + form2.submit_element = L"reallySignIn"; + + // Match against an inexact copy + EXPECT_TRUE(db.GetLogins(form2, &result)); + EXPECT_EQ(1, result.size()); + delete result[0]; + result.clear(); + + // Uh oh, the site changed origin & action URL's all at once! + PasswordForm form3(form2); + form3.action = GURL(L"http://www.google.com/new/accounts/Login"); + + // signon_realm is the same, should match. + EXPECT_TRUE(db.GetLogins(form3, &result)); + EXPECT_EQ(1, result.size()); + delete result[0]; + result.clear(); + + // Imagine the site moves to a secure server for login. + PasswordForm form4(form3); + form4.signon_realm = "https://www.google.com"; + form4.ssl_valid = true; + + // We have only an http record, so no match for this. + EXPECT_TRUE(db.GetLogins(form4, &result)); + EXPECT_EQ(0, result.size()); + + // Let's imagine the user logs into the secure site. + EXPECT_TRUE(db.AddLogin(form4)); + EXPECT_TRUE(db.GetAllLogins(&result, true)); + EXPECT_EQ(2, result.size()); + delete result[0]; + delete result[1]; + result.clear(); + + // Now the match works + EXPECT_TRUE(db.GetLogins(form4, &result)); + EXPECT_EQ(1, result.size()); + delete result[0]; + result.clear(); + + // The user chose to forget the original but not the new. + EXPECT_TRUE(db.RemoveLogin(form)); + EXPECT_TRUE(db.GetAllLogins(&result, true)); + EXPECT_EQ(1, result.size()); + delete result[0]; + result.clear(); + + // The old form wont match the new site (http vs https). + EXPECT_TRUE(db.GetLogins(form, &result)); + EXPECT_EQ(0, result.size()); + + // The user's request for the HTTPS site is intercepted + // by an attacker who presents an invalid SSL cert. + PasswordForm form5(form4); + form5.ssl_valid = 0; + + // It will match in this case. + EXPECT_TRUE(db.GetLogins(form5, &result)); + EXPECT_EQ(1, result.size()); + delete result[0]; + result.clear(); + + // User changes his password. + PasswordForm form6(form5); + form6.password_value = L"test6"; + form6.preferred = true; + + // We update, and check to make sure it matches the + // old form, and there is only one record. + EXPECT_TRUE(db.UpdateLogin(form6)); + // matches + EXPECT_TRUE(db.GetLogins(form5, &result)); + EXPECT_EQ(1, result.size()); + delete result[0]; + result.clear(); + // Only one record. + EXPECT_TRUE(db.GetAllLogins(&result, true)); + EXPECT_EQ(1, result.size()); + // password element was updated. + EXPECT_EQ(form6.password_value, result[0]->password_value); + // Preferred login. + EXPECT_TRUE(form6.preferred); + delete result[0]; + result.clear(); + + // Make sure everything can disappear. + EXPECT_TRUE(db.RemoveLogin(form4)); + EXPECT_TRUE(db.GetAllLogins(&result, true)); + EXPECT_EQ(0, result.size()); +} + +static bool AddTimestampedLogin(WebDatabase* db, std::string url, + std::wstring unique_string, const Time& time) { + // Example password form. + PasswordForm form; + form.origin = GURL(url + std::string("/LoginAuth")); + form.username_element = unique_string.c_str(); + form.username_value = unique_string.c_str(); + form.password_element = unique_string.c_str(); + form.submit_element = L"signIn"; + form.signon_realm = url; + form.date_created = time; + return db->AddLogin(form); +} + +static void ClearResults(std::vector<PasswordForm*>* results) { + for (size_t i = 0; i < results->size(); ++i) { + delete (*results)[i]; + } + results->clear(); +} + +TEST_F(WebDatabaseTest, ClearPrivateData_SavedPasswords) { + WebDatabase db; + + EXPECT_TRUE(db.Init(file_)); + + std::vector<PasswordForm*> result; + + // Verify the database is empty. + EXPECT_TRUE(db.GetAllLogins(&result, true)); + EXPECT_EQ(0, result.size()); + + Time now = Time::Now(); + TimeDelta one_day = TimeDelta::FromDays(1); + + // Create one with a 0 time. + EXPECT_TRUE(AddTimestampedLogin(&db, "1", L"foo1", Time())); + // Create one for now and +/- 1 day. + EXPECT_TRUE(AddTimestampedLogin(&db, "2", L"foo2", now - one_day)); + EXPECT_TRUE(AddTimestampedLogin(&db, "3", L"foo3", now)); + EXPECT_TRUE(AddTimestampedLogin(&db, "4", L"foo4", now + one_day)); + + // Verify inserts worked. + EXPECT_TRUE(db.GetAllLogins(&result, true)); + EXPECT_EQ(4, result.size()); + ClearResults(&result); + + // Delete everything from today's date and on. + db.RemoveLoginsCreatedBetween(now, Time()); + + // Should have deleted half of what we inserted. + EXPECT_TRUE(db.GetAllLogins(&result, true)); + EXPECT_EQ(2, result.size()); + ClearResults(&result); + + // Delete with 0 date (should delete all). + db.RemoveLoginsCreatedBetween(Time(), Time()); + + // Verify nothing is left. + EXPECT_TRUE(db.GetAllLogins(&result, true)); + EXPECT_EQ(0, result.size()); +} + +TEST_F(WebDatabaseTest, BlacklistedLogins) { + WebDatabase db; + + EXPECT_TRUE(db.Init(file_)); + std::vector<PasswordForm*> result; + + // Verify the database is empty. + EXPECT_TRUE(db.GetAllLogins(&result, true)); + ASSERT_EQ(0, result.size()); + + // Save a form as blacklisted. + PasswordForm form; + form.origin = GURL(L"http://www.google.com/accounts/LoginAuth"); + form.action = GURL(L"http://www.google.com/accounts/Login"); + form.username_element = L"Email"; + form.password_element = L"Passwd"; + form.submit_element = L"signIn"; + form.signon_realm = "http://www.google.com"; + form.ssl_valid = false; + form.preferred = true; + form.blacklisted_by_user = true; + form.scheme = PasswordForm::SCHEME_HTML; + EXPECT_TRUE(db.AddLogin(form)); + + // Get all non-blacklisted logins (should be none). + EXPECT_TRUE(db.GetAllLogins(&result, false)); + ASSERT_EQ(0, result.size()); + + // GetLogins should give the blacklisted result. + EXPECT_TRUE(db.GetLogins(form, &result)); + EXPECT_EQ(1, result.size()); + ClearResults(&result); + + // So should GetAll including blacklisted. + EXPECT_TRUE(db.GetAllLogins(&result, true)); + EXPECT_EQ(1, result.size()); + ClearResults(&result); +} + +TEST_F(WebDatabaseTest, WebAppHasAllImages) { + WebDatabase db; + + EXPECT_TRUE(db.Init(file_)); + GURL url("http://google.com"); + + // Initial value for unknown web app should be false. + EXPECT_FALSE(db.GetWebAppHasAllImages(url)); + + // Set the value and make sure it took. + EXPECT_TRUE(db.SetWebAppHasAllImages(url, true)); + EXPECT_TRUE(db.GetWebAppHasAllImages(url)); + + // Remove the app and make sure value reverts to default. + EXPECT_TRUE(db.RemoveWebApp(url)); + EXPECT_FALSE(db.GetWebAppHasAllImages(url)); +} + +TEST_F(WebDatabaseTest, WebAppImages) { + WebDatabase db; + + ASSERT_TRUE(db.Init(file_)); + GURL url("http://google.com"); + + // Web app should initially have no images. + std::vector<SkBitmap> images; + ASSERT_TRUE(db.GetWebAppImages(url, &images)); + ASSERT_EQ(0, images.size()); + + // Add an image. + SkBitmap image; + image.setConfig(SkBitmap::kARGB_8888_Config, 16, 16); + image.allocPixels(); + image.eraseColor(SK_ColorBLACK); + ASSERT_TRUE(db.SetWebAppImage(url, image)); + + // Make sure we get the image back. + ASSERT_TRUE(db.GetWebAppImages(url, &images)); + ASSERT_EQ(1, images.size()); + ASSERT_EQ(16, images[0].width()); + ASSERT_EQ(16, images[0].height()); + + // Add another 16x16 image and make sure it replaces the original. + image.setConfig(SkBitmap::kARGB_8888_Config, 16, 16); + image.allocPixels(); + image.eraseColor(SK_ColorBLACK); + // Some random pixels so that we can identify the image. + *(reinterpret_cast<unsigned char*>(image.getPixels())) = 0xAB; + ASSERT_TRUE(db.SetWebAppImage(url, image)); + images.clear(); + ASSERT_TRUE(db.GetWebAppImages(url, &images)); + ASSERT_EQ(1, images.size()); + ASSERT_EQ(16, images[0].width()); + ASSERT_EQ(16, images[0].height()); + images[0].lockPixels(); + unsigned char* pixels = + reinterpret_cast<unsigned char*>(images[0].getPixels()); + ASSERT_TRUE(pixels != NULL); + ASSERT_EQ(0xAB, *pixels); + images[0].unlockPixels(); + + // Add another image at a bigger size. + image.setConfig(SkBitmap::kARGB_8888_Config, 32, 32); + image.allocPixels(); + image.eraseColor(SK_ColorBLACK); + ASSERT_TRUE(db.SetWebAppImage(url, image)); + + // Make sure we get both images back. + images.clear(); + ASSERT_TRUE(db.GetWebAppImages(url, &images)); + ASSERT_EQ(2, images.size()); + if (images[0].width() == 16) { + ASSERT_EQ(16, images[0].width()); + ASSERT_EQ(16, images[0].height()); + ASSERT_EQ(32, images[1].width()); + ASSERT_EQ(32, images[1].height()); + } else { + ASSERT_EQ(32, images[0].width()); + ASSERT_EQ(32, images[0].height()); + ASSERT_EQ(16, images[1].width()); + ASSERT_EQ(16, images[1].height()); + } +} |