1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
12 #include "nsProxyRelease.h"
13 #include "nsThreadUtils.h"
14 #include "nsIClassInfoImpl.h"
17 #include "mozStorageBindingParams.h"
18 #include "mozStorageConnection.h"
19 #include "mozStorageAsyncStatementJSHelper.h"
20 #include "mozStorageAsyncStatementParams.h"
21 #include "mozStoragePrivateHelpers.h"
22 #include "mozStorageStatementRow.h"
23 #include "mozStorageStatement.h"
25 #include "mozilla/Logging.h"
27 extern mozilla::LazyLogModule gStorageLog
;
32 ////////////////////////////////////////////////////////////////////////////////
35 NS_IMPL_CI_INTERFACE_GETTER(AsyncStatement
, mozIStorageAsyncStatement
,
36 mozIStorageBaseStatement
, mozIStorageBindingParams
,
37 mozilla::storage::StorageBaseStatementInternal
)
39 class AsyncStatementClassInfo
: public nsIClassInfo
{
41 constexpr AsyncStatementClassInfo() {}
43 NS_DECL_ISUPPORTS_INHERITED
46 GetInterfaces(nsTArray
<nsIID
>& _array
) override
{
47 return NS_CI_INTERFACE_GETTER_NAME(AsyncStatement
)(_array
);
51 GetScriptableHelper(nsIXPCScriptable
** _helper
) override
{
52 static AsyncStatementJSHelper sJSHelper
;
53 *_helper
= &sJSHelper
;
58 GetContractID(nsACString
& aContractID
) override
{
59 aContractID
.SetIsVoid(true);
64 GetClassDescription(nsACString
& aDesc
) override
{
65 aDesc
.SetIsVoid(true);
70 GetClassID(nsCID
** _id
) override
{
76 GetFlags(uint32_t* _flags
) override
{
82 GetClassIDNoAlloc(nsCID
* _cid
) override
{ return NS_ERROR_NOT_AVAILABLE
; }
85 NS_IMETHODIMP_(MozExternalRefCountType
) AsyncStatementClassInfo::AddRef() {
88 NS_IMETHODIMP_(MozExternalRefCountType
) AsyncStatementClassInfo::Release() {
91 NS_IMPL_QUERY_INTERFACE(AsyncStatementClassInfo
, nsIClassInfo
)
93 static AsyncStatementClassInfo sAsyncStatementClassInfo
;
95 ////////////////////////////////////////////////////////////////////////////////
98 AsyncStatement::AsyncStatement()
99 : StorageBaseStatementInternal(), mFinalized(false) {}
101 nsresult
AsyncStatement::initialize(Connection
* aDBConnection
,
102 sqlite3
* aNativeConnection
,
103 const nsACString
& aSQLStatement
) {
104 MOZ_ASSERT(aDBConnection
, "No database connection given!");
105 MOZ_ASSERT(aDBConnection
->isConnectionReadyOnThisThread(),
106 "Database connection should be valid");
107 MOZ_ASSERT(aNativeConnection
, "No native connection given!");
109 mDBConnection
= aDBConnection
;
110 mNativeConnection
= aNativeConnection
;
111 mSQLString
= aSQLStatement
;
113 MOZ_LOG(gStorageLog
, LogLevel::Debug
,
114 ("Inited async statement '%s' (0x%p)", mSQLString
.get(), this));
117 // We want to try and test for LIKE and that consumers are using
118 // escapeStringForLIKE instead of just trusting user input. The idea to
119 // check to see if they are binding a parameter after like instead of just
120 // using a string. We only do this in debug builds because it's expensive!
121 auto c
= nsCaseInsensitiveCStringComparator
;
122 nsACString::const_iterator start
, end
, e
;
123 aSQLStatement
.BeginReading(start
);
124 aSQLStatement
.EndReading(end
);
126 while (::FindInReadable(" LIKE"_ns
, start
, e
, c
)) {
127 // We have a LIKE in here, so we perform our tests
128 // FindInReadable moves the iterator, so we have to get a new one for
129 // each test we perform.
130 nsACString::const_iterator s1
, s2
, s3
;
131 s1
= s2
= s3
= start
;
133 if (!(::FindInReadable(" LIKE ?"_ns
, s1
, end
, c
) ||
134 ::FindInReadable(" LIKE :"_ns
, s2
, end
, c
) ||
135 ::FindInReadable(" LIKE @"_ns
, s3
, end
, c
))) {
136 // At this point, we didn't find a LIKE statement followed by ?, :,
137 // or @, all of which are valid characters for binding a parameter.
138 // We will warn the consumer that they may not be safely using LIKE.
140 "Unsafe use of LIKE detected! Please ensure that you "
141 "are using mozIStorageAsyncStatement::escapeStringForLIKE "
142 "and that you are binding that result to the statement "
143 "to prevent SQL injection attacks.");
146 // resetting start and e
155 mozIStorageBindingParams
* AsyncStatement::getParams() {
158 // If we do not have an array object yet, make it.
160 nsCOMPtr
<mozIStorageBindingParamsArray
> array
;
161 rv
= NewBindingParamsArray(getter_AddRefs(array
));
162 NS_ENSURE_SUCCESS(rv
, nullptr);
164 mParamsArray
= static_cast<BindingParamsArray
*>(array
.get());
167 // If there isn't already any rows added, we'll have to add one to use.
168 if (mParamsArray
->length() == 0) {
169 RefPtr
<AsyncBindingParams
> params(new AsyncBindingParams(mParamsArray
));
170 NS_ENSURE_TRUE(params
, nullptr);
172 rv
= mParamsArray
->AddParams(params
);
173 NS_ENSURE_SUCCESS(rv
, nullptr);
175 // We have to unlock our params because AddParams locks them. This is safe
176 // because no reference to the params object was, or ever will be given out.
177 params
->unlock(nullptr);
179 // We also want to lock our array at this point - we don't want anything to
181 mParamsArray
->lock();
184 return *mParamsArray
->begin();
188 * If we are here then we know there are no pending async executions relying on
189 * us (StatementData holds a reference to us; this also goes for our own
190 * AsyncStatementFinalizer which proxies its release to the calling thread) and
191 * so it is always safe to destroy our sqlite3_stmt if one exists. We can be
192 * destroyed on the caller thread by garbage-collection/reference counting or on
193 * the async thread by the last execution of a statement that already lost its
196 AsyncStatement::~AsyncStatement() {
197 destructorAsyncFinalize();
199 // If we are getting destroyed on the wrong thread, proxy the connection
200 // release to the right thread. I'm not sure why we do this.
201 bool onCallingThread
= false;
202 (void)mDBConnection
->threadOpenedOn
->IsOnCurrentThread(&onCallingThread
);
203 if (!onCallingThread
) {
204 // NS_ProxyRelase only magic forgets for us if mDBConnection is an
205 // nsCOMPtr. Which it is not; it's an nsRefPtr.
206 nsCOMPtr
<nsIThread
> targetThread(mDBConnection
->threadOpenedOn
);
207 NS_ProxyRelease("AsyncStatement::mDBConnection", targetThread
,
208 mDBConnection
.forget());
212 ////////////////////////////////////////////////////////////////////////////////
215 NS_IMPL_ADDREF(AsyncStatement
)
216 NS_IMPL_RELEASE(AsyncStatement
)
218 NS_INTERFACE_MAP_BEGIN(AsyncStatement
)
219 NS_INTERFACE_MAP_ENTRY(mozIStorageAsyncStatement
)
220 NS_INTERFACE_MAP_ENTRY(mozIStorageBaseStatement
)
221 NS_INTERFACE_MAP_ENTRY(mozIStorageBindingParams
)
222 NS_INTERFACE_MAP_ENTRY(mozilla::storage::StorageBaseStatementInternal
)
223 if (aIID
.Equals(NS_GET_IID(nsIClassInfo
))) {
224 foundInterface
= static_cast<nsIClassInfo
*>(&sAsyncStatementClassInfo
);
226 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports
, mozIStorageAsyncStatement
)
229 ////////////////////////////////////////////////////////////////////////////////
230 //// StorageBaseStatementInternal
232 Connection
* AsyncStatement::getOwner() { return mDBConnection
; }
234 int AsyncStatement::getAsyncStatement(sqlite3_stmt
** _stmt
) {
236 // Make sure we are never called on the connection's owning thread.
237 bool onOpenedThread
= false;
238 (void)mDBConnection
->threadOpenedOn
->IsOnCurrentThread(&onOpenedThread
);
239 NS_ASSERTION(!onOpenedThread
,
240 "We should only be called on the async thread!");
243 if (!mAsyncStatement
) {
244 int rc
= mDBConnection
->prepareStatement(mNativeConnection
, mSQLString
,
246 if (rc
!= SQLITE_OK
) {
247 MOZ_LOG(gStorageLog
, LogLevel::Error
,
248 ("Sqlite statement prepare error: %d '%s'", rc
,
249 ::sqlite3_errmsg(mNativeConnection
)));
250 MOZ_LOG(gStorageLog
, LogLevel::Error
,
251 ("Statement was: '%s'", mSQLString
.get()));
255 MOZ_LOG(gStorageLog
, LogLevel::Debug
,
256 ("Initialized statement '%s' (0x%p)", mSQLString
.get(),
260 *_stmt
= mAsyncStatement
;
264 nsresult
AsyncStatement::getAsynchronousStatementData(StatementData
& _data
) {
265 if (mFinalized
) return NS_ERROR_UNEXPECTED
;
267 // Pass null for the sqlite3_stmt; it will be requested on demand from the
269 _data
= StatementData(nullptr, bindingParamsArray(), this);
274 already_AddRefed
<mozIStorageBindingParams
> AsyncStatement::newBindingParams(
275 mozIStorageBindingParamsArray
* aOwner
) {
276 if (mFinalized
) return nullptr;
278 nsCOMPtr
<mozIStorageBindingParams
> params(new AsyncBindingParams(aOwner
));
279 return params
.forget();
282 ////////////////////////////////////////////////////////////////////////////////
283 //// mozIStorageAsyncStatement
285 // (nothing is specific to mozIStorageAsyncStatement)
287 ////////////////////////////////////////////////////////////////////////////////
288 //// StorageBaseStatementInternal
290 // proxy to StorageBaseStatementInternal using its define helper.
291 MIXIN_IMPL_STORAGEBASESTATEMENTINTERNAL(
292 AsyncStatement
, if (mFinalized
) return NS_ERROR_UNEXPECTED
;)
295 AsyncStatement::Finalize() {
296 if (mFinalized
) return NS_OK
;
300 MOZ_LOG(gStorageLog
, LogLevel::Debug
,
301 ("Finalizing statement '%s'", mSQLString
.get()));
305 // Release the params holder, so it can release the reference to us.
306 mStatementParamsHolder
= nullptr;
312 AsyncStatement::BindParameters(mozIStorageBindingParamsArray
* aParameters
) {
313 if (mFinalized
) return NS_ERROR_UNEXPECTED
;
315 BindingParamsArray
* array
= static_cast<BindingParamsArray
*>(aParameters
);
316 if (array
->getOwner() != this) return NS_ERROR_UNEXPECTED
;
318 if (array
->length() == 0) return NS_ERROR_UNEXPECTED
;
320 mParamsArray
= array
;
321 mParamsArray
->lock();
327 AsyncStatement::GetState(int32_t* _state
) {
329 *_state
= MOZ_STORAGE_STATEMENT_INVALID
;
331 *_state
= MOZ_STORAGE_STATEMENT_READY
;
336 ////////////////////////////////////////////////////////////////////////////////
337 //// mozIStorageBindingParams
339 BOILERPLATE_BIND_PROXIES(AsyncStatement
,
340 if (mFinalized
) return NS_ERROR_UNEXPECTED
;)
342 } // namespace storage
343 } // namespace mozilla