PageRenderTime 24ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/netcore/System.Private.CoreLib/shared/Internal/Win32/RegistryKey.cs

https://github.com/directhex/mono-1
C# | 487 lines | 369 code | 59 blank | 59 comment | 72 complexity | 407b7c39890170d4ed567058527119e9 MD5 | raw file
  1. // Licensed to the .NET Foundation under one or more agreements.
  2. // The .NET Foundation licenses this file to you under the MIT license.
  3. // See the LICENSE file in the project root for more information.
  4. using System;
  5. using System.Buffers;
  6. using System.Collections.Generic;
  7. using System.Diagnostics;
  8. using System.Diagnostics.CodeAnalysis;
  9. using System.IO;
  10. using System.Security;
  11. using Internal.Win32.SafeHandles;
  12. //
  13. // A minimal version of RegistryKey that supports just what CoreLib needs.
  14. //
  15. // Internal.Win32 namespace avoids confusion with the public standalone Microsoft.Win32.Registry implementation
  16. // that lives in corefx.
  17. //
  18. namespace Internal.Win32
  19. {
  20. internal sealed class RegistryKey : IDisposable
  21. {
  22. // MSDN defines the following limits for registry key names & values:
  23. // Key Name: 255 characters
  24. // Value name: 16,383 Unicode characters
  25. // Value: either 1 MB or current available memory, depending on registry format.
  26. private const int MaxKeyLength = 255;
  27. private const int MaxValueLength = 16383;
  28. private readonly SafeRegistryHandle _hkey;
  29. private RegistryKey(SafeRegistryHandle hkey)
  30. {
  31. _hkey = hkey;
  32. }
  33. void IDisposable.Dispose()
  34. {
  35. if (_hkey != null)
  36. {
  37. _hkey.Dispose();
  38. }
  39. }
  40. public void DeleteValue(string name, bool throwOnMissingValue)
  41. {
  42. int errorCode = Interop.Advapi32.RegDeleteValue(_hkey, name);
  43. //
  44. // From windows 2003 server, if the name is too long we will get error code ERROR_FILENAME_EXCED_RANGE
  45. // This still means the name doesn't exist. We need to be consistent with previous OS.
  46. //
  47. if (errorCode == Interop.Errors.ERROR_FILE_NOT_FOUND ||
  48. errorCode == Interop.Errors.ERROR_FILENAME_EXCED_RANGE)
  49. {
  50. if (throwOnMissingValue)
  51. {
  52. throw new ArgumentException(SR.Arg_RegSubKeyValueAbsent);
  53. }
  54. else
  55. {
  56. // Otherwise, reset and just return giving no indication to the user.
  57. // (For compatibility)
  58. errorCode = 0;
  59. }
  60. }
  61. // We really should throw an exception here if errorCode was bad,
  62. // but we can't for compatibility reasons.
  63. Debug.Assert(errorCode == 0, "RegDeleteValue failed. Here's your error code: " + errorCode);
  64. }
  65. internal static RegistryKey OpenBaseKey(IntPtr hKey)
  66. {
  67. return new RegistryKey(new SafeRegistryHandle(hKey, false));
  68. }
  69. public RegistryKey? OpenSubKey(string name)
  70. {
  71. return OpenSubKey(name, false);
  72. }
  73. public RegistryKey? OpenSubKey(string name, bool writable)
  74. {
  75. // Make sure that the name does not contain double slahes
  76. Debug.Assert(!name.Contains(@"\\"));
  77. int ret = Interop.Advapi32.RegOpenKeyEx(_hkey,
  78. name,
  79. 0,
  80. writable ?
  81. Interop.Advapi32.RegistryOperations.KEY_READ | Interop.Advapi32.RegistryOperations.KEY_WRITE :
  82. Interop.Advapi32.RegistryOperations.KEY_READ,
  83. out SafeRegistryHandle result);
  84. if (ret == 0 && !result.IsInvalid)
  85. {
  86. return new RegistryKey(result);
  87. }
  88. // Return null if we didn't find the key.
  89. if (ret == Interop.Errors.ERROR_ACCESS_DENIED || ret == Interop.Errors.ERROR_BAD_IMPERSONATION_LEVEL)
  90. {
  91. // We need to throw SecurityException here for compatibility reasons,
  92. // although UnauthorizedAccessException will make more sense.
  93. throw new SecurityException(SR.Security_RegistryPermission);
  94. }
  95. return null;
  96. }
  97. public string[] GetSubKeyNames()
  98. {
  99. var names = new List<string>();
  100. char[] name = ArrayPool<char>.Shared.Rent(MaxKeyLength + 1);
  101. try
  102. {
  103. int result;
  104. int nameLength = name.Length;
  105. while ((result = Interop.Advapi32.RegEnumKeyEx(
  106. _hkey,
  107. names.Count,
  108. name,
  109. ref nameLength,
  110. null,
  111. null,
  112. null,
  113. null)) != Interop.Errors.ERROR_NO_MORE_ITEMS)
  114. {
  115. switch (result)
  116. {
  117. case Interop.Errors.ERROR_SUCCESS:
  118. names.Add(new string(name, 0, nameLength));
  119. nameLength = name.Length;
  120. break;
  121. default:
  122. // Throw the error
  123. Win32Error(result, null);
  124. break;
  125. }
  126. }
  127. }
  128. finally
  129. {
  130. ArrayPool<char>.Shared.Return(name);
  131. }
  132. return names.ToArray();
  133. }
  134. public unsafe string[] GetValueNames()
  135. {
  136. var names = new List<string>();
  137. // Names in the registry aren't usually very long, although they can go to as large
  138. // as 16383 characters (MaxValueLength).
  139. //
  140. // Every call to RegEnumValue will allocate another buffer to get the data from
  141. // NtEnumerateValueKey before copying it back out to our passed in buffer. This can
  142. // add up quickly- we'll try to keep the memory pressure low and grow the buffer
  143. // only if needed.
  144. char[]? name = ArrayPool<char>.Shared.Rent(100);
  145. try
  146. {
  147. int result;
  148. int nameLength = name.Length;
  149. while ((result = Interop.Advapi32.RegEnumValue(
  150. _hkey,
  151. names.Count,
  152. name,
  153. ref nameLength,
  154. IntPtr.Zero,
  155. null,
  156. null,
  157. null)) != Interop.Errors.ERROR_NO_MORE_ITEMS)
  158. {
  159. switch (result)
  160. {
  161. // The size is only ever reported back correctly in the case
  162. // of ERROR_SUCCESS. It will almost always be changed, however.
  163. case Interop.Errors.ERROR_SUCCESS:
  164. names.Add(new string(name, 0, nameLength));
  165. break;
  166. case Interop.Errors.ERROR_MORE_DATA:
  167. {
  168. char[] oldName = name;
  169. int oldLength = oldName.Length;
  170. name = null;
  171. ArrayPool<char>.Shared.Return(oldName);
  172. name = ArrayPool<char>.Shared.Rent(checked(oldLength * 2));
  173. }
  174. break;
  175. default:
  176. // Throw the error
  177. Win32Error(result, null);
  178. break;
  179. }
  180. // Always set the name length back to the buffer size
  181. nameLength = name.Length;
  182. }
  183. }
  184. finally
  185. {
  186. if (name != null)
  187. ArrayPool<char>.Shared.Return(name);
  188. }
  189. return names.ToArray();
  190. }
  191. public object? GetValue(string name)
  192. {
  193. return GetValue(name, null);
  194. }
  195. [return: NotNullIfNotNull("defaultValue")]
  196. public object? GetValue(string name, object? defaultValue)
  197. {
  198. object? data = defaultValue;
  199. int type = 0;
  200. int datasize = 0;
  201. int ret = Interop.Advapi32.RegQueryValueEx(_hkey, name, null, ref type, (byte[]?)null, ref datasize);
  202. if (ret != 0)
  203. {
  204. // For stuff like ERROR_FILE_NOT_FOUND, we want to return null (data).
  205. // Some OS's returned ERROR_MORE_DATA even in success cases, so we
  206. // want to continue on through the function.
  207. if (ret != Interop.Errors.ERROR_MORE_DATA)
  208. return data;
  209. }
  210. if (datasize < 0)
  211. {
  212. // unexpected code path
  213. Debug.Fail("[InternalGetValue] RegQueryValue returned ERROR_SUCCESS but gave a negative datasize");
  214. datasize = 0;
  215. }
  216. switch (type)
  217. {
  218. case Interop.Advapi32.RegistryValues.REG_NONE:
  219. case Interop.Advapi32.RegistryValues.REG_DWORD_BIG_ENDIAN:
  220. case Interop.Advapi32.RegistryValues.REG_BINARY:
  221. {
  222. byte[] blob = new byte[datasize];
  223. ret = Interop.Advapi32.RegQueryValueEx(_hkey, name, null, ref type, blob, ref datasize);
  224. data = blob;
  225. }
  226. break;
  227. case Interop.Advapi32.RegistryValues.REG_QWORD:
  228. { // also REG_QWORD_LITTLE_ENDIAN
  229. if (datasize > 8)
  230. {
  231. // prevent an AV in the edge case that datasize is larger than sizeof(long)
  232. goto case Interop.Advapi32.RegistryValues.REG_BINARY;
  233. }
  234. long blob = 0;
  235. Debug.Assert(datasize == 8, "datasize==8");
  236. // Here, datasize must be 8 when calling this
  237. ret = Interop.Advapi32.RegQueryValueEx(_hkey, name, null, ref type, ref blob, ref datasize);
  238. data = blob;
  239. }
  240. break;
  241. case Interop.Advapi32.RegistryValues.REG_DWORD:
  242. { // also REG_DWORD_LITTLE_ENDIAN
  243. if (datasize > 4)
  244. {
  245. // prevent an AV in the edge case that datasize is larger than sizeof(int)
  246. goto case Interop.Advapi32.RegistryValues.REG_QWORD;
  247. }
  248. int blob = 0;
  249. Debug.Assert(datasize == 4, "datasize==4");
  250. // Here, datasize must be four when calling this
  251. ret = Interop.Advapi32.RegQueryValueEx(_hkey, name, null, ref type, ref blob, ref datasize);
  252. data = blob;
  253. }
  254. break;
  255. case Interop.Advapi32.RegistryValues.REG_SZ:
  256. {
  257. if (datasize % 2 == 1)
  258. {
  259. // handle the case where the registry contains an odd-byte length (corrupt data?)
  260. try
  261. {
  262. datasize = checked(datasize + 1);
  263. }
  264. catch (OverflowException e)
  265. {
  266. throw new IOException(SR.Arg_RegGetOverflowBug, e);
  267. }
  268. }
  269. char[] blob = new char[datasize / 2];
  270. ret = Interop.Advapi32.RegQueryValueEx(_hkey, name, null, ref type, blob, ref datasize);
  271. if (blob.Length > 0 && blob[^1] == (char)0)
  272. {
  273. data = new string(blob, 0, blob.Length - 1);
  274. }
  275. else
  276. {
  277. // in the very unlikely case the data is missing null termination,
  278. // pass in the whole char[] to prevent truncating a character
  279. data = new string(blob);
  280. }
  281. }
  282. break;
  283. case Interop.Advapi32.RegistryValues.REG_EXPAND_SZ:
  284. {
  285. if (datasize % 2 == 1)
  286. {
  287. // handle the case where the registry contains an odd-byte length (corrupt data?)
  288. try
  289. {
  290. datasize = checked(datasize + 1);
  291. }
  292. catch (OverflowException e)
  293. {
  294. throw new IOException(SR.Arg_RegGetOverflowBug, e);
  295. }
  296. }
  297. char[] blob = new char[datasize / 2];
  298. ret = Interop.Advapi32.RegQueryValueEx(_hkey, name, null, ref type, blob, ref datasize);
  299. if (blob.Length > 0 && blob[^1] == (char)0)
  300. {
  301. data = new string(blob, 0, blob.Length - 1);
  302. }
  303. else
  304. {
  305. // in the very unlikely case the data is missing null termination,
  306. // pass in the whole char[] to prevent truncating a character
  307. data = new string(blob);
  308. }
  309. data = Environment.ExpandEnvironmentVariables((string)data);
  310. }
  311. break;
  312. case Interop.Advapi32.RegistryValues.REG_MULTI_SZ:
  313. {
  314. if (datasize % 2 == 1)
  315. {
  316. // handle the case where the registry contains an odd-byte length (corrupt data?)
  317. try
  318. {
  319. datasize = checked(datasize + 1);
  320. }
  321. catch (OverflowException e)
  322. {
  323. throw new IOException(SR.Arg_RegGetOverflowBug, e);
  324. }
  325. }
  326. char[] blob = new char[datasize / 2];
  327. ret = Interop.Advapi32.RegQueryValueEx(_hkey, name, null, ref type, blob, ref datasize);
  328. // make sure the string is null terminated before processing the data
  329. if (blob.Length > 0 && blob[^1] != (char)0)
  330. {
  331. Array.Resize(ref blob, blob.Length + 1);
  332. }
  333. string[] strings = Array.Empty<string>();
  334. int stringsCount = 0;
  335. int cur = 0;
  336. int len = blob.Length;
  337. while (ret == 0 && cur < len)
  338. {
  339. int nextNull = cur;
  340. while (nextNull < len && blob[nextNull] != (char)0)
  341. {
  342. nextNull++;
  343. }
  344. string? toAdd = null;
  345. if (nextNull < len)
  346. {
  347. Debug.Assert(blob[nextNull] == (char)0, "blob[nextNull] should be 0");
  348. if (nextNull - cur > 0)
  349. {
  350. toAdd = new string(blob, cur, nextNull - cur);
  351. }
  352. else
  353. {
  354. // we found an empty string. But if we're at the end of the data,
  355. // it's just the extra null terminator.
  356. if (nextNull != len - 1)
  357. {
  358. toAdd = string.Empty;
  359. }
  360. }
  361. }
  362. else
  363. {
  364. toAdd = new string(blob, cur, len - cur);
  365. }
  366. cur = nextNull + 1;
  367. if (toAdd != null)
  368. {
  369. if (strings.Length == stringsCount)
  370. {
  371. Array.Resize(ref strings, stringsCount > 0 ? stringsCount * 2 : 4);
  372. }
  373. strings[stringsCount++] = toAdd;
  374. }
  375. }
  376. Array.Resize(ref strings, stringsCount);
  377. data = strings;
  378. }
  379. break;
  380. case Interop.Advapi32.RegistryValues.REG_LINK:
  381. default:
  382. break;
  383. }
  384. return data;
  385. }
  386. // The actual api is SetValue(string name, object value) but we only need to set Strings
  387. // so this is a cut-down version that supports on that.
  388. internal void SetValue(string name, string value)
  389. {
  390. if (value == null)
  391. throw new ArgumentNullException(nameof(value));
  392. if (name != null && name.Length > MaxValueLength)
  393. throw new ArgumentException(SR.Arg_RegValStrLenBug, nameof(name));
  394. int ret = Interop.Advapi32.RegSetValueEx(_hkey,
  395. name,
  396. 0,
  397. Interop.Advapi32.RegistryValues.REG_SZ,
  398. value,
  399. checked(value.Length * 2 + 2));
  400. if (ret != 0)
  401. {
  402. Win32Error(ret, null);
  403. }
  404. }
  405. internal static void Win32Error(int errorCode, string? str)
  406. {
  407. switch (errorCode)
  408. {
  409. case Interop.Errors.ERROR_ACCESS_DENIED:
  410. if (str != null)
  411. throw new UnauthorizedAccessException(SR.Format(SR.UnauthorizedAccess_RegistryKeyGeneric_Key, str));
  412. else
  413. throw new UnauthorizedAccessException();
  414. case Interop.Errors.ERROR_FILE_NOT_FOUND:
  415. throw new IOException(SR.Arg_RegKeyNotFound, errorCode);
  416. default:
  417. throw new IOException(Interop.Kernel32.GetMessage(errorCode), errorCode);
  418. }
  419. }
  420. }
  421. internal static class Registry
  422. {
  423. /// <summary>Current User Key. This key should be used as the root for all user specific settings.</summary>
  424. public static readonly RegistryKey CurrentUser = RegistryKey.OpenBaseKey(unchecked((IntPtr)(int)0x80000001));
  425. /// <summary>Local Machine key. This key should be used as the root for all machine specific settings.</summary>
  426. public static readonly RegistryKey LocalMachine = RegistryKey.OpenBaseKey(unchecked((IntPtr)(int)0x80000002));
  427. }
  428. }