diff options
Diffstat (limited to 'storage/mozStorageAsyncStatement.cpp')
-rw-r--r-- | storage/mozStorageAsyncStatement.cpp | 343 |
1 files changed, 343 insertions, 0 deletions
diff --git a/storage/mozStorageAsyncStatement.cpp b/storage/mozStorageAsyncStatement.cpp new file mode 100644 index 0000000000..ba050e9d33 --- /dev/null +++ b/storage/mozStorageAsyncStatement.cpp @@ -0,0 +1,343 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * 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 <limits.h> +#include <stdio.h> + +#include "nsError.h" +#include "nsMemory.h" +#include "nsProxyRelease.h" +#include "nsThreadUtils.h" +#include "nsIClassInfoImpl.h" +#include "Variant.h" + +#include "mozStorageBindingParams.h" +#include "mozStorageConnection.h" +#include "mozStorageAsyncStatementJSHelper.h" +#include "mozStorageAsyncStatementParams.h" +#include "mozStoragePrivateHelpers.h" +#include "mozStorageStatementRow.h" +#include "mozStorageStatement.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(nsTArray<nsIID>& _array) override { + return NS_CI_INTERFACE_GETTER_NAME(AsyncStatement)(_array); + } + + NS_IMETHOD + GetScriptableHelper(nsIXPCScriptable** _helper) override { + static AsyncStatementJSHelper sJSHelper; + *_helper = &sJSHelper; + return NS_OK; + } + + NS_IMETHOD + GetContractID(nsACString& aContractID) override { + aContractID.SetIsVoid(true); + return NS_OK; + } + + NS_IMETHOD + GetClassDescription(nsACString& aDesc) override { + aDesc.SetIsVoid(true); + 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->isConnectionReadyOnThisThread(), + "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(), this)); + +#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! + auto c = nsCaseInsensitiveCStringComparator; + nsACString::const_iterator start, end, e; + aSQLStatement.BeginReading(start); + aSQLStatement.EndReading(end); + e = end; + while (::FindInReadable(" LIKE"_ns, 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(" LIKE ?"_ns, s1, end, c) || + ::FindInReadable(" LIKE :"_ns, s2, end, c) || + ::FindInReadable(" LIKE @"_ns, 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<mozIStorageBindingParamsArray> array; + rv = NewBindingParamsArray(getter_AddRefs(array)); + NS_ENSURE_SUCCESS(rv, nullptr); + + mParamsArray = static_cast<BindingParamsArray*>(array.get()); + } + + // If there isn't already any rows added, we'll have to add one to use. + if (mParamsArray->length() == 0) { + RefPtr<AsyncBindingParams> 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<nsIThread> targetThread(mDBConnection->threadOpenedOn); + NS_ProxyRelease("AsyncStatement::mDBConnection", 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<nsIClassInfo*>(&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<mozIStorageBindingParams> AsyncStatement::newBindingParams( + mozIStorageBindingParamsArray* aOwner) { + if (mFinalized) return nullptr; + + nsCOMPtr<mozIStorageBindingParams> 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<BindingParamsArray*>(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 |