|
//---------------------------------------------------------------------------
//
// <copyright file="PackagingUtilities.cs" company="Microsoft">
// Copyright (C) Microsoft Corporation. All rights reserved.
// </copyright>
//
// Description:
//
// History:
// 05/13/2004: Microsoft Creation
//
//---------------------------------------------------------------------------
using System;
using System.IO;
using System.IO.IsolatedStorage;
using MS.Internal.WindowsBase; // FriendAccessAllowed
using System.Xml; // For XmlReader
using System.Diagnostics; // For Debug.Assert
using System.Text; // For Encoding
using System.Windows; // For Exception strings - SRID
using System.Security; // for SecurityCritical
using System.Security.Permissions; // for permissions
using Microsoft.Win32; // for Registry classes
using MS.Internal;
namespace MS.Internal.IO.Packaging
{
[FriendAccessAllowed] // Built into Base, used by Framework and Core
internal static class PackagingUtilities
{
//------------------------------------------------------
//
// Internal Fields
//
//------------------------------------------------------
internal static readonly string RelationshipNamespaceUri = "http://schemas.openxmlformats.org/package/2006/relationships";
internal static readonly ContentType RelationshipPartContentType
= new ContentType("application/vnd.openxmlformats-package.relationships+xml");
internal const string ContainerFileExtension = "xps";
internal const string XamlFileExtension = "xaml";
//------------------------------------------------------
//
// Internal Properties
//
//------------------------------------------------------
//------------------------------------------------------
//
// Internal Methods
//
//------------------------------------------------------
#region Internal Methods
/// <summary>
/// This method is used to determine if we support a given Encoding as per the
/// OPC and XPS specs. Currently the only two encodings supported are UTF-8 and
/// UTF-16 (Little Endian and Big Endian)
/// </summary>
/// <param name="reader">XmlTextReader</param>
/// <returns>throws an exception if the encoding is not UTF-8 or UTF-16</returns>
internal static void PerformInitailReadAndVerifyEncoding(XmlTextReader reader)
{
Invariant.Assert(reader != null && reader.ReadState == ReadState.Initial);
//If the first node is XmlDeclaration we check to see if the encoding attribute is present
if (reader.Read() && reader.NodeType == XmlNodeType.XmlDeclaration && reader.Depth == 0)
{
string encoding;
encoding = reader.GetAttribute(_encodingAttribute);
if (encoding != null && encoding.Length > 0)
{
encoding = encoding.ToUpperInvariant();
//If a non-empty encoding attribute is present [for example - <?xml version="1.0" encoding="utf-8" ?>]
//we check to see if the value is either "utf-8" or utf-16. Only these two values are supported
//Note: For Byte order markings that require additional information to be specified in
//the encoding attribute in XmlDeclaration have already been ruled out by this check as we allow for
//only two valid values.
if (String.CompareOrdinal(encoding, _webNameUTF8) == 0
|| String.CompareOrdinal(encoding, _webNameUnicode) == 0)
return;
else
//if the encoding attribute has any other value we throw an exception
throw new FileFormatException(SR.Get(SRID.EncodingNotSupported));
}
}
//if the XmlDeclaration is not present, or encoding attribute is not present, we
//base our decision on byte order marking. reader.Encoding will take that into account
//and return the correct value.
//Note: For Byte order markings that require additional information to be specified in
//the encoding attribute in XmlDeclaration have already been ruled out by the check above.
//Note: If not encoding attribute is present or no byte order marking is present the
//encoding default to UTF8
if (!(reader.Encoding is UnicodeEncoding || reader.Encoding is UTF8Encoding))
throw new FileFormatException(SR.Get(SRID.EncodingNotSupported));
}
/// <summary>
/// VerifyStreamReadArgs
/// </summary>
/// <param name="s">stream</param>
/// <param name="buffer">buffer</param>
/// <param name="offset">offset</param>
/// <param name="count">count</param>
/// <remarks>Common argument verification for Stream.Read()</remarks>
static internal void VerifyStreamReadArgs(Stream s, byte[] buffer, int offset, int count)
{
if (!s.CanRead)
throw new NotSupportedException(SR.Get(SRID.ReadNotSupported));
if (buffer == null)
{
throw new ArgumentNullException("buffer");
}
if (offset < 0)
{
throw new ArgumentOutOfRangeException("offset", SR.Get(SRID.OffsetNegative));
}
if (count < 0)
{
throw new ArgumentOutOfRangeException("count", SR.Get(SRID.ReadCountNegative));
}
checked // catch any integer overflows
{
if (offset + count > buffer.Length)
{
throw new ArgumentException(SR.Get(SRID.ReadBufferTooSmall), "buffer");
}
}
}
/// <summary>
/// VerifyStreamWriteArgs
/// </summary>
/// <param name="s"></param>
/// <param name="buffer"></param>
/// <param name="offset"></param>
/// <param name="count"></param>
/// <remarks>common argument verification for Stream.Write</remarks>
static internal void VerifyStreamWriteArgs(Stream s, byte[] buffer, int offset, int count)
{
if (!s.CanWrite)
throw new NotSupportedException(SR.Get(SRID.WriteNotSupported));
if (buffer == null)
{
throw new ArgumentNullException("buffer");
}
if (offset < 0)
{
throw new ArgumentOutOfRangeException("offset", SR.Get(SRID.OffsetNegative));
}
if (count < 0)
{
throw new ArgumentOutOfRangeException("count", SR.Get(SRID.WriteCountNegative));
}
checked
{
if (offset + count > buffer.Length)
throw new ArgumentException(SR.Get(SRID.WriteBufferTooSmall), "buffer");
}
}
/// <summary>
/// Read utility that is guaranteed to return the number of bytes requested
/// if they are available.
/// </summary>
/// <param name="stream">stream to read from</param>
/// <param name="buffer">buffer to read into</param>
/// <param name="offset">offset in buffer to write to</param>
/// <param name="count">bytes to read</param>
/// <returns>bytes read</returns>
/// <remarks>Normal Stream.Read does not guarantee how many bytes it will
/// return. This one does.</remarks>
internal static int ReliableRead(Stream stream, byte[] buffer, int offset, int count)
{
return ReliableRead(stream, buffer, offset, count, count);
}
/// <summary>
/// Read utility that is guaranteed to return the number of bytes requested
/// if they are available.
/// </summary>
/// <param name="stream">stream to read from</param>
/// <param name="buffer">buffer to read into</param>
/// <param name="offset">offset in buffer to write to</param>
/// <param name="requestedCount">count of bytes that we would like to read (max read size to try)</param>
/// <param name="requiredCount">minimal count of bytes that we would like to read (min read size to achieve)</param>
/// <returns>bytes read</returns>
/// <remarks>Normal Stream.Read does not guarantee how many bytes it will
/// return. This one does.</remarks>
internal static int ReliableRead(Stream stream, byte[] buffer, int offset, int requestedCount, int requiredCount)
{
Invariant.Assert(stream != null);
Invariant.Assert(buffer != null);
Invariant.Assert(buffer.Length > 0);
Invariant.Assert(offset >= 0);
Invariant.Assert(requestedCount >= 0);
Invariant.Assert(requiredCount >= 0);
Invariant.Assert(checked(offset + requestedCount <= buffer.Length));
Invariant.Assert(requiredCount <= requestedCount);
// let's read the whole block into our buffer
int totalBytesRead = 0;
while (totalBytesRead < requiredCount)
{
int bytesRead = stream.Read(buffer,
offset + totalBytesRead,
requestedCount - totalBytesRead);
if (bytesRead == 0)
{
break;
}
totalBytesRead += bytesRead;
}
return totalBytesRead;
}
/// <summary>
/// Read utility that is guaranteed to return the number of bytes requested
/// if they are available.
/// </summary>
/// <param name="reader">BinaryReader to read from</param>
/// <param name="buffer">buffer to read into</param>
/// <param name="offset">offset in buffer to write to</param>
/// <param name="count">bytes to read</param>
/// <returns>bytes read</returns>
/// <remarks>Normal Stream.Read does not guarantee how many bytes it will
/// return. This one does.</remarks>
internal static int ReliableRead(BinaryReader reader, byte[] buffer, int offset, int count)
{
return ReliableRead(reader, buffer, offset, count, count);
}
/// <summary>
/// Read utility that is guaranteed to return the number of bytes requested
/// if they are available.
/// </summary>
/// <param name="reader">BinaryReader to read from</param>
/// <param name="buffer">buffer to read into</param>
/// <param name="offset">offset in buffer to write to</param>
/// <param name="requestedCount">count of bytes that we would like to read (max read size to try)</param>
/// <param name="requiredCount">minimal count of bytes that we would like to read (min read size to achieve)</param>
/// <returns>bytes read</returns>
/// <remarks>Normal Stream.Read does not guarantee how many bytes it will
/// return. This one does.</remarks>
internal static int ReliableRead(BinaryReader reader, byte[] buffer, int offset, int requestedCount, int requiredCount)
{
Invariant.Assert(reader != null);
Invariant.Assert(buffer != null);
Invariant.Assert(buffer.Length > 0);
Invariant.Assert(offset >= 0);
Invariant.Assert(requestedCount >= 0);
Invariant.Assert(requiredCount >= 0);
Invariant.Assert(checked(offset + requestedCount <= buffer.Length));
Invariant.Assert(requiredCount <= requestedCount);
// let's read the whole block into our buffer
int totalBytesRead = 0;
while (totalBytesRead < requiredCount)
{
int bytesRead = reader.Read(buffer,
offset + totalBytesRead,
requestedCount - totalBytesRead);
if (bytesRead == 0)
{
break;
}
totalBytesRead += bytesRead;
}
return totalBytesRead;
}
/// <summary>
/// CopyStream utility that is guaranteed to return the number of bytes copied (may be less then requested,
/// if source stream doesn't have enough data)
/// </summary>
/// <param name="sourceStream">stream to read from</param>
/// <param name="targetStream">stream to write to </param>
/// <param name="bytesToCopy">number of bytes to be copied(use Int64.MaxValue if the whole stream needs to be copied)</param>
/// <param name="bufferSize">number of bytes to be copied (usually it is 4K for scenarios where we expect a lot of data
/// like in SparseMemoryStream case it could be larger </param>
/// <returns>bytes copied (might be less than requested if source stream is too short</returns>
/// <remarks>Neither source nor target stream are seeked; it is up to the caller to make sure that their positions are properly set.
/// Target stream isn't truncated even if it has more data past the area that was copied.</remarks>
internal static long CopyStream(Stream sourceStream, Stream targetStream, long bytesToCopy, int bufferSize)
{
Invariant.Assert(sourceStream != null);
Invariant.Assert(targetStream != null);
Invariant.Assert(bytesToCopy >= 0);
Invariant.Assert(bufferSize > 0);
byte[] buffer = new byte[bufferSize];
// let's read the whole block into our buffer
long bytesLeftToCopy = bytesToCopy;
while (bytesLeftToCopy > 0)
{
int bytesRead = sourceStream.Read(buffer, 0, (int)Math.Min(bytesLeftToCopy, (long)bufferSize));
if (bytesRead == 0)
{
targetStream.Flush();
return bytesToCopy - bytesLeftToCopy;
}
targetStream.Write(buffer, 0, bytesRead);
bytesLeftToCopy -= bytesRead;
}
// It must not be negative
Debug.Assert(bytesLeftToCopy == 0);
targetStream.Flush();
return bytesToCopy;
}
/// <summary>
/// Create a User-Domain Scoped IsolatedStorage file (or Machine-Domain scoped file if current user has no profile)
/// </summary>
/// <param name="fileName">returns the created file name</param>
/// <param name="retryCount">number of times to retry in case of name collision (legal values between 0 and 100)</param>
/// <returns>the created stream</returns>
/// <exception cref="IOException">retryCount was exceeded</exception>
/// <remarks>This function locks on IsoStoreSyncRoot and is thread-safe</remarks>
internal static Stream CreateUserScopedIsolatedStorageFileStreamWithRandomName(int retryCount, out String fileName)
{
// negative is illegal and place an upper limit of 100
if (retryCount < 0 || retryCount > 100)
throw new ArgumentOutOfRangeException("retryCount");
Stream s = null;
fileName = null;
// GetRandomFileName returns a very random name, but collisions are still possible so we
// retry if we encounter one.
while (true)
{
try
{
// This function returns a highly-random name in 8.3 format.
fileName = Path.GetRandomFileName();
lock (IsoStoreSyncRoot)
{
lock (IsolatedStorageFileLock)
{
s = GetDefaultIsolatedStorageFile().GetStream(fileName);
}
}
// if we get to here we have a success condition so we can safely exit
break;
}
catch (IOException)
{
// assume it is a name collision and ignore if we have not exhausted our retry count
if (--retryCount < 0)
throw;
}
}
return s;
}
/// <summary>
/// Calculate overlap between two blocks, returning the offset and length of the overlap
/// </summary>
/// <param name="block1Offset"></param>
/// <param name="block1Size"></param>
/// <param name="block2Offset"></param>
/// <param name="block2Size"></param>
/// <param name="overlapBlockOffset"></param>
/// <param name="overlapBlockSize"></param>
internal static void CalculateOverlap(long block1Offset, long block1Size,
long block2Offset, long block2Size,
out long overlapBlockOffset, out long overlapBlockSize)
{
checked
{
overlapBlockOffset = Math.Max(block1Offset, block2Offset);
overlapBlockSize = Math.Min(block1Offset + block1Size, block2Offset + block2Size) - overlapBlockOffset;
if (overlapBlockSize <= 0)
{
overlapBlockSize = 0;
}
}
}
/// <summary>
/// This method returns the count of xml attributes other than:
/// 1. xmlns="namespace"
/// 2. xmlns:someprefix="namespace"
/// Reader should be positioned at the Element whose attributes
/// are to be counted.
/// </summary>
/// <param name="reader"></param>
/// <returns>An integer indicating the number of non-xmlns attributes</returns>
internal static int GetNonXmlnsAttributeCount(XmlReader reader)
{
Debug.Assert(reader != null, "xmlReader should not be null");
Debug.Assert(reader.NodeType == XmlNodeType.Element, "XmlReader should be positioned at an Element");
int readerCount = 0;
//If true, reader moves to the attribute
//If false, there are no more attributes (or none)
//and in that case the position of the reader is unchanged.
//First time through, since the reader will be positioned at an Element,
//MoveToNextAttribute is the same as MoveToFirstAttribute.
while (reader.MoveToNextAttribute())
{
if (String.CompareOrdinal(reader.Name, XmlNamespace) != 0 &&
String.CompareOrdinal(reader.Prefix, XmlNamespace) != 0)
readerCount++;
}
//re-position the reader to the element
reader.MoveToElement();
return readerCount;
}
/// <summary>
/// Any usage of IsolatedStorage static properties should lock on this for thread-safety
/// </summary>
internal static Object IsoStoreSyncRoot
{
get
{
return _isoStoreSyncObject;
}
}
/// <summary>
/// IsolatedStorageFile (in mscorlib) has a deadlock (see Dev11 992845).
/// To work around that deadlock, we apply our own locking around calls
/// that invoke the problematic methods (IsolatedStorageFile.Lock and Unlock),
/// using this object as the lock token. See Dev11 880952.
/// If and when the CLR fixes 992845, this object and all its uses can
/// presumably be removed.
/// Caution: This workaround has two weaknesses:
/// 1. It depends on knowing how the problematic methods are used.
/// They are internal, and outside our control. The workaround
/// is based on the state of the CLR code as of July 2014. If
/// CLR changes it, WPF may have to change as well. However, it
/// looks like the CLR code hasn't changed (in ways that would
/// affect us) in many many years, and it seems unlikely it would.
/// 2. It only touches WPF's direct usage of IsolatedStorage. An app
/// could use IsolatedStorage directly - such usage risks
/// deadlock, independent of the workaround.
/// There's nothing WPF can do about either of these; the only way to
/// address them is by fixing 992845 at the CLR level. Fortunately, they
/// both seem unlikely to matter in practice.
/// </summary>
internal static Object IsolatedStorageFileLock
{
get
{
return _isolatedStorageFileLock;
}
}
#endregion Internal Methods
//------------------------------------------------------
//
// Private Methods
//
//------------------------------------------------------
/// <summary>
/// Delete file created using CreateUserScopedIsolatedStorageFileStreamWithRandomName()
/// </summary>
/// <param name="fileName"></param>
/// <remarks>Correctly handles temp/isostore differences</remarks>
private static void DeleteIsolatedStorageFile(String fileName)
{
lock (IsoStoreSyncRoot)
{
lock (IsolatedStorageFileLock)
{
try
{
GetDefaultIsolatedStorageFile().IsoFile.DeleteFile(fileName);
}
catch (IsolatedStorageException)
{
// if IsolatedStorage can't delete the file, just abandon it.
// This can happen when two different processes run the
// same app (DDVSO 259773).
}
}
}
}
/// <summary>
/// Returns the IsolatedStorageFile scoped to Assembly, Domain and User
/// </summary>
/// <remarks>Callers must lock on IsoStoreSyncRoot before calling this for thread-safety.
/// For example:
///
/// lock (IsoStoreSyncRoot)
/// {
/// // do something with the returned IsolatedStorageFile
/// PackagingUtilities.DefaultIsolatedStorageFile.DeleteFile(_isolatedStorageStreamFileName);
/// }
///
///</remarks>
private static ReliableIsolatedStorageFileFolder GetDefaultIsolatedStorageFile()
{
// Cache and re-use the same object for multiple requests - resurrect if disposed
if (_defaultFile == null || _defaultFile.IsDisposed())
{
_defaultFile = new ReliableIsolatedStorageFileFolder();
}
return _defaultFile;
}
///<summary>
/// Determine if current user has a User Profile so we can determine the appropriate
/// scope to use for IsolatedStorage functionality.
///</summary>
///<SecurityNote>
/// Critical - Asserts read registry permission...
/// - Asserts ControlPrincipal to access current user identity
/// TAS - only returns a bool
///</SecurityNote>
[SecurityCritical, SecurityTreatAsSafe]
private static bool UserHasProfile()
{
// Acquire permissions to read the one key we care about from the registry
// Acquite permission to query the current user identity
PermissionSet permissionSet = new PermissionSet(PermissionState.None);
permissionSet.AddPermission(new SecurityPermission(SecurityPermissionFlag.ControlPrincipal));
permissionSet.AddPermission(new RegistryPermission(RegistryPermissionAccess.Read,
_fullProfileListKeyName));
permissionSet.Assert();
bool userHasProfile = false;
RegistryKey userProfileKey = null;
try
{
// inspect registry and look for user profile via SID
string userSid = System.Security.Principal.WindowsIdentity.GetCurrent().User.Value;
userProfileKey = Registry.LocalMachine.OpenSubKey(_profileListKeyName + @"\" + userSid);
userHasProfile = userProfileKey != null;
}
finally
{
if (userProfileKey != null)
userProfileKey.Close();
CodeAccessPermission.RevertAssert();
}
return userHasProfile;
}
//------------------------------------------------------
//
// Private Classes
//
//------------------------------------------------------
/// <summary>
/// This class extends IsolatedStorageFileStream by adding a finalizer to ensure that
/// the underlying file is deleted when the stream is closed.
/// </summary>
private class SafeIsolatedStorageFileStream : IsolatedStorageFileStream
{
//------------------------------------------------------
//
// Internal Methods
//
//------------------------------------------------------
internal SafeIsolatedStorageFileStream(
string path, FileMode mode, FileAccess access,
FileShare share, ReliableIsolatedStorageFileFolder folder)
: base(path, mode, access, share, folder.IsoFile)
{
if (path == null)
throw new ArgumentNullException("path");
_path = path;
_folder = folder;
_folder.AddRef();
}
//------------------------------------------------------
//
// Protected Methods
//
//------------------------------------------------------
protected override void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
// Non-standard pattern - call base.Dispose() first.
// This is required because the base class is a stream and we cannot
// delete the underlying file storage before it has a chance to close
// and release it.
base.Dispose(disposing);
if (_path != null)
{
PackagingUtilities.DeleteIsolatedStorageFile(_path);
_path = null;
}
//Decrement the count of files
_folder.DecRef();
_folder = null;
GC.SuppressFinalize(this);
}
_disposed = true;
}
}
//------------------------------------------------------
//
// Private Fields
//
//------------------------------------------------------
private string _path;
private ReliableIsolatedStorageFileFolder _folder;
private bool _disposed;
}
/// <summary>
/// This class extends IsolatedStorageFileStream by adding a finalizer to ensure that
/// the underlying file is deleted when the stream is closed.
/// </summary>
private class ReliableIsolatedStorageFileFolder : IDisposable
{
//------------------------------------------------------
//
// Internal Properties
//
//------------------------------------------------------
internal IsolatedStorageFile IsoFile
{
get
{
CheckDisposed();
return _file;
}
}
/// <summary>
/// Call this when a new file is created in the isoFolder
/// </summary>
internal void AddRef()
{
lock (IsoStoreSyncRoot)
{
CheckDisposed();
checked
{
++_refCount;
}
}
}
/// <summary>
/// Call this when a new file is deleted from the isoFolder
/// </summary>
internal void DecRef()
{
lock (IsoStoreSyncRoot)
{
CheckDisposed();
checked
{
--_refCount;
}
if (_refCount <= 0)
{
Dispose();
}
}
}
/// <summary>
/// Only used within a lock statement
/// </summary>
/// <returns></returns>
internal bool IsDisposed()
{
return _disposed;
}
//------------------------------------------------------
//
// Internal Methods
//
//------------------------------------------------------
internal ReliableIsolatedStorageFileFolder()
{
_userHasProfile = UserHasProfile();
_file = GetCurrentStore();
}
/// <summary>
/// This triggers AddRef because SafeIsoStream does this in its constructor
/// </summary>
/// <param name="fileName"></param>
/// <returns></returns>
internal Stream GetStream(String fileName)
{
CheckDisposed();
// This constructor uses a scope that isolates by AppDomain and User
// We cannot include Assembly scope because it prevents sharing between Base and Core dll's
return new SafeIsolatedStorageFileStream(
fileName, FileMode.Create, FileAccess.ReadWrite, FileShare.None,
this);
}
/// <summary>
/// IDisposable.Dispose()
/// </summary>
public void Dispose()
{
Dispose(true);
}
//------------------------------------------------------
//
// Protected Methods
//
//------------------------------------------------------
protected virtual void Dispose(bool disposing)
{
try
{
// only lock if we are disposing
if (disposing)
{
lock (IsoStoreSyncRoot)
{
// update state before calling ISF.Remove, in case it
// throws an exception (DDVSO 259773)
IsolatedStorageFile file = _file;
_file = null;
GC.SuppressFinalize(this);
if (!_disposed)
{
_disposed = true;
using (file)
{
lock (IsolatedStorageFileLock)
{
file.Remove();
}
}
}
}
}
else
{
// We cannot rely on other managed objects in our finalizer
// so we allocate a fresh object to help us delete our temp folder.
using (IsolatedStorageFile file = GetCurrentStore())
{
file.Remove();
}
}
}
catch (IsolatedStorageException)
{
// IsolatedStorageException can be thrown if the files that are being deleted, are
// currently in use. These files will not get cleaned up.
}
}
//------------------------------------------------------
//
// Private Methods
//
//------------------------------------------------------
/// <summary>
/// Call this sparingly as it allocates resources
/// </summary>
/// <returns></returns>
// Note: For workaround (Dev11 880952), nothing is needed here;
// Of the 6 convenience methods around ISF.GetStore, only
// GetUserStoreForApplication uses the problematic locking.
private IsolatedStorageFile GetCurrentStore()
{
if (_userHasProfile)
{
return IsolatedStorageFile.GetUserStoreForDomain();
}
else
{
return IsolatedStorageFile.GetMachineStoreForDomain();
}
}
~ReliableIsolatedStorageFileFolder()
{
Dispose(false);
}
void CheckDisposed()
{
if (_disposed)
throw new ObjectDisposedException("ReliableIsolatedStorageFileFolder");
}
//------------------------------------------------------
//
// Private Fields
//
//------------------------------------------------------
private static IsolatedStorageFile _file;
private static bool _userHasProfile;
private int _refCount; // number of outstanding "streams"
private bool _disposed;
}
//------------------------------------------------------
//
// Private Fields
//
//------------------------------------------------------
/// <summary>
/// Synchronize access to IsolatedStorage methods that can step on each-other
/// </summary>
/// <remarks>See PS 1468964 for details.</remarks>
private static Object _isoStoreSyncObject = new Object();
private static Object _isolatedStorageFileLock = new Object(); // see Dev11 880952
private static ReliableIsolatedStorageFileFolder _defaultFile;
private const string XmlNamespace = "xmlns";
private const string _encodingAttribute = "encoding";
private static readonly string _webNameUTF8 = Encoding.UTF8.WebName.ToUpperInvariant();
private static readonly string _webNameUnicode = Encoding.Unicode.WebName.ToUpperInvariant();
/// <summary>
/// ProfileListKeyName
/// </summary>
///<SecurityNote>
/// _profileListKeyName must remain readonly for security reasons
///</SecurityNote>
private const string _profileListKeyName = @"SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList";
private const string _fullProfileListKeyName = @"HKEY_LOCAL_MACHINE\" + _profileListKeyName;
}
}
|