Line data Source code
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 "mozIStorageError.h"
18 :
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"
26 : #include "nsDOMClassInfo.h"
27 :
28 : #include "mozilla/Logging.h"
29 :
30 : extern mozilla::LazyLogModule gStorageLog;
31 :
32 : namespace mozilla {
33 : namespace storage {
34 :
35 : ////////////////////////////////////////////////////////////////////////////////
36 : //// nsIClassInfo
37 :
38 1 : NS_IMPL_CI_INTERFACE_GETTER(AsyncStatement,
39 : mozIStorageAsyncStatement,
40 : mozIStorageBaseStatement,
41 : mozIStorageBindingParams,
42 : mozilla::storage::StorageBaseStatementInternal)
43 :
44 : class AsyncStatementClassInfo : public nsIClassInfo
45 : {
46 : public:
47 : constexpr AsyncStatementClassInfo() {}
48 :
49 : NS_DECL_ISUPPORTS_INHERITED
50 :
51 : NS_IMETHOD
52 1 : GetInterfaces(uint32_t *_count, nsIID ***_array) override
53 : {
54 1 : return NS_CI_INTERFACE_GETTER_NAME(AsyncStatement)(_count, _array);
55 : }
56 :
57 : NS_IMETHOD
58 7 : GetScriptableHelper(nsIXPCScriptable **_helper) override
59 : {
60 7 : static AsyncStatementJSHelper sJSHelper;
61 7 : *_helper = &sJSHelper;
62 7 : return NS_OK;
63 : }
64 :
65 : NS_IMETHOD
66 0 : GetContractID(char **_contractID) override
67 : {
68 0 : *_contractID = nullptr;
69 0 : return NS_OK;
70 : }
71 :
72 : NS_IMETHOD
73 0 : GetClassDescription(char **_desc) override
74 : {
75 0 : *_desc = nullptr;
76 0 : return NS_OK;
77 : }
78 :
79 : NS_IMETHOD
80 0 : GetClassID(nsCID **_id) override
81 : {
82 0 : *_id = nullptr;
83 0 : return NS_OK;
84 : }
85 :
86 : NS_IMETHOD
87 7 : GetFlags(uint32_t *_flags) override
88 : {
89 7 : *_flags = 0;
90 7 : return NS_OK;
91 : }
92 :
93 : NS_IMETHOD
94 0 : GetClassIDNoAlloc(nsCID *_cid) override
95 : {
96 0 : return NS_ERROR_NOT_AVAILABLE;
97 : }
98 : };
99 :
100 13 : NS_IMETHODIMP_(MozExternalRefCountType) AsyncStatementClassInfo::AddRef() { return 2; }
101 10 : NS_IMETHODIMP_(MozExternalRefCountType) AsyncStatementClassInfo::Release() { return 1; }
102 10 : NS_IMPL_QUERY_INTERFACE(AsyncStatementClassInfo, nsIClassInfo)
103 :
104 : static AsyncStatementClassInfo sAsyncStatementClassInfo;
105 :
106 : ////////////////////////////////////////////////////////////////////////////////
107 : //// AsyncStatement
108 :
109 16 : AsyncStatement::AsyncStatement()
110 : : StorageBaseStatementInternal()
111 16 : , mFinalized(false)
112 : {
113 16 : }
114 :
115 : nsresult
116 16 : AsyncStatement::initialize(Connection *aDBConnection,
117 : sqlite3 *aNativeConnection,
118 : const nsACString &aSQLStatement)
119 : {
120 16 : MOZ_ASSERT(aDBConnection, "No database connection given!");
121 16 : MOZ_ASSERT(aDBConnection->isConnectionReadyOnThisThread(), "Database connection should be valid");
122 16 : MOZ_ASSERT(aNativeConnection, "No native connection given!");
123 :
124 16 : mDBConnection = aDBConnection;
125 16 : mNativeConnection = aNativeConnection;
126 16 : mSQLString = aSQLStatement;
127 :
128 16 : MOZ_LOG(gStorageLog, LogLevel::Debug, ("Inited async statement '%s' (0x%p)",
129 : mSQLString.get(), this));
130 :
131 : #ifdef DEBUG
132 : // We want to try and test for LIKE and that consumers are using
133 : // escapeStringForLIKE instead of just trusting user input. The idea to
134 : // check to see if they are binding a parameter after like instead of just
135 : // using a string. We only do this in debug builds because it's expensive!
136 16 : const nsCaseInsensitiveCStringComparator c;
137 16 : nsACString::const_iterator start, end, e;
138 16 : aSQLStatement.BeginReading(start);
139 16 : aSQLStatement.EndReading(end);
140 16 : e = end;
141 18 : while (::FindInReadable(NS_LITERAL_CSTRING(" LIKE"), start, e, c)) {
142 : // We have a LIKE in here, so we perform our tests
143 : // FindInReadable moves the iterator, so we have to get a new one for
144 : // each test we perform.
145 1 : nsACString::const_iterator s1, s2, s3;
146 1 : s1 = s2 = s3 = start;
147 :
148 4 : if (!(::FindInReadable(NS_LITERAL_CSTRING(" LIKE ?"), s1, end, c) ||
149 3 : ::FindInReadable(NS_LITERAL_CSTRING(" LIKE :"), s2, end, c) ||
150 1 : ::FindInReadable(NS_LITERAL_CSTRING(" LIKE @"), s3, end, c))) {
151 : // At this point, we didn't find a LIKE statement followed by ?, :,
152 : // or @, all of which are valid characters for binding a parameter.
153 : // We will warn the consumer that they may not be safely using LIKE.
154 : NS_WARNING("Unsafe use of LIKE detected! Please ensure that you "
155 : "are using mozIStorageAsyncStatement::escapeStringForLIKE "
156 : "and that you are binding that result to the statement "
157 0 : "to prevent SQL injection attacks.");
158 : }
159 :
160 : // resetting start and e
161 1 : start = e;
162 1 : e = end;
163 : }
164 : #endif
165 :
166 16 : return NS_OK;
167 : }
168 :
169 : mozIStorageBindingParams *
170 10 : AsyncStatement::getParams()
171 : {
172 : nsresult rv;
173 :
174 : // If we do not have an array object yet, make it.
175 10 : if (!mParamsArray) {
176 12 : nsCOMPtr<mozIStorageBindingParamsArray> array;
177 6 : rv = NewBindingParamsArray(getter_AddRefs(array));
178 6 : NS_ENSURE_SUCCESS(rv, nullptr);
179 :
180 6 : mParamsArray = static_cast<BindingParamsArray *>(array.get());
181 : }
182 :
183 : // If there isn't already any rows added, we'll have to add one to use.
184 10 : if (mParamsArray->length() == 0) {
185 18 : RefPtr<AsyncBindingParams> params(new AsyncBindingParams(mParamsArray));
186 6 : NS_ENSURE_TRUE(params, nullptr);
187 :
188 6 : rv = mParamsArray->AddParams(params);
189 6 : NS_ENSURE_SUCCESS(rv, nullptr);
190 :
191 : // We have to unlock our params because AddParams locks them. This is safe
192 : // because no reference to the params object was, or ever will be given out.
193 6 : params->unlock(nullptr);
194 :
195 : // We also want to lock our array at this point - we don't want anything to
196 : // be added to it.
197 6 : mParamsArray->lock();
198 : }
199 :
200 10 : return *mParamsArray->begin();
201 : }
202 :
203 : /**
204 : * If we are here then we know there are no pending async executions relying on
205 : * us (StatementData holds a reference to us; this also goes for our own
206 : * AsyncStatementFinalizer which proxies its release to the calling thread) and
207 : * so it is always safe to destroy our sqlite3_stmt if one exists. We can be
208 : * destroyed on the caller thread by garbage-collection/reference counting or on
209 : * the async thread by the last execution of a statement that already lost its
210 : * main-thread refs.
211 : */
212 4 : AsyncStatement::~AsyncStatement()
213 : {
214 2 : destructorAsyncFinalize();
215 :
216 : // If we are getting destroyed on the wrong thread, proxy the connection
217 : // release to the right thread. I'm not sure why we do this.
218 2 : bool onCallingThread = false;
219 2 : (void)mDBConnection->threadOpenedOn->IsOnCurrentThread(&onCallingThread);
220 2 : if (!onCallingThread) {
221 : // NS_ProxyRelase only magic forgets for us if mDBConnection is an
222 : // nsCOMPtr. Which it is not; it's an nsRefPtr.
223 4 : nsCOMPtr<nsIThread> targetThread(mDBConnection->threadOpenedOn);
224 : NS_ProxyRelease(
225 : "AsyncStatement::mDBConnection",
226 2 : targetThread, mDBConnection.forget());
227 : }
228 2 : }
229 :
230 : ////////////////////////////////////////////////////////////////////////////////
231 : //// nsISupports
232 :
233 122 : NS_IMPL_ADDREF(AsyncStatement)
234 103 : NS_IMPL_RELEASE(AsyncStatement)
235 :
236 90 : NS_INTERFACE_MAP_BEGIN(AsyncStatement)
237 90 : NS_INTERFACE_MAP_ENTRY(mozIStorageAsyncStatement)
238 65 : NS_INTERFACE_MAP_ENTRY(mozIStorageBaseStatement)
239 62 : NS_INTERFACE_MAP_ENTRY(mozIStorageBindingParams)
240 62 : NS_INTERFACE_MAP_ENTRY(mozilla::storage::StorageBaseStatementInternal)
241 42 : if (aIID.Equals(NS_GET_IID(nsIClassInfo))) {
242 7 : foundInterface = static_cast<nsIClassInfo *>(&sAsyncStatementClassInfo);
243 : }
244 : else
245 35 : NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, mozIStorageAsyncStatement)
246 28 : NS_INTERFACE_MAP_END
247 :
248 :
249 : ////////////////////////////////////////////////////////////////////////////////
250 : //// StorageBaseStatementInternal
251 :
252 : Connection *
253 0 : AsyncStatement::getOwner()
254 : {
255 0 : return mDBConnection;
256 : }
257 :
258 : int
259 11 : AsyncStatement::getAsyncStatement(sqlite3_stmt **_stmt)
260 : {
261 : #ifdef DEBUG
262 : // Make sure we are never called on the connection's owning thread.
263 11 : bool onOpenedThread = false;
264 11 : (void)mDBConnection->threadOpenedOn->IsOnCurrentThread(&onOpenedThread);
265 11 : NS_ASSERTION(!onOpenedThread,
266 : "We should only be called on the async thread!");
267 : #endif
268 :
269 11 : if (!mAsyncStatement) {
270 10 : int rc = mDBConnection->prepareStatement(mNativeConnection, mSQLString,
271 10 : &mAsyncStatement);
272 10 : if (rc != SQLITE_OK) {
273 0 : MOZ_LOG(gStorageLog, LogLevel::Error,
274 : ("Sqlite statement prepare error: %d '%s'", rc,
275 : ::sqlite3_errmsg(mNativeConnection)));
276 0 : MOZ_LOG(gStorageLog, LogLevel::Error,
277 : ("Statement was: '%s'", mSQLString.get()));
278 0 : *_stmt = nullptr;
279 0 : return rc;
280 : }
281 10 : MOZ_LOG(gStorageLog, LogLevel::Debug, ("Initialized statement '%s' (0x%p)",
282 : mSQLString.get(),
283 : mAsyncStatement));
284 : }
285 :
286 11 : *_stmt = mAsyncStatement;
287 11 : return SQLITE_OK;
288 : }
289 :
290 : nsresult
291 11 : AsyncStatement::getAsynchronousStatementData(StatementData &_data)
292 : {
293 11 : if (mFinalized)
294 0 : return NS_ERROR_UNEXPECTED;
295 :
296 : // Pass null for the sqlite3_stmt; it will be requested on demand from the
297 : // async thread.
298 11 : _data = StatementData(nullptr, bindingParamsArray(), this);
299 :
300 11 : return NS_OK;
301 : }
302 :
303 : already_AddRefed<mozIStorageBindingParams>
304 0 : AsyncStatement::newBindingParams(mozIStorageBindingParamsArray *aOwner)
305 : {
306 0 : if (mFinalized)
307 0 : return nullptr;
308 :
309 0 : nsCOMPtr<mozIStorageBindingParams> params(new AsyncBindingParams(aOwner));
310 0 : return params.forget();
311 : }
312 :
313 :
314 : ////////////////////////////////////////////////////////////////////////////////
315 : //// mozIStorageAsyncStatement
316 :
317 : // (nothing is specific to mozIStorageAsyncStatement)
318 :
319 : ////////////////////////////////////////////////////////////////////////////////
320 : //// StorageBaseStatementInternal
321 :
322 : // proxy to StorageBaseStatementInternal using its define helper.
323 15 : MIXIN_IMPL_STORAGEBASESTATEMENTINTERNAL(
324 : AsyncStatement,
325 : if (mFinalized) return NS_ERROR_UNEXPECTED;)
326 :
327 : NS_IMETHODIMP
328 4 : AsyncStatement::Finalize()
329 : {
330 4 : if (mFinalized)
331 0 : return NS_OK;
332 :
333 4 : mFinalized = true;
334 :
335 4 : MOZ_LOG(gStorageLog, LogLevel::Debug, ("Finalizing statement '%s'",
336 : mSQLString.get()));
337 :
338 4 : asyncFinalize();
339 :
340 : // Release the params holder, so it can release the reference to us.
341 4 : mStatementParamsHolder = nullptr;
342 :
343 4 : return NS_OK;
344 : }
345 :
346 : NS_IMETHODIMP
347 0 : AsyncStatement::BindParameters(mozIStorageBindingParamsArray *aParameters)
348 : {
349 0 : if (mFinalized)
350 0 : return NS_ERROR_UNEXPECTED;
351 :
352 0 : BindingParamsArray *array = static_cast<BindingParamsArray *>(aParameters);
353 0 : if (array->getOwner() != this)
354 0 : return NS_ERROR_UNEXPECTED;
355 :
356 0 : if (array->length() == 0)
357 0 : return NS_ERROR_UNEXPECTED;
358 :
359 0 : mParamsArray = array;
360 0 : mParamsArray->lock();
361 :
362 0 : return NS_OK;
363 : }
364 :
365 : NS_IMETHODIMP
366 10 : AsyncStatement::GetState(int32_t *_state)
367 : {
368 10 : if (mFinalized)
369 0 : *_state = MOZ_STORAGE_STATEMENT_INVALID;
370 : else
371 10 : *_state = MOZ_STORAGE_STATEMENT_READY;
372 :
373 10 : return NS_OK;
374 : }
375 :
376 : ////////////////////////////////////////////////////////////////////////////////
377 : //// mozIStorageBindingParams
378 :
379 10 : BOILERPLATE_BIND_PROXIES(
380 : AsyncStatement,
381 : if (mFinalized) return NS_ERROR_UNEXPECTED;
382 : )
383 :
384 : } // namespace storage
385 : } // namespace mozilla
|