1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
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 <windows.h>
8 #include <shlwapi.h>
9 #include <stdlib.h>
10 #include "nsWindowsRegKey.h"
11 #include "nsString.h"
12 #include "nsCOMPtr.h"
13 #include "mozilla/Attributes.h"
14 #include "nsAutoPtr.h"
15 
16 //-----------------------------------------------------------------------------
17 
18 // According to MSDN, the following limits apply (in characters excluding room
19 // for terminating null character):
20 #define MAX_KEY_NAME_LEN     255
21 #define MAX_VALUE_NAME_LEN   16383
22 
23 class nsWindowsRegKey final : public nsIWindowsRegKey
24 {
25 public:
26   NS_DECL_ISUPPORTS
27   NS_DECL_NSIWINDOWSREGKEY
28 
nsWindowsRegKey()29   nsWindowsRegKey()
30     : mKey(nullptr)
31     , mWatchEvent(nullptr)
32     , mWatchRecursive(FALSE)
33   {
34   }
35 
36 private:
~nsWindowsRegKey()37   ~nsWindowsRegKey()
38   {
39     Close();
40   }
41 
42   HKEY   mKey;
43   HANDLE mWatchEvent;
44   BOOL   mWatchRecursive;
45 };
46 
NS_IMPL_ISUPPORTS(nsWindowsRegKey,nsIWindowsRegKey)47 NS_IMPL_ISUPPORTS(nsWindowsRegKey, nsIWindowsRegKey)
48 
49 NS_IMETHODIMP
50 nsWindowsRegKey::GetKey(HKEY* aKey)
51 {
52   *aKey = mKey;
53   return NS_OK;
54 }
55 
56 NS_IMETHODIMP
SetKey(HKEY aKey)57 nsWindowsRegKey::SetKey(HKEY aKey)
58 {
59   // We do not close the older aKey!
60   StopWatching();
61 
62   mKey = aKey;
63   return NS_OK;
64 }
65 
66 NS_IMETHODIMP
Close()67 nsWindowsRegKey::Close()
68 {
69   StopWatching();
70 
71   if (mKey) {
72     RegCloseKey(mKey);
73     mKey = nullptr;
74   }
75   return NS_OK;
76 }
77 
78 NS_IMETHODIMP
Open(uint32_t aRootKey,const nsAString & aPath,uint32_t aMode)79 nsWindowsRegKey::Open(uint32_t aRootKey, const nsAString& aPath,
80                       uint32_t aMode)
81 {
82   Close();
83 
84   LONG rv = RegOpenKeyExW((HKEY)(intptr_t)aRootKey,
85                           PromiseFlatString(aPath).get(), 0, (REGSAM)aMode,
86                           &mKey);
87   return (rv == ERROR_SUCCESS) ? NS_OK : NS_ERROR_FAILURE;
88 }
89 
90 NS_IMETHODIMP
Create(uint32_t aRootKey,const nsAString & aPath,uint32_t aMode)91 nsWindowsRegKey::Create(uint32_t aRootKey, const nsAString& aPath,
92                         uint32_t aMode)
93 {
94   Close();
95 
96   DWORD disposition;
97   LONG rv = RegCreateKeyExW((HKEY)(intptr_t)aRootKey,
98                             PromiseFlatString(aPath).get(), 0, nullptr,
99                             REG_OPTION_NON_VOLATILE, (REGSAM)aMode, nullptr,
100                             &mKey, &disposition);
101   return (rv == ERROR_SUCCESS) ? NS_OK : NS_ERROR_FAILURE;
102 }
103 
104 NS_IMETHODIMP
OpenChild(const nsAString & aPath,uint32_t aMode,nsIWindowsRegKey ** aResult)105 nsWindowsRegKey::OpenChild(const nsAString& aPath, uint32_t aMode,
106                            nsIWindowsRegKey** aResult)
107 {
108   if (NS_WARN_IF(!mKey)) {
109     return NS_ERROR_NOT_INITIALIZED;
110   }
111 
112   nsCOMPtr<nsIWindowsRegKey> child = new nsWindowsRegKey();
113 
114   nsresult rv = child->Open((uintptr_t)mKey, aPath, aMode);
115   if (NS_FAILED(rv)) {
116     return rv;
117   }
118 
119   child.swap(*aResult);
120   return NS_OK;
121 }
122 
123 NS_IMETHODIMP
CreateChild(const nsAString & aPath,uint32_t aMode,nsIWindowsRegKey ** aResult)124 nsWindowsRegKey::CreateChild(const nsAString& aPath, uint32_t aMode,
125                              nsIWindowsRegKey** aResult)
126 {
127   if (NS_WARN_IF(!mKey)) {
128     return NS_ERROR_NOT_INITIALIZED;
129   }
130 
131   nsCOMPtr<nsIWindowsRegKey> child = new nsWindowsRegKey();
132 
133   nsresult rv = child->Create((uintptr_t)mKey, aPath, aMode);
134   if (NS_FAILED(rv)) {
135     return rv;
136   }
137 
138   child.swap(*aResult);
139   return NS_OK;
140 }
141 
142 NS_IMETHODIMP
GetChildCount(uint32_t * aResult)143 nsWindowsRegKey::GetChildCount(uint32_t* aResult)
144 {
145   if (NS_WARN_IF(!mKey)) {
146     return NS_ERROR_NOT_INITIALIZED;
147   }
148 
149   DWORD numSubKeys;
150   LONG rv = RegQueryInfoKeyW(mKey, nullptr, nullptr, nullptr, &numSubKeys,
151                              nullptr, nullptr, nullptr, nullptr, nullptr,
152                              nullptr, nullptr);
153   if (rv != ERROR_SUCCESS) {
154     return NS_ERROR_FAILURE;
155   }
156 
157   *aResult = numSubKeys;
158   return NS_OK;
159 }
160 
161 NS_IMETHODIMP
GetChildName(uint32_t aIndex,nsAString & aResult)162 nsWindowsRegKey::GetChildName(uint32_t aIndex, nsAString& aResult)
163 {
164   if (NS_WARN_IF(!mKey)) {
165     return NS_ERROR_NOT_INITIALIZED;
166   }
167 
168   FILETIME lastWritten;
169 
170   wchar_t nameBuf[MAX_KEY_NAME_LEN + 1];
171   DWORD nameLen = sizeof(nameBuf) / sizeof(nameBuf[0]);
172 
173   LONG rv = RegEnumKeyExW(mKey, aIndex, nameBuf, &nameLen, nullptr, nullptr,
174                           nullptr, &lastWritten);
175   if (rv != ERROR_SUCCESS) {
176     return NS_ERROR_NOT_AVAILABLE;  // XXX what's the best error code here?
177   }
178 
179   aResult.Assign(nameBuf, nameLen);
180 
181   return NS_OK;
182 }
183 
184 NS_IMETHODIMP
HasChild(const nsAString & aName,bool * aResult)185 nsWindowsRegKey::HasChild(const nsAString& aName, bool* aResult)
186 {
187   if (NS_WARN_IF(!mKey)) {
188     return NS_ERROR_NOT_INITIALIZED;
189   }
190 
191   // Check for the existence of a child key by opening the key with minimal
192   // rights.  Perhaps there is a more efficient way to do this?
193 
194   HKEY key;
195   LONG rv = RegOpenKeyExW(mKey, PromiseFlatString(aName).get(), 0,
196                           STANDARD_RIGHTS_READ, &key);
197 
198   if ((*aResult = (rv == ERROR_SUCCESS && key))) {
199     RegCloseKey(key);
200   }
201 
202   return NS_OK;
203 }
204 
205 NS_IMETHODIMP
GetValueCount(uint32_t * aResult)206 nsWindowsRegKey::GetValueCount(uint32_t* aResult)
207 {
208   if (NS_WARN_IF(!mKey)) {
209     return NS_ERROR_NOT_INITIALIZED;
210   }
211 
212   DWORD numValues;
213   LONG rv = RegQueryInfoKeyW(mKey, nullptr, nullptr, nullptr, nullptr,
214                              nullptr, nullptr, &numValues, nullptr, nullptr,
215                              nullptr, nullptr);
216   if (rv != ERROR_SUCCESS) {
217     return NS_ERROR_FAILURE;
218   }
219 
220   *aResult = numValues;
221   return NS_OK;
222 }
223 
224 NS_IMETHODIMP
GetValueName(uint32_t aIndex,nsAString & aResult)225 nsWindowsRegKey::GetValueName(uint32_t aIndex, nsAString& aResult)
226 {
227   if (NS_WARN_IF(!mKey)) {
228     return NS_ERROR_NOT_INITIALIZED;
229   }
230 
231   wchar_t nameBuf[MAX_VALUE_NAME_LEN];
232   DWORD nameLen = sizeof(nameBuf) / sizeof(nameBuf[0]);
233 
234   LONG rv = RegEnumValueW(mKey, aIndex, nameBuf, &nameLen, nullptr, nullptr,
235                           nullptr, nullptr);
236   if (rv != ERROR_SUCCESS) {
237     return NS_ERROR_NOT_AVAILABLE;  // XXX what's the best error code here?
238   }
239 
240   aResult.Assign(nameBuf, nameLen);
241 
242   return NS_OK;
243 }
244 
245 NS_IMETHODIMP
HasValue(const nsAString & aName,bool * aResult)246 nsWindowsRegKey::HasValue(const nsAString& aName, bool* aResult)
247 {
248   if (NS_WARN_IF(!mKey)) {
249     return NS_ERROR_NOT_INITIALIZED;
250   }
251 
252   LONG rv = RegQueryValueExW(mKey, PromiseFlatString(aName).get(), 0, nullptr,
253                              nullptr, nullptr);
254 
255   *aResult = (rv == ERROR_SUCCESS);
256   return NS_OK;
257 }
258 
259 NS_IMETHODIMP
RemoveChild(const nsAString & aName)260 nsWindowsRegKey::RemoveChild(const nsAString& aName)
261 {
262   if (NS_WARN_IF(!mKey)) {
263     return NS_ERROR_NOT_INITIALIZED;
264   }
265 
266   LONG rv = RegDeleteKeyW(mKey, PromiseFlatString(aName).get());
267 
268   return (rv == ERROR_SUCCESS) ? NS_OK : NS_ERROR_FAILURE;
269 }
270 
271 NS_IMETHODIMP
RemoveValue(const nsAString & aName)272 nsWindowsRegKey::RemoveValue(const nsAString& aName)
273 {
274   if (NS_WARN_IF(!mKey)) {
275     return NS_ERROR_NOT_INITIALIZED;
276   }
277 
278   LONG rv = RegDeleteValueW(mKey, PromiseFlatString(aName).get());
279 
280   return (rv == ERROR_SUCCESS) ? NS_OK : NS_ERROR_FAILURE;
281 }
282 
283 NS_IMETHODIMP
GetValueType(const nsAString & aName,uint32_t * aResult)284 nsWindowsRegKey::GetValueType(const nsAString& aName, uint32_t* aResult)
285 {
286   if (NS_WARN_IF(!mKey)) {
287     return NS_ERROR_NOT_INITIALIZED;
288   }
289 
290   LONG rv = RegQueryValueExW(mKey, PromiseFlatString(aName).get(), 0,
291                              (LPDWORD)aResult, nullptr, nullptr);
292   return (rv == ERROR_SUCCESS) ? NS_OK : NS_ERROR_FAILURE;
293 }
294 
295 NS_IMETHODIMP
ReadStringValue(const nsAString & aName,nsAString & aResult)296 nsWindowsRegKey::ReadStringValue(const nsAString& aName, nsAString& aResult)
297 {
298   if (NS_WARN_IF(!mKey)) {
299     return NS_ERROR_NOT_INITIALIZED;
300   }
301 
302   DWORD type, size;
303 
304   const nsString& flatName = PromiseFlatString(aName);
305 
306   LONG rv = RegQueryValueExW(mKey, flatName.get(), 0, &type, nullptr, &size);
307   if (rv != ERROR_SUCCESS) {
308     return NS_ERROR_FAILURE;
309   }
310 
311   // This must be a string type in order to fetch the value as a string.
312   // We're being a bit forgiving here by allowing types other than REG_SZ.
313   if (type != REG_SZ && type != REG_EXPAND_SZ && type != REG_MULTI_SZ) {
314     return NS_ERROR_FAILURE;
315   }
316 
317   // The buffer size must be a multiple of 2.
318   if (size % 2 != 0) {
319     return NS_ERROR_UNEXPECTED;
320   }
321 
322   if (size == 0) {
323     aResult.Truncate();
324     return NS_OK;
325   }
326 
327   // |size| may or may not include the terminating null character.
328   DWORD resultLen = size / 2;
329 
330   if (!aResult.SetLength(resultLen, mozilla::fallible)) {
331     return NS_ERROR_OUT_OF_MEMORY;
332   }
333 
334   nsAString::iterator begin;
335   aResult.BeginWriting(begin);
336 
337   rv = RegQueryValueExW(mKey, flatName.get(), 0, &type, (LPBYTE)begin.get(),
338                         &size);
339 
340   if (!aResult.CharAt(resultLen - 1)) {
341     // The string passed to us had a null terminator in the final position.
342     aResult.Truncate(resultLen - 1);
343   }
344 
345   // Expand the environment variables if needed
346   if (type == REG_EXPAND_SZ) {
347     const nsString& flatSource = PromiseFlatString(aResult);
348     resultLen = ExpandEnvironmentStringsW(flatSource.get(), nullptr, 0);
349     if (resultLen > 1) {
350       nsAutoString expandedResult;
351       // |resultLen| includes the terminating null character
352       --resultLen;
353       if (!expandedResult.SetLength(resultLen, mozilla::fallible)) {
354         return NS_ERROR_OUT_OF_MEMORY;
355       }
356 
357       nsAString::iterator begin;
358       expandedResult.BeginWriting(begin);
359 
360       resultLen = ExpandEnvironmentStringsW(flatSource.get(),
361                                             wwc(begin.get()),
362                                             resultLen + 1);
363       if (resultLen <= 0) {
364         rv = ERROR_UNKNOWN_FEATURE;
365         aResult.Truncate();
366       } else {
367         rv = ERROR_SUCCESS;
368         aResult = expandedResult;
369       }
370     } else if (resultLen == 1) {
371       // It apparently expands to nothing (just a null terminator).
372       aResult.Truncate();
373     }
374   }
375 
376   return (rv == ERROR_SUCCESS) ? NS_OK : NS_ERROR_FAILURE;
377 }
378 
379 NS_IMETHODIMP
ReadIntValue(const nsAString & aName,uint32_t * aResult)380 nsWindowsRegKey::ReadIntValue(const nsAString& aName, uint32_t* aResult)
381 {
382   if (NS_WARN_IF(!mKey)) {
383     return NS_ERROR_NOT_INITIALIZED;
384   }
385 
386   DWORD size = sizeof(*aResult);
387   LONG rv = RegQueryValueExW(mKey, PromiseFlatString(aName).get(), 0, nullptr,
388                              (LPBYTE)aResult, &size);
389   return (rv == ERROR_SUCCESS) ? NS_OK : NS_ERROR_FAILURE;
390 }
391 
392 NS_IMETHODIMP
ReadInt64Value(const nsAString & aName,uint64_t * aResult)393 nsWindowsRegKey::ReadInt64Value(const nsAString& aName, uint64_t* aResult)
394 {
395   if (NS_WARN_IF(!mKey)) {
396     return NS_ERROR_NOT_INITIALIZED;
397   }
398 
399   DWORD size = sizeof(*aResult);
400   LONG rv = RegQueryValueExW(mKey, PromiseFlatString(aName).get(), 0, nullptr,
401                              (LPBYTE)aResult, &size);
402   return (rv == ERROR_SUCCESS) ? NS_OK : NS_ERROR_FAILURE;
403 }
404 
405 NS_IMETHODIMP
ReadBinaryValue(const nsAString & aName,nsACString & aResult)406 nsWindowsRegKey::ReadBinaryValue(const nsAString& aName, nsACString& aResult)
407 {
408   if (NS_WARN_IF(!mKey)) {
409     return NS_ERROR_NOT_INITIALIZED;
410   }
411 
412   DWORD size;
413   LONG rv = RegQueryValueExW(mKey, PromiseFlatString(aName).get(), 0,
414                              nullptr, nullptr, &size);
415 
416   if (rv != ERROR_SUCCESS) {
417     return NS_ERROR_FAILURE;
418   }
419 
420   if (!size) {
421     aResult.Truncate();
422     return NS_OK;
423   }
424 
425   if (!aResult.SetLength(size, mozilla::fallible)) {
426     return NS_ERROR_OUT_OF_MEMORY;
427   }
428 
429   nsACString::iterator begin;
430   aResult.BeginWriting(begin);
431 
432   rv = RegQueryValueExW(mKey, PromiseFlatString(aName).get(), 0, nullptr,
433                         (LPBYTE)begin.get(), &size);
434   return (rv == ERROR_SUCCESS) ? NS_OK : NS_ERROR_FAILURE;
435 }
436 
437 NS_IMETHODIMP
WriteStringValue(const nsAString & aName,const nsAString & aValue)438 nsWindowsRegKey::WriteStringValue(const nsAString& aName,
439                                   const nsAString& aValue)
440 {
441   if (NS_WARN_IF(!mKey)) {
442     return NS_ERROR_NOT_INITIALIZED;
443   }
444 
445   // Need to indicate complete size of buffer including null terminator.
446   const nsString& flatValue = PromiseFlatString(aValue);
447 
448   LONG rv = RegSetValueExW(mKey, PromiseFlatString(aName).get(), 0, REG_SZ,
449                            (const BYTE*)flatValue.get(),
450                            (flatValue.Length() + 1) * sizeof(char16_t));
451   return (rv == ERROR_SUCCESS) ? NS_OK : NS_ERROR_FAILURE;
452 }
453 
454 NS_IMETHODIMP
WriteIntValue(const nsAString & aName,uint32_t aValue)455 nsWindowsRegKey::WriteIntValue(const nsAString& aName, uint32_t aValue)
456 {
457   if (NS_WARN_IF(!mKey)) {
458     return NS_ERROR_NOT_INITIALIZED;
459   }
460 
461   LONG rv = RegSetValueExW(mKey, PromiseFlatString(aName).get(), 0, REG_DWORD,
462                            (const BYTE*)&aValue, sizeof(aValue));
463   return (rv == ERROR_SUCCESS) ? NS_OK : NS_ERROR_FAILURE;
464 }
465 
466 NS_IMETHODIMP
WriteInt64Value(const nsAString & aName,uint64_t aValue)467 nsWindowsRegKey::WriteInt64Value(const nsAString& aName, uint64_t aValue)
468 {
469   if (NS_WARN_IF(!mKey)) {
470     return NS_ERROR_NOT_INITIALIZED;
471   }
472 
473   LONG rv = RegSetValueExW(mKey, PromiseFlatString(aName).get(), 0, REG_QWORD,
474                            (const BYTE*)&aValue, sizeof(aValue));
475   return (rv == ERROR_SUCCESS) ? NS_OK : NS_ERROR_FAILURE;
476 }
477 
478 NS_IMETHODIMP
WriteBinaryValue(const nsAString & aName,const nsACString & aValue)479 nsWindowsRegKey::WriteBinaryValue(const nsAString& aName,
480                                   const nsACString& aValue)
481 {
482   if (NS_WARN_IF(!mKey)) {
483     return NS_ERROR_NOT_INITIALIZED;
484   }
485 
486   const nsCString& flatValue = PromiseFlatCString(aValue);
487   LONG rv = RegSetValueExW(mKey, PromiseFlatString(aName).get(), 0, REG_BINARY,
488                            (const BYTE*)flatValue.get(), flatValue.Length());
489   return (rv == ERROR_SUCCESS) ? NS_OK : NS_ERROR_FAILURE;
490 }
491 
492 NS_IMETHODIMP
StartWatching(bool aRecurse)493 nsWindowsRegKey::StartWatching(bool aRecurse)
494 {
495   if (NS_WARN_IF(!mKey)) {
496     return NS_ERROR_NOT_INITIALIZED;
497   }
498 
499   if (mWatchEvent) {
500     return NS_OK;
501   }
502 
503   mWatchEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr);
504   if (!mWatchEvent) {
505     return NS_ERROR_OUT_OF_MEMORY;
506   }
507 
508   DWORD filter = REG_NOTIFY_CHANGE_NAME |
509                  REG_NOTIFY_CHANGE_ATTRIBUTES |
510                  REG_NOTIFY_CHANGE_LAST_SET |
511                  REG_NOTIFY_CHANGE_SECURITY;
512 
513   LONG rv = RegNotifyChangeKeyValue(mKey, aRecurse, filter, mWatchEvent, TRUE);
514   if (rv != ERROR_SUCCESS) {
515     StopWatching();
516     // On older versions of Windows, this call is not implemented, so simply
517     // return NS_OK in those cases and pretend that the watching is happening.
518     return (rv == ERROR_CALL_NOT_IMPLEMENTED) ? NS_OK : NS_ERROR_FAILURE;
519   }
520 
521   mWatchRecursive = aRecurse;
522   return NS_OK;
523 }
524 
525 NS_IMETHODIMP
StopWatching()526 nsWindowsRegKey::StopWatching()
527 {
528   if (mWatchEvent) {
529     CloseHandle(mWatchEvent);
530     mWatchEvent = nullptr;
531   }
532   return NS_OK;
533 }
534 
535 NS_IMETHODIMP
HasChanged(bool * aResult)536 nsWindowsRegKey::HasChanged(bool* aResult)
537 {
538   if (mWatchEvent && WaitForSingleObject(mWatchEvent, 0) == WAIT_OBJECT_0) {
539     // An event only gets signaled once, then it's done, so we have to set up
540     // another event to watch.
541     StopWatching();
542     StartWatching(mWatchRecursive);
543     *aResult = true;
544   } else {
545     *aResult = false;
546   }
547   return NS_OK;
548 }
549 
550 NS_IMETHODIMP
IsWatching(bool * aResult)551 nsWindowsRegKey::IsWatching(bool* aResult)
552 {
553   *aResult = (mWatchEvent != nullptr);
554   return NS_OK;
555 }
556 
557 //-----------------------------------------------------------------------------
558 
559 void
NS_NewWindowsRegKey(nsIWindowsRegKey ** aResult)560 NS_NewWindowsRegKey(nsIWindowsRegKey** aResult)
561 {
562   RefPtr<nsWindowsRegKey> key = new nsWindowsRegKey();
563   key.forget(aResult);
564 }
565 
566 //-----------------------------------------------------------------------------
567 
568 nsresult
nsWindowsRegKeyConstructor(nsISupports * aDelegate,const nsIID & aIID,void ** aResult)569 nsWindowsRegKeyConstructor(nsISupports* aDelegate, const nsIID& aIID,
570                            void** aResult)
571 {
572   if (aDelegate) {
573     return NS_ERROR_NO_AGGREGATION;
574   }
575 
576   nsCOMPtr<nsIWindowsRegKey> key;
577   NS_NewWindowsRegKey(getter_AddRefs(key));
578   return key->QueryInterface(aIID, aResult);
579 }
580