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/. */
6
7 #include <limits.h>
8 #include <stdio.h>
9
10 #include "nsError.h"
11 #include "nsMemory.h"
12 #include "nsProxyRelease.h"
13 #include "nsThreadUtils.h"
14 #include "nsIClassInfoImpl.h"
15 #include "Variant.h"
16
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"
24
25 #include "mozilla/Logging.h"
26
27 extern mozilla::LazyLogModule gStorageLog;
28
29 namespace mozilla {
30 namespace storage {
31
32 ////////////////////////////////////////////////////////////////////////////////
33 //// nsIClassInfo
34
35 NS_IMPL_CI_INTERFACE_GETTER(AsyncStatement, mozIStorageAsyncStatement,
36 mozIStorageBaseStatement, mozIStorageBindingParams,
37 mozilla::storage::StorageBaseStatementInternal)
38
39 class AsyncStatementClassInfo : public nsIClassInfo {
40 public:
AsyncStatementClassInfo()41 constexpr AsyncStatementClassInfo() {}
42
43 NS_DECL_ISUPPORTS_INHERITED
44
45 NS_IMETHOD
GetInterfaces(nsTArray<nsIID> & _array)46 GetInterfaces(nsTArray<nsIID>& _array) override {
47 return NS_CI_INTERFACE_GETTER_NAME(AsyncStatement)(_array);
48 }
49
50 NS_IMETHOD
GetScriptableHelper(nsIXPCScriptable ** _helper)51 GetScriptableHelper(nsIXPCScriptable** _helper) override {
52 static AsyncStatementJSHelper sJSHelper;
53 *_helper = &sJSHelper;
54 return NS_OK;
55 }
56
57 NS_IMETHOD
GetContractID(nsACString & aContractID)58 GetContractID(nsACString& aContractID) override {
59 aContractID.SetIsVoid(true);
60 return NS_OK;
61 }
62
63 NS_IMETHOD
GetClassDescription(nsACString & aDesc)64 GetClassDescription(nsACString& aDesc) override {
65 aDesc.SetIsVoid(true);
66 return NS_OK;
67 }
68
69 NS_IMETHOD
GetClassID(nsCID ** _id)70 GetClassID(nsCID** _id) override {
71 *_id = nullptr;
72 return NS_OK;
73 }
74
75 NS_IMETHOD
GetFlags(uint32_t * _flags)76 GetFlags(uint32_t* _flags) override {
77 *_flags = 0;
78 return NS_OK;
79 }
80
81 NS_IMETHOD
GetClassIDNoAlloc(nsCID * _cid)82 GetClassIDNoAlloc(nsCID* _cid) override { return NS_ERROR_NOT_AVAILABLE; }
83 };
84
NS_IMETHODIMP_(MozExternalRefCountType)85 NS_IMETHODIMP_(MozExternalRefCountType) AsyncStatementClassInfo::AddRef() {
86 return 2;
87 }
NS_IMETHODIMP_(MozExternalRefCountType)88 NS_IMETHODIMP_(MozExternalRefCountType) AsyncStatementClassInfo::Release() {
89 return 1;
90 }
91 NS_IMPL_QUERY_INTERFACE(AsyncStatementClassInfo, nsIClassInfo)
92
93 static AsyncStatementClassInfo sAsyncStatementClassInfo;
94
95 ////////////////////////////////////////////////////////////////////////////////
96 //// AsyncStatement
97
AsyncStatement()98 AsyncStatement::AsyncStatement()
99 : StorageBaseStatementInternal(), mFinalized(false) {}
100
initialize(Connection * aDBConnection,sqlite3 * aNativeConnection,const nsACString & aSQLStatement)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!");
108
109 mDBConnection = aDBConnection;
110 mNativeConnection = aNativeConnection;
111 mSQLString = aSQLStatement;
112
113 MOZ_LOG(gStorageLog, LogLevel::Debug,
114 ("Inited async statement '%s' (0x%p)", mSQLString.get(), this));
115
116 #ifdef DEBUG
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);
125 e = 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;
132
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.
139 NS_WARNING(
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.");
144 }
145
146 // resetting start and e
147 start = e;
148 e = end;
149 }
150 #endif
151
152 return NS_OK;
153 }
154
getParams()155 mozIStorageBindingParams* AsyncStatement::getParams() {
156 nsresult rv;
157
158 // If we do not have an array object yet, make it.
159 if (!mParamsArray) {
160 nsCOMPtr<mozIStorageBindingParamsArray> array;
161 rv = NewBindingParamsArray(getter_AddRefs(array));
162 NS_ENSURE_SUCCESS(rv, nullptr);
163
164 mParamsArray = static_cast<BindingParamsArray*>(array.get());
165 }
166
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);
171
172 rv = mParamsArray->AddParams(params);
173 NS_ENSURE_SUCCESS(rv, nullptr);
174
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);
178
179 // We also want to lock our array at this point - we don't want anything to
180 // be added to it.
181 mParamsArray->lock();
182 }
183
184 return *mParamsArray->begin();
185 }
186
187 /**
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
194 * main-thread refs.
195 */
~AsyncStatement()196 AsyncStatement::~AsyncStatement() {
197 destructorAsyncFinalize();
198
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());
209 }
210 }
211
212 ////////////////////////////////////////////////////////////////////////////////
213 //// nsISupports
214
215 NS_IMPL_ADDREF(AsyncStatement)
216 NS_IMPL_RELEASE(AsyncStatement)
217
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);
225 } else
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports,mozIStorageAsyncStatement)226 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, mozIStorageAsyncStatement)
227 NS_INTERFACE_MAP_END
228
229 ////////////////////////////////////////////////////////////////////////////////
230 //// StorageBaseStatementInternal
231
232 Connection* AsyncStatement::getOwner() { return mDBConnection; }
233
getAsyncStatement(sqlite3_stmt ** _stmt)234 int AsyncStatement::getAsyncStatement(sqlite3_stmt** _stmt) {
235 #ifdef DEBUG
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!");
241 #endif
242
243 if (!mAsyncStatement) {
244 int rc = mDBConnection->prepareStatement(mNativeConnection, mSQLString,
245 &mAsyncStatement);
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()));
252 *_stmt = nullptr;
253 return rc;
254 }
255 MOZ_LOG(gStorageLog, LogLevel::Debug,
256 ("Initialized statement '%s' (0x%p)", mSQLString.get(),
257 mAsyncStatement));
258 }
259
260 *_stmt = mAsyncStatement;
261 return SQLITE_OK;
262 }
263
getAsynchronousStatementData(StatementData & _data)264 nsresult AsyncStatement::getAsynchronousStatementData(StatementData& _data) {
265 if (mFinalized) return NS_ERROR_UNEXPECTED;
266
267 // Pass null for the sqlite3_stmt; it will be requested on demand from the
268 // async thread.
269 _data = StatementData(nullptr, bindingParamsArray(), this);
270
271 return NS_OK;
272 }
273
newBindingParams(mozIStorageBindingParamsArray * aOwner)274 already_AddRefed<mozIStorageBindingParams> AsyncStatement::newBindingParams(
275 mozIStorageBindingParamsArray* aOwner) {
276 if (mFinalized) return nullptr;
277
278 nsCOMPtr<mozIStorageBindingParams> params(new AsyncBindingParams(aOwner));
279 return params.forget();
280 }
281
282 ////////////////////////////////////////////////////////////////////////////////
283 //// mozIStorageAsyncStatement
284
285 // (nothing is specific to mozIStorageAsyncStatement)
286
287 ////////////////////////////////////////////////////////////////////////////////
288 //// StorageBaseStatementInternal
289
290 // proxy to StorageBaseStatementInternal using its define helper.
MIXIN_IMPL_STORAGEBASESTATEMENTINTERNAL(AsyncStatement,if (mFinalized)return NS_ERROR_UNEXPECTED;)291 MIXIN_IMPL_STORAGEBASESTATEMENTINTERNAL(
292 AsyncStatement, if (mFinalized) return NS_ERROR_UNEXPECTED;)
293
294 NS_IMETHODIMP
295 AsyncStatement::Finalize() {
296 if (mFinalized) return NS_OK;
297
298 mFinalized = true;
299
300 MOZ_LOG(gStorageLog, LogLevel::Debug,
301 ("Finalizing statement '%s'", mSQLString.get()));
302
303 asyncFinalize();
304
305 // Release the params holder, so it can release the reference to us.
306 mStatementParamsHolder = nullptr;
307
308 return NS_OK;
309 }
310
311 NS_IMETHODIMP
BindParameters(mozIStorageBindingParamsArray * aParameters)312 AsyncStatement::BindParameters(mozIStorageBindingParamsArray* aParameters) {
313 if (mFinalized) return NS_ERROR_UNEXPECTED;
314
315 BindingParamsArray* array = static_cast<BindingParamsArray*>(aParameters);
316 if (array->getOwner() != this) return NS_ERROR_UNEXPECTED;
317
318 if (array->length() == 0) return NS_ERROR_UNEXPECTED;
319
320 mParamsArray = array;
321 mParamsArray->lock();
322
323 return NS_OK;
324 }
325
326 NS_IMETHODIMP
GetState(int32_t * _state)327 AsyncStatement::GetState(int32_t* _state) {
328 if (mFinalized)
329 *_state = MOZ_STORAGE_STATEMENT_INVALID;
330 else
331 *_state = MOZ_STORAGE_STATEMENT_READY;
332
333 return NS_OK;
334 }
335
336 ////////////////////////////////////////////////////////////////////////////////
337 //// mozIStorageBindingParams
338
339 BOILERPLATE_BIND_PROXIES(AsyncStatement,
340 if (mFinalized) return NS_ERROR_UNEXPECTED;)
341
342 } // namespace storage
343 } // namespace mozilla
344