/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include #include #include "nsError.h" #include "nsMemory.h" #include "nsProxyRelease.h" #include "nsThreadUtils.h" #include "nsIClassInfoImpl.h" #include "Variant.h" #include "mozIStorageError.h" #include "mozStorageBindingParams.h" #include "mozStorageConnection.h" #include "mozStorageAsyncStatementJSHelper.h" #include "mozStorageAsyncStatementParams.h" #include "mozStoragePrivateHelpers.h" #include "mozStorageStatementRow.h" #include "mozStorageStatement.h" #include "nsDOMClassInfo.h" #include "mozilla/Logging.h" extern mozilla::LazyLogModule gStorageLog; namespace mozilla { namespace storage { //////////////////////////////////////////////////////////////////////////////// //// nsIClassInfo NS_IMPL_CI_INTERFACE_GETTER(AsyncStatement, mozIStorageAsyncStatement, mozIStorageBaseStatement, mozIStorageBindingParams, mozilla::storage::StorageBaseStatementInternal) class AsyncStatementClassInfo : public nsIClassInfo { public: constexpr AsyncStatementClassInfo() {} NS_DECL_ISUPPORTS_INHERITED NS_IMETHOD GetInterfaces(uint32_t *_count, nsIID ***_array) override { return NS_CI_INTERFACE_GETTER_NAME(AsyncStatement)(_count, _array); } NS_IMETHOD GetScriptableHelper(nsIXPCScriptable **_helper) override { static AsyncStatementJSHelper sJSHelper; *_helper = &sJSHelper; return NS_OK; } NS_IMETHOD GetContractID(char **_contractID) override { *_contractID = nullptr; return NS_OK; } NS_IMETHOD GetClassDescription(char **_desc) override { *_desc = nullptr; return NS_OK; } NS_IMETHOD GetClassID(nsCID **_id) override { *_id = nullptr; return NS_OK; } NS_IMETHOD GetFlags(uint32_t *_flags) override { *_flags = 0; return NS_OK; } NS_IMETHOD GetClassIDNoAlloc(nsCID *_cid) override { return NS_ERROR_NOT_AVAILABLE; } }; NS_IMETHODIMP_(MozExternalRefCountType) AsyncStatementClassInfo::AddRef() { return 2; } NS_IMETHODIMP_(MozExternalRefCountType) AsyncStatementClassInfo::Release() { return 1; } NS_IMPL_QUERY_INTERFACE(AsyncStatementClassInfo, nsIClassInfo) static AsyncStatementClassInfo sAsyncStatementClassInfo; //////////////////////////////////////////////////////////////////////////////// //// AsyncStatement AsyncStatement::AsyncStatement() : StorageBaseStatementInternal() , mFinalized(false) { } nsresult AsyncStatement::initialize(Connection *aDBConnection, sqlite3 *aNativeConnection, const nsACString &aSQLStatement) { MOZ_ASSERT(aDBConnection, "No database connection given!"); MOZ_ASSERT(!aDBConnection->isClosed(), "Database connection should be valid"); MOZ_ASSERT(aNativeConnection, "No native connection given!"); mDBConnection = aDBConnection; mNativeConnection = aNativeConnection; mSQLString = aSQLStatement; MOZ_LOG(gStorageLog, LogLevel::Debug, ("Inited async statement '%s' (0x%p)", mSQLString.get())); #ifdef DEBUG // We want to try and test for LIKE and that consumers are using // escapeStringForLIKE instead of just trusting user input. The idea to // check to see if they are binding a parameter after like instead of just // using a string. We only do this in debug builds because it's expensive! const nsCaseInsensitiveCStringComparator c; nsACString::const_iterator start, end, e; aSQLStatement.BeginReading(start); aSQLStatement.EndReading(end); e = end; while (::FindInReadable(NS_LITERAL_CSTRING(" LIKE"), start, e, c)) { // We have a LIKE in here, so we perform our tests // FindInReadable moves the iterator, so we have to get a new one for // each test we perform. nsACString::const_iterator s1, s2, s3; s1 = s2 = s3 = start; if (!(::FindInReadable(NS_LITERAL_CSTRING(" LIKE ?"), s1, end, c) || ::FindInReadable(NS_LITERAL_CSTRING(" LIKE :"), s2, end, c) || ::FindInReadable(NS_LITERAL_CSTRING(" LIKE @"), s3, end, c))) { // At this point, we didn't find a LIKE statement followed by ?, :, // or @, all of which are valid characters for binding a parameter. // We will warn the consumer that they may not be safely using LIKE. NS_WARNING("Unsafe use of LIKE detected! Please ensure that you " "are using mozIStorageAsyncStatement::escapeStringForLIKE " "and that you are binding that result to the statement " "to prevent SQL injection attacks."); } // resetting start and e start = e; e = end; } #endif return NS_OK; } mozIStorageBindingParams * AsyncStatement::getParams() { nsresult rv; // If we do not have an array object yet, make it. if (!mParamsArray) { nsCOMPtr array; rv = NewBindingParamsArray(getter_AddRefs(array)); NS_ENSURE_SUCCESS(rv, nullptr); mParamsArray = static_cast(array.get()); } // If there isn't already any rows added, we'll have to add one to use. if (mParamsArray->length() == 0) { RefPtr params(new AsyncBindingParams(mParamsArray)); NS_ENSURE_TRUE(params, nullptr); rv = mParamsArray->AddParams(params); NS_ENSURE_SUCCESS(rv, nullptr); // We have to unlock our params because AddParams locks them. This is safe // because no reference to the params object was, or ever will be given out. params->unlock(nullptr); // We also want to lock our array at this point - we don't want anything to // be added to it. mParamsArray->lock(); } return *mParamsArray->begin(); } /** * If we are here then we know there are no pending async executions relying on * us (StatementData holds a reference to us; this also goes for our own * AsyncStatementFinalizer which proxies its release to the calling thread) and * so it is always safe to destroy our sqlite3_stmt if one exists. We can be * destroyed on the caller thread by garbage-collection/reference counting or on * the async thread by the last execution of a statement that already lost its * main-thread refs. */ AsyncStatement::~AsyncStatement() { destructorAsyncFinalize(); // If we are getting destroyed on the wrong thread, proxy the connection // release to the right thread. I'm not sure why we do this. bool onCallingThread = false; (void)mDBConnection->threadOpenedOn->IsOnCurrentThread(&onCallingThread); if (!onCallingThread) { // NS_ProxyRelase only magic forgets for us if mDBConnection is an // nsCOMPtr. Which it is not; it's an nsRefPtr. nsCOMPtr targetThread(mDBConnection->threadOpenedOn); NS_ProxyRelease(targetThread, mDBConnection.forget()); } } //////////////////////////////////////////////////////////////////////////////// //// nsISupports NS_IMPL_ADDREF(AsyncStatement) NS_IMPL_RELEASE(AsyncStatement) NS_INTERFACE_MAP_BEGIN(AsyncStatement) NS_INTERFACE_MAP_ENTRY(mozIStorageAsyncStatement) NS_INTERFACE_MAP_ENTRY(mozIStorageBaseStatement) NS_INTERFACE_MAP_ENTRY(mozIStorageBindingParams) NS_INTERFACE_MAP_ENTRY(mozilla::storage::StorageBaseStatementInternal) if (aIID.Equals(NS_GET_IID(nsIClassInfo))) { foundInterface = static_cast(&sAsyncStatementClassInfo); } else NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, mozIStorageAsyncStatement) NS_INTERFACE_MAP_END //////////////////////////////////////////////////////////////////////////////// //// StorageBaseStatementInternal Connection * AsyncStatement::getOwner() { return mDBConnection; } int AsyncStatement::getAsyncStatement(sqlite3_stmt **_stmt) { #ifdef DEBUG // Make sure we are never called on the connection's owning thread. bool onOpenedThread = false; (void)mDBConnection->threadOpenedOn->IsOnCurrentThread(&onOpenedThread); NS_ASSERTION(!onOpenedThread, "We should only be called on the async thread!"); #endif if (!mAsyncStatement) { int rc = mDBConnection->prepareStatement(mNativeConnection, mSQLString, &mAsyncStatement); if (rc != SQLITE_OK) { MOZ_LOG(gStorageLog, LogLevel::Error, ("Sqlite statement prepare error: %d '%s'", rc, ::sqlite3_errmsg(mNativeConnection))); MOZ_LOG(gStorageLog, LogLevel::Error, ("Statement was: '%s'", mSQLString.get())); *_stmt = nullptr; return rc; } MOZ_LOG(gStorageLog, LogLevel::Debug, ("Initialized statement '%s' (0x%p)", mSQLString.get(), mAsyncStatement)); } *_stmt = mAsyncStatement; return SQLITE_OK; } nsresult AsyncStatement::getAsynchronousStatementData(StatementData &_data) { if (mFinalized) return NS_ERROR_UNEXPECTED; // Pass null for the sqlite3_stmt; it will be requested on demand from the // async thread. _data = StatementData(nullptr, bindingParamsArray(), this); return NS_OK; } already_AddRefed AsyncStatement::newBindingParams(mozIStorageBindingParamsArray *aOwner) { if (mFinalized) return nullptr; nsCOMPtr params(new AsyncBindingParams(aOwner)); return params.forget(); } //////////////////////////////////////////////////////////////////////////////// //// mozIStorageAsyncStatement // (nothing is specific to mozIStorageAsyncStatement) //////////////////////////////////////////////////////////////////////////////// //// StorageBaseStatementInternal // proxy to StorageBaseStatementInternal using its define helper. MIXIN_IMPL_STORAGEBASESTATEMENTINTERNAL( AsyncStatement, if (mFinalized) return NS_ERROR_UNEXPECTED;) NS_IMETHODIMP AsyncStatement::Finalize() { if (mFinalized) return NS_OK; mFinalized = true; MOZ_LOG(gStorageLog, LogLevel::Debug, ("Finalizing statement '%s'", mSQLString.get())); asyncFinalize(); // Release the params holder, so it can release the reference to us. mStatementParamsHolder = nullptr; return NS_OK; } NS_IMETHODIMP AsyncStatement::BindParameters(mozIStorageBindingParamsArray *aParameters) { if (mFinalized) return NS_ERROR_UNEXPECTED; BindingParamsArray *array = static_cast(aParameters); if (array->getOwner() != this) return NS_ERROR_UNEXPECTED; if (array->length() == 0) return NS_ERROR_UNEXPECTED; mParamsArray = array; mParamsArray->lock(); return NS_OK; } NS_IMETHODIMP AsyncStatement::GetState(int32_t *_state) { if (mFinalized) *_state = MOZ_STORAGE_STATEMENT_INVALID; else *_state = MOZ_STORAGE_STATEMENT_READY; return NS_OK; } //////////////////////////////////////////////////////////////////////////////// //// mozIStorageBindingParams BOILERPLATE_BIND_PROXIES( AsyncStatement, if (mFinalized) return NS_ERROR_UNEXPECTED; ) } // namespace storage } // namespace mozilla