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 "mozIStorageError.h"
19 #include "mozStorageBindingParams.h"
20 #include "mozStorageConnection.h"
21 #include "mozStorageAsyncStatementJSHelper.h"
22 #include "mozStorageAsyncStatementParams.h"
23 #include "mozStoragePrivateHelpers.h"
24 #include "mozStorageStatementRow.h"
25 #include "mozStorageStatement.h"
27 #include "mozilla/Logging.h"
29 extern mozilla::LazyLogModule gStorageLog
;
34 ////////////////////////////////////////////////////////////////////////////////
37 NS_IMPL_CI_INTERFACE_GETTER(AsyncStatement
,
38 mozIStorageAsyncStatement
,
39 mozIStorageBaseStatement
,
40 mozIStorageBindingParams
,
41 mozilla::storage::StorageBaseStatementInternal
)
43 class AsyncStatementClassInfo
: public nsIClassInfo
46 constexpr AsyncStatementClassInfo() {}
48 NS_DECL_ISUPPORTS_INHERITED
51 GetInterfaces(uint32_t *_count
, nsIID
***_array
) override
53 return NS_CI_INTERFACE_GETTER_NAME(AsyncStatement
)(_count
, _array
);
57 GetScriptableHelper(nsIXPCScriptable
**_helper
) override
59 static AsyncStatementJSHelper sJSHelper
;
60 *_helper
= &sJSHelper
;
65 GetContractID(nsACString
& aContractID
) override
67 aContractID
.SetIsVoid(true);
72 GetClassDescription(nsACString
& aDesc
) override
74 aDesc
.SetIsVoid(true);
79 GetClassID(nsCID
**_id
) override
86 GetFlags(uint32_t *_flags
) override
93 GetClassIDNoAlloc(nsCID
*_cid
) override
95 return NS_ERROR_NOT_AVAILABLE
;
99 NS_IMETHODIMP_(MozExternalRefCountType
) AsyncStatementClassInfo::AddRef() { return 2; }
100 NS_IMETHODIMP_(MozExternalRefCountType
) AsyncStatementClassInfo::Release() { return 1; }
101 NS_IMPL_QUERY_INTERFACE(AsyncStatementClassInfo
, nsIClassInfo
)
103 static AsyncStatementClassInfo sAsyncStatementClassInfo
;
105 ////////////////////////////////////////////////////////////////////////////////
108 AsyncStatement::AsyncStatement()
109 : StorageBaseStatementInternal()
115 AsyncStatement::initialize(Connection
*aDBConnection
,
116 sqlite3
*aNativeConnection
,
117 const nsACString
&aSQLStatement
)
119 MOZ_ASSERT(aDBConnection
, "No database connection given!");
120 MOZ_ASSERT(aDBConnection
->isConnectionReadyOnThisThread(), "Database connection should be valid");
121 MOZ_ASSERT(aNativeConnection
, "No native connection given!");
123 mDBConnection
= aDBConnection
;
124 mNativeConnection
= aNativeConnection
;
125 mSQLString
= aSQLStatement
;
127 MOZ_LOG(gStorageLog
, LogLevel::Debug
, ("Inited async statement '%s' (0x%p)",
128 mSQLString
.get(), this));
131 // We want to try and test for LIKE and that consumers are using
132 // escapeStringForLIKE instead of just trusting user input. The idea to
133 // check to see if they are binding a parameter after like instead of just
134 // using a string. We only do this in debug builds because it's expensive!
135 const nsCaseInsensitiveCStringComparator c
;
136 nsACString::const_iterator start
, end
, e
;
137 aSQLStatement
.BeginReading(start
);
138 aSQLStatement
.EndReading(end
);
140 while (::FindInReadable(NS_LITERAL_CSTRING(" LIKE"), start
, e
, c
)) {
141 // We have a LIKE in here, so we perform our tests
142 // FindInReadable moves the iterator, so we have to get a new one for
143 // each test we perform.
144 nsACString::const_iterator s1
, s2
, s3
;
145 s1
= s2
= s3
= start
;
147 if (!(::FindInReadable(NS_LITERAL_CSTRING(" LIKE ?"), s1
, end
, c
) ||
148 ::FindInReadable(NS_LITERAL_CSTRING(" LIKE :"), s2
, end
, c
) ||
149 ::FindInReadable(NS_LITERAL_CSTRING(" LIKE @"), s3
, end
, c
))) {
150 // At this point, we didn't find a LIKE statement followed by ?, :,
151 // or @, all of which are valid characters for binding a parameter.
152 // We will warn the consumer that they may not be safely using LIKE.
153 NS_WARNING("Unsafe use of LIKE detected! Please ensure that you "
154 "are using mozIStorageAsyncStatement::escapeStringForLIKE "
155 "and that you are binding that result to the statement "
156 "to prevent SQL injection attacks.");
159 // resetting start and e
168 mozIStorageBindingParams
*
169 AsyncStatement::getParams()
173 // If we do not have an array object yet, make it.
175 nsCOMPtr
<mozIStorageBindingParamsArray
> array
;
176 rv
= NewBindingParamsArray(getter_AddRefs(array
));
177 NS_ENSURE_SUCCESS(rv
, nullptr);
179 mParamsArray
= static_cast<BindingParamsArray
*>(array
.get());
182 // If there isn't already any rows added, we'll have to add one to use.
183 if (mParamsArray
->length() == 0) {
184 RefPtr
<AsyncBindingParams
> params(new AsyncBindingParams(mParamsArray
));
185 NS_ENSURE_TRUE(params
, nullptr);
187 rv
= mParamsArray
->AddParams(params
);
188 NS_ENSURE_SUCCESS(rv
, nullptr);
190 // We have to unlock our params because AddParams locks them. This is safe
191 // because no reference to the params object was, or ever will be given out.
192 params
->unlock(nullptr);
194 // We also want to lock our array at this point - we don't want anything to
196 mParamsArray
->lock();
199 return *mParamsArray
->begin();
203 * If we are here then we know there are no pending async executions relying on
204 * us (StatementData holds a reference to us; this also goes for our own
205 * AsyncStatementFinalizer which proxies its release to the calling thread) and
206 * so it is always safe to destroy our sqlite3_stmt if one exists. We can be
207 * destroyed on the caller thread by garbage-collection/reference counting or on
208 * the async thread by the last execution of a statement that already lost its
211 AsyncStatement::~AsyncStatement()
213 destructorAsyncFinalize();
215 // If we are getting destroyed on the wrong thread, proxy the connection
216 // release to the right thread. I'm not sure why we do this.
217 bool onCallingThread
= false;
218 (void)mDBConnection
->threadOpenedOn
->IsOnCurrentThread(&onCallingThread
);
219 if (!onCallingThread
) {
220 // NS_ProxyRelase only magic forgets for us if mDBConnection is an
221 // nsCOMPtr. Which it is not; it's an nsRefPtr.
222 nsCOMPtr
<nsIThread
> targetThread(mDBConnection
->threadOpenedOn
);
224 "AsyncStatement::mDBConnection",
225 targetThread
, mDBConnection
.forget());
229 ////////////////////////////////////////////////////////////////////////////////
232 NS_IMPL_ADDREF(AsyncStatement
)
233 NS_IMPL_RELEASE(AsyncStatement
)
235 NS_INTERFACE_MAP_BEGIN(AsyncStatement
)
236 NS_INTERFACE_MAP_ENTRY(mozIStorageAsyncStatement
)
237 NS_INTERFACE_MAP_ENTRY(mozIStorageBaseStatement
)
238 NS_INTERFACE_MAP_ENTRY(mozIStorageBindingParams
)
239 NS_INTERFACE_MAP_ENTRY(mozilla::storage::StorageBaseStatementInternal
)
240 if (aIID
.Equals(NS_GET_IID(nsIClassInfo
))) {
241 foundInterface
= static_cast<nsIClassInfo
*>(&sAsyncStatementClassInfo
);
244 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports
, mozIStorageAsyncStatement
)
248 ////////////////////////////////////////////////////////////////////////////////
249 //// StorageBaseStatementInternal
252 AsyncStatement::getOwner()
254 return mDBConnection
;
258 AsyncStatement::getAsyncStatement(sqlite3_stmt
**_stmt
)
261 // Make sure we are never called on the connection's owning thread.
262 bool onOpenedThread
= false;
263 (void)mDBConnection
->threadOpenedOn
->IsOnCurrentThread(&onOpenedThread
);
264 NS_ASSERTION(!onOpenedThread
,
265 "We should only be called on the async thread!");
268 if (!mAsyncStatement
) {
269 int rc
= mDBConnection
->prepareStatement(mNativeConnection
, mSQLString
,
271 if (rc
!= SQLITE_OK
) {
272 MOZ_LOG(gStorageLog
, LogLevel::Error
,
273 ("Sqlite statement prepare error: %d '%s'", rc
,
274 ::sqlite3_errmsg(mNativeConnection
)));
275 MOZ_LOG(gStorageLog
, LogLevel::Error
,
276 ("Statement was: '%s'", mSQLString
.get()));
280 MOZ_LOG(gStorageLog
, LogLevel::Debug
, ("Initialized statement '%s' (0x%p)",
285 *_stmt
= mAsyncStatement
;
290 AsyncStatement::getAsynchronousStatementData(StatementData
&_data
)
293 return NS_ERROR_UNEXPECTED
;
295 // Pass null for the sqlite3_stmt; it will be requested on demand from the
297 _data
= StatementData(nullptr, bindingParamsArray(), this);
302 already_AddRefed
<mozIStorageBindingParams
>
303 AsyncStatement::newBindingParams(mozIStorageBindingParamsArray
*aOwner
)
308 nsCOMPtr
<mozIStorageBindingParams
> params(new AsyncBindingParams(aOwner
));
309 return params
.forget();
313 ////////////////////////////////////////////////////////////////////////////////
314 //// mozIStorageAsyncStatement
316 // (nothing is specific to mozIStorageAsyncStatement)
318 ////////////////////////////////////////////////////////////////////////////////
319 //// StorageBaseStatementInternal
321 // proxy to StorageBaseStatementInternal using its define helper.
322 MIXIN_IMPL_STORAGEBASESTATEMENTINTERNAL(
324 if (mFinalized
) return NS_ERROR_UNEXPECTED
;)
327 AsyncStatement::Finalize()
334 MOZ_LOG(gStorageLog
, LogLevel::Debug
, ("Finalizing statement '%s'",
339 // Release the params holder, so it can release the reference to us.
340 mStatementParamsHolder
= nullptr;
346 AsyncStatement::BindParameters(mozIStorageBindingParamsArray
*aParameters
)
349 return NS_ERROR_UNEXPECTED
;
351 BindingParamsArray
*array
= static_cast<BindingParamsArray
*>(aParameters
);
352 if (array
->getOwner() != this)
353 return NS_ERROR_UNEXPECTED
;
355 if (array
->length() == 0)
356 return NS_ERROR_UNEXPECTED
;
358 mParamsArray
= array
;
359 mParamsArray
->lock();
365 AsyncStatement::GetState(int32_t *_state
)
368 *_state
= MOZ_STORAGE_STATEMENT_INVALID
;
370 *_state
= MOZ_STORAGE_STATEMENT_READY
;
375 ////////////////////////////////////////////////////////////////////////////////
376 //// mozIStorageBindingParams
378 BOILERPLATE_BIND_PROXIES(
380 if (mFinalized
) return NS_ERROR_UNEXPECTED
;
383 } // namespace storage
384 } // namespace mozilla