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() = default;
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() : mFinalized(false) {}
99 nsresult
AsyncStatement::initialize(Connection
* aDBConnection
,
100 sqlite3
* aNativeConnection
,
101 const nsACString
& aSQLStatement
) {
102 MOZ_ASSERT(aDBConnection
, "No database connection given!");
103 MOZ_ASSERT(aDBConnection
->isConnectionReadyOnThisThread(),
104 "Database connection should be valid");
105 MOZ_ASSERT(aNativeConnection
, "No native connection given!");
107 mDBConnection
= aDBConnection
;
108 mNativeConnection
= aNativeConnection
;
109 mSQLString
= aSQLStatement
;
111 MOZ_LOG(gStorageLog
, LogLevel::Debug
,
112 ("Inited async statement '%s' (0x%p)", mSQLString
.get(), this));
115 // We want to try and test for LIKE and that consumers are using
116 // escapeStringForLIKE instead of just trusting user input. The idea to
117 // check to see if they are binding a parameter after like instead of just
118 // using a string. We only do this in debug builds because it's expensive!
119 auto c
= nsCaseInsensitiveCStringComparator
;
120 nsACString::const_iterator start
, end
, e
;
121 aSQLStatement
.BeginReading(start
);
122 aSQLStatement
.EndReading(end
);
124 while (::FindInReadable(" LIKE"_ns
, start
, e
, c
)) {
125 // We have a LIKE in here, so we perform our tests
126 // FindInReadable moves the iterator, so we have to get a new one for
127 // each test we perform.
128 nsACString::const_iterator s1
, s2
, s3
;
129 s1
= s2
= s3
= start
;
131 if (!(::FindInReadable(" LIKE ?"_ns
, s1
, end
, c
) ||
132 ::FindInReadable(" LIKE :"_ns
, s2
, end
, c
) ||
133 ::FindInReadable(" LIKE @"_ns
, s3
, end
, c
))) {
134 // At this point, we didn't find a LIKE statement followed by ?, :,
135 // or @, all of which are valid characters for binding a parameter.
136 // We will warn the consumer that they may not be safely using LIKE.
138 "Unsafe use of LIKE detected! Please ensure that you "
139 "are using mozIStorageAsyncStatement::escapeStringForLIKE "
140 "and that you are binding that result to the statement "
141 "to prevent SQL injection attacks.");
144 // resetting start and e
153 mozIStorageBindingParams
* AsyncStatement::getParams() {
156 // If we do not have an array object yet, make it.
158 nsCOMPtr
<mozIStorageBindingParamsArray
> array
;
159 rv
= NewBindingParamsArray(getter_AddRefs(array
));
160 NS_ENSURE_SUCCESS(rv
, nullptr);
162 mParamsArray
= static_cast<BindingParamsArray
*>(array
.get());
165 // If there isn't already any rows added, we'll have to add one to use.
166 if (mParamsArray
->length() == 0) {
167 RefPtr
<AsyncBindingParams
> params(new AsyncBindingParams(mParamsArray
));
168 NS_ENSURE_TRUE(params
, nullptr);
170 rv
= mParamsArray
->AddParams(params
);
171 NS_ENSURE_SUCCESS(rv
, nullptr);
173 // We have to unlock our params because AddParams locks them. This is safe
174 // because no reference to the params object was, or ever will be given out.
175 params
->unlock(nullptr);
177 // We also want to lock our array at this point - we don't want anything to
179 mParamsArray
->lock();
182 return *mParamsArray
->begin();
186 * If we are here then we know there are no pending async executions relying on
187 * us (StatementData holds a reference to us; this also goes for our own
188 * AsyncStatementFinalizer which proxies its release to the calling event
189 * target) and so it is always safe to destroy our sqlite3_stmt if one exists.
190 * We can be destroyed on the caller event target by
191 * garbage-collection/reference counting or on the async event target by the
192 * last execution of a statement that already lost its main-thread refs.
194 AsyncStatement::~AsyncStatement() {
195 destructorAsyncFinalize();
197 // If we are getting destroyed on the wrong event target, proxy the connection
198 // release to the right one.
199 if (!IsOnCurrentSerialEventTarget(mDBConnection
->eventTargetOpenedOn
)) {
200 // NS_ProxyRelase only magic forgets for us if mDBConnection is an
201 // nsCOMPtr. Which it is not; it's a RefPtr.
202 nsCOMPtr
<nsIEventTarget
> target(mDBConnection
->eventTargetOpenedOn
);
203 NS_ProxyRelease("AsyncStatement::mDBConnection", target
,
204 mDBConnection
.forget());
208 ////////////////////////////////////////////////////////////////////////////////
211 NS_IMPL_ADDREF(AsyncStatement
)
212 NS_IMPL_RELEASE(AsyncStatement
)
214 NS_INTERFACE_MAP_BEGIN(AsyncStatement
)
215 NS_INTERFACE_MAP_ENTRY(mozIStorageAsyncStatement
)
216 NS_INTERFACE_MAP_ENTRY(mozIStorageBaseStatement
)
217 NS_INTERFACE_MAP_ENTRY(mozIStorageBindingParams
)
218 NS_INTERFACE_MAP_ENTRY(mozilla::storage::StorageBaseStatementInternal
)
219 if (aIID
.Equals(NS_GET_IID(nsIClassInfo
))) {
220 foundInterface
= static_cast<nsIClassInfo
*>(&sAsyncStatementClassInfo
);
222 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports
, mozIStorageAsyncStatement
)
225 ////////////////////////////////////////////////////////////////////////////////
226 //// StorageBaseStatementInternal
228 Connection
* AsyncStatement::getOwner() { return mDBConnection
; }
230 int AsyncStatement::getAsyncStatement(sqlite3_stmt
** _stmt
) {
232 // Make sure we are never called on the connection's owning event target.
234 !IsOnCurrentSerialEventTarget(mDBConnection
->eventTargetOpenedOn
),
235 "We should only be called on the async event target!");
238 if (!mAsyncStatement
) {
239 int rc
= mDBConnection
->prepareStatement(mNativeConnection
, mSQLString
,
241 if (rc
!= SQLITE_OK
) {
242 MOZ_LOG(gStorageLog
, LogLevel::Error
,
243 ("Sqlite statement prepare error: %d '%s'", rc
,
244 ::sqlite3_errmsg(mNativeConnection
)));
245 MOZ_LOG(gStorageLog
, LogLevel::Error
,
246 ("Statement was: '%s'", mSQLString
.get()));
250 MOZ_LOG(gStorageLog
, LogLevel::Debug
,
251 ("Initialized statement '%s' (0x%p)", mSQLString
.get(),
255 *_stmt
= mAsyncStatement
;
259 nsresult
AsyncStatement::getAsynchronousStatementData(StatementData
& _data
) {
260 if (mFinalized
) return NS_ERROR_UNEXPECTED
;
262 // Pass null for the sqlite3_stmt; it will be requested on demand from the
263 // async event target.
264 _data
= StatementData(nullptr, bindingParamsArray(), this);
269 already_AddRefed
<mozIStorageBindingParams
> AsyncStatement::newBindingParams(
270 mozIStorageBindingParamsArray
* aOwner
) {
271 if (mFinalized
) return nullptr;
273 nsCOMPtr
<mozIStorageBindingParams
> params(new AsyncBindingParams(aOwner
));
274 return params
.forget();
277 ////////////////////////////////////////////////////////////////////////////////
278 //// mozIStorageAsyncStatement
280 // (nothing is specific to mozIStorageAsyncStatement)
282 ////////////////////////////////////////////////////////////////////////////////
283 //// StorageBaseStatementInternal
285 // proxy to StorageBaseStatementInternal using its define helper.
286 MIXIN_IMPL_STORAGEBASESTATEMENTINTERNAL(
287 AsyncStatement
, if (mFinalized
) return NS_ERROR_UNEXPECTED
;)
290 AsyncStatement::Finalize() {
291 if (mFinalized
) return NS_OK
;
295 MOZ_LOG(gStorageLog
, LogLevel::Debug
,
296 ("Finalizing statement '%s'", mSQLString
.get()));
300 // Release the params holder, so it can release the reference to us.
301 mStatementParamsHolder
= nullptr;
307 AsyncStatement::BindParameters(mozIStorageBindingParamsArray
* aParameters
) {
308 if (mFinalized
) return NS_ERROR_UNEXPECTED
;
310 BindingParamsArray
* array
= static_cast<BindingParamsArray
*>(aParameters
);
311 if (array
->getOwner() != this) return NS_ERROR_UNEXPECTED
;
313 if (array
->length() == 0) return NS_ERROR_UNEXPECTED
;
315 mParamsArray
= array
;
316 mParamsArray
->lock();
322 AsyncStatement::GetState(int32_t* _state
) {
324 *_state
= MOZ_STORAGE_STATEMENT_INVALID
;
326 *_state
= MOZ_STORAGE_STATEMENT_READY
;
331 ////////////////////////////////////////////////////////////////////////////////
332 //// mozIStorageBindingParams
334 BOILERPLATE_BIND_PROXIES(AsyncStatement
,
335 if (mFinalized
) return NS_ERROR_UNEXPECTED
;)
337 } // namespace storage
338 } // namespace mozilla