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