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