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