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/. */
11 #include "nsProxyRelease.h"
12 #include "nsThreadUtils.h"
13 #include "nsIClassInfoImpl.h"
16 #include "mozStorageBindingParams.h"
17 #include "mozStorageConnection.h"
18 #include "mozStorageAsyncStatementJSHelper.h"
19 #include "mozStorageAsyncStatementParams.h"
20 #include "mozStoragePrivateHelpers.h"
21 #include "mozStorageStatementRow.h"
22 #include "mozStorageStatement.h"
24 #include "mozilla/Logging.h"
26 extern mozilla::LazyLogModule gStorageLog
;
31 ////////////////////////////////////////////////////////////////////////////////
34 NS_IMPL_CI_INTERFACE_GETTER(AsyncStatement
, mozIStorageAsyncStatement
,
35 mozIStorageBaseStatement
, mozIStorageBindingParams
,
36 mozilla::storage::StorageBaseStatementInternal
)
38 class AsyncStatementClassInfo
: public nsIClassInfo
{
40 constexpr AsyncStatementClassInfo() {}
42 NS_DECL_ISUPPORTS_INHERITED
45 GetInterfaces(nsTArray
<nsIID
>& _array
) override
{
46 return NS_CI_INTERFACE_GETTER_NAME(AsyncStatement
)(_array
);
50 GetScriptableHelper(nsIXPCScriptable
** _helper
) override
{
51 static AsyncStatementJSHelper sJSHelper
;
52 *_helper
= &sJSHelper
;
57 GetContractID(nsACString
& aContractID
) override
{
58 aContractID
.SetIsVoid(true);
63 GetClassDescription(nsACString
& aDesc
) override
{
64 aDesc
.SetIsVoid(true);
69 GetClassID(nsCID
** _id
) override
{
75 GetFlags(uint32_t* _flags
) override
{
81 GetClassIDNoAlloc(nsCID
* _cid
) override
{ return NS_ERROR_NOT_AVAILABLE
; }
84 NS_IMETHODIMP_(MozExternalRefCountType
) AsyncStatementClassInfo::AddRef() {
87 NS_IMETHODIMP_(MozExternalRefCountType
) AsyncStatementClassInfo::Release() {
90 NS_IMPL_QUERY_INTERFACE(AsyncStatementClassInfo
, nsIClassInfo
)
92 static AsyncStatementClassInfo sAsyncStatementClassInfo
;
94 ////////////////////////////////////////////////////////////////////////////////
97 AsyncStatement::AsyncStatement()
98 : StorageBaseStatementInternal(), mFinalized(false) {}
100 nsresult
AsyncStatement::initialize(Connection
* aDBConnection
,
101 sqlite3
* aNativeConnection
,
102 const nsACString
& aSQLStatement
) {
103 MOZ_ASSERT(aDBConnection
, "No database connection given!");
104 MOZ_ASSERT(aDBConnection
->isConnectionReadyOnThisThread(),
105 "Database connection should be valid");
106 MOZ_ASSERT(aNativeConnection
, "No native connection given!");
108 mDBConnection
= aDBConnection
;
109 mNativeConnection
= aNativeConnection
;
110 mSQLString
= aSQLStatement
;
112 MOZ_LOG(gStorageLog
, LogLevel::Debug
,
113 ("Inited async statement '%s' (0x%p)", mSQLString
.get(), this));
116 // We want to try and test for LIKE and that consumers are using
117 // escapeStringForLIKE instead of just trusting user input. The idea to
118 // check to see if they are binding a parameter after like instead of just
119 // using a string. We only do this in debug builds because it's expensive!
120 auto c
= nsCaseInsensitiveCStringComparator
;
121 nsACString::const_iterator start
, end
, e
;
122 aSQLStatement
.BeginReading(start
);
123 aSQLStatement
.EndReading(end
);
125 while (::FindInReadable(" LIKE"_ns
, start
, e
, c
)) {
126 // We have a LIKE in here, so we perform our tests
127 // FindInReadable moves the iterator, so we have to get a new one for
128 // each test we perform.
129 nsACString::const_iterator s1
, s2
, s3
;
130 s1
= s2
= s3
= start
;
132 if (!(::FindInReadable(" LIKE ?"_ns
, s1
, end
, c
) ||
133 ::FindInReadable(" LIKE :"_ns
, s2
, end
, c
) ||
134 ::FindInReadable(" LIKE @"_ns
, s3
, end
, c
))) {
135 // At this point, we didn't find a LIKE statement followed by ?, :,
136 // or @, all of which are valid characters for binding a parameter.
137 // We will warn the consumer that they may not be safely using LIKE.
139 "Unsafe use of LIKE detected! Please ensure that you "
140 "are using mozIStorageAsyncStatement::escapeStringForLIKE "
141 "and that you are binding that result to the statement "
142 "to prevent SQL injection attacks.");
145 // resetting start and e
154 mozIStorageBindingParams
* AsyncStatement::getParams() {
157 // If we do not have an array object yet, make it.
159 nsCOMPtr
<mozIStorageBindingParamsArray
> array
;
160 rv
= NewBindingParamsArray(getter_AddRefs(array
));
161 NS_ENSURE_SUCCESS(rv
, nullptr);
163 mParamsArray
= static_cast<BindingParamsArray
*>(array
.get());
166 // If there isn't already any rows added, we'll have to add one to use.
167 if (mParamsArray
->length() == 0) {
168 RefPtr
<AsyncBindingParams
> params(new AsyncBindingParams(mParamsArray
));
169 NS_ENSURE_TRUE(params
, nullptr);
171 rv
= mParamsArray
->AddParams(params
);
172 NS_ENSURE_SUCCESS(rv
, nullptr);
174 // We have to unlock our params because AddParams locks them. This is safe
175 // because no reference to the params object was, or ever will be given out.
176 params
->unlock(nullptr);
178 // We also want to lock our array at this point - we don't want anything to
180 mParamsArray
->lock();
183 return *mParamsArray
->begin();
187 * If we are here then we know there are no pending async executions relying on
188 * us (StatementData holds a reference to us; this also goes for our own
189 * AsyncStatementFinalizer which proxies its release to the calling event
190 * target) and so it is always safe to destroy our sqlite3_stmt if one exists.
191 * We can be destroyed on the caller event target by
192 * garbage-collection/reference counting or on the async event target by the
193 * last execution of a statement that already lost its main-thread refs.
195 AsyncStatement::~AsyncStatement() {
196 destructorAsyncFinalize();
198 // If we are getting destroyed on the wrong event target, proxy the connection
199 // release to the right one.
200 if (!IsOnCurrentSerialEventTarget(mDBConnection
->eventTargetOpenedOn
)) {
201 // NS_ProxyRelase only magic forgets for us if mDBConnection is an
202 // nsCOMPtr. Which it is not; it's a RefPtr.
203 nsCOMPtr
<nsIEventTarget
> target(mDBConnection
->eventTargetOpenedOn
);
204 NS_ProxyRelease("AsyncStatement::mDBConnection", target
,
205 mDBConnection
.forget());
209 ////////////////////////////////////////////////////////////////////////////////
212 NS_IMPL_ADDREF(AsyncStatement
)
213 NS_IMPL_RELEASE(AsyncStatement
)
215 NS_INTERFACE_MAP_BEGIN(AsyncStatement
)
216 NS_INTERFACE_MAP_ENTRY(mozIStorageAsyncStatement
)
217 NS_INTERFACE_MAP_ENTRY(mozIStorageBaseStatement
)
218 NS_INTERFACE_MAP_ENTRY(mozIStorageBindingParams
)
219 NS_INTERFACE_MAP_ENTRY(mozilla::storage::StorageBaseStatementInternal
)
220 if (aIID
.Equals(NS_GET_IID(nsIClassInfo
))) {
221 foundInterface
= static_cast<nsIClassInfo
*>(&sAsyncStatementClassInfo
);
223 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports
, mozIStorageAsyncStatement
)
226 ////////////////////////////////////////////////////////////////////////////////
227 //// StorageBaseStatementInternal
229 Connection
* AsyncStatement::getOwner() { return mDBConnection
; }
231 int AsyncStatement::getAsyncStatement(sqlite3_stmt
** _stmt
) {
233 // Make sure we are never called on the connection's owning event target.
235 !IsOnCurrentSerialEventTarget(mDBConnection
->eventTargetOpenedOn
),
236 "We should only be called on the async event target!");
239 if (!mAsyncStatement
) {
240 int rc
= mDBConnection
->prepareStatement(mNativeConnection
, mSQLString
,
242 if (rc
!= SQLITE_OK
) {
243 MOZ_LOG(gStorageLog
, LogLevel::Error
,
244 ("Sqlite statement prepare error: %d '%s'", rc
,
245 ::sqlite3_errmsg(mNativeConnection
)));
246 MOZ_LOG(gStorageLog
, LogLevel::Error
,
247 ("Statement was: '%s'", mSQLString
.get()));
251 MOZ_LOG(gStorageLog
, LogLevel::Debug
,
252 ("Initialized statement '%s' (0x%p)", mSQLString
.get(),
256 *_stmt
= mAsyncStatement
;
260 nsresult
AsyncStatement::getAsynchronousStatementData(StatementData
& _data
) {
261 if (mFinalized
) return NS_ERROR_UNEXPECTED
;
263 // Pass null for the sqlite3_stmt; it will be requested on demand from the
264 // async event target.
265 _data
= StatementData(nullptr, bindingParamsArray(), this);
270 already_AddRefed
<mozIStorageBindingParams
> AsyncStatement::newBindingParams(
271 mozIStorageBindingParamsArray
* aOwner
) {
272 if (mFinalized
) return nullptr;
274 nsCOMPtr
<mozIStorageBindingParams
> params(new AsyncBindingParams(aOwner
));
275 return params
.forget();
278 ////////////////////////////////////////////////////////////////////////////////
279 //// mozIStorageAsyncStatement
281 // (nothing is specific to mozIStorageAsyncStatement)
283 ////////////////////////////////////////////////////////////////////////////////
284 //// StorageBaseStatementInternal
286 // proxy to StorageBaseStatementInternal using its define helper.
287 MIXIN_IMPL_STORAGEBASESTATEMENTINTERNAL(
288 AsyncStatement
, if (mFinalized
) return NS_ERROR_UNEXPECTED
;)
291 AsyncStatement::Finalize() {
292 if (mFinalized
) return NS_OK
;
296 MOZ_LOG(gStorageLog
, LogLevel::Debug
,
297 ("Finalizing statement '%s'", mSQLString
.get()));
301 // Release the params holder, so it can release the reference to us.
302 mStatementParamsHolder
= nullptr;
308 AsyncStatement::BindParameters(mozIStorageBindingParamsArray
* aParameters
) {
309 if (mFinalized
) return NS_ERROR_UNEXPECTED
;
311 BindingParamsArray
* array
= static_cast<BindingParamsArray
*>(aParameters
);
312 if (array
->getOwner() != this) return NS_ERROR_UNEXPECTED
;
314 if (array
->length() == 0) return NS_ERROR_UNEXPECTED
;
316 mParamsArray
= array
;
317 mParamsArray
->lock();
323 AsyncStatement::GetState(int32_t* _state
) {
325 *_state
= MOZ_STORAGE_STATEMENT_INVALID
;
327 *_state
= MOZ_STORAGE_STATEMENT_READY
;
332 ////////////////////////////////////////////////////////////////////////////////
333 //// mozIStorageBindingParams
335 BOILERPLATE_BIND_PROXIES(AsyncStatement
,
336 if (mFinalized
) return NS_ERROR_UNEXPECTED
;)
338 } // namespace storage
339 } // namespace mozilla