// // Copyright (c) 2009 Microsoft Corporation. All rights reserved. // using System; using System.Runtime.Caching.Hosting; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics.CodeAnalysis; using System.Runtime.Caching.Resources; using System.Globalization; using System.Security; using System.Security.Permissions; using System.Text; using System.Threading; namespace System.Runtime.Caching { public sealed class HostFileChangeMonitor : FileChangeMonitor { private const int MAX_CHAR_COUNT_OF_LONG_CONVERTED_TO_HEXADECIMAL_STRING = 16; private static IFileChangeNotificationSystem s_fcn; private readonly ReadOnlyCollection _filePaths; private String _uniqueId; private Object _fcnState; private DateTimeOffset _lastModified; private HostFileChangeMonitor() { } // hide default .ctor private void InitDisposableMembers() { bool dispose = true; try { string uniqueId = null; if (_filePaths.Count == 1) { string path = _filePaths[0]; DateTimeOffset lastWrite; long fileSize; s_fcn.StartMonitoring(path, new OnChangedCallback(OnChanged), out _fcnState, out lastWrite, out fileSize); uniqueId = path + lastWrite.UtcDateTime.Ticks.ToString("X", CultureInfo.InvariantCulture) + fileSize.ToString("X", CultureInfo.InvariantCulture); _lastModified = lastWrite; } else { int capacity = 0; foreach (string path in _filePaths) { capacity += path.Length + (2 * MAX_CHAR_COUNT_OF_LONG_CONVERTED_TO_HEXADECIMAL_STRING); } Hashtable fcnState = new Hashtable(_filePaths.Count); _fcnState = fcnState; StringBuilder sb = new StringBuilder(capacity); foreach (string path in _filePaths) { if (fcnState.Contains(path)) { continue; } DateTimeOffset lastWrite; long fileSize; object state; s_fcn.StartMonitoring(path, new OnChangedCallback(OnChanged), out state, out lastWrite, out fileSize); fcnState[path] = state; sb.Append(path); sb.Append(lastWrite.UtcDateTime.Ticks.ToString("X", CultureInfo.InvariantCulture)); sb.Append(fileSize.ToString("X", CultureInfo.InvariantCulture)); if (lastWrite > _lastModified) { _lastModified = lastWrite; } } uniqueId = sb.ToString(); } _uniqueId = uniqueId; dispose = false; } finally { InitializationComplete(); if (dispose) { Dispose(); } } } [SecuritySafeCritical] [PermissionSet(SecurityAction.Assert, Unrestricted = true)] [SuppressMessage("Microsoft.Security", "CA2106:SecureAsserts", Justification = "Grandfathered suppression from original caching code checkin")] private static void InitFCN() { if (s_fcn == null) { IFileChangeNotificationSystem fcn = null; IServiceProvider host = ObjectCache.Host; if (host != null) { fcn = host.GetService(typeof(IFileChangeNotificationSystem)) as IFileChangeNotificationSystem; } if (fcn == null) { fcn = new FileChangeNotificationSystem(); } Interlocked.CompareExchange(ref s_fcn, fcn, null); } } // // protected members // protected override void Dispose(bool disposing) { if (disposing && s_fcn != null) { if (_filePaths != null && _fcnState != null) { if (_filePaths.Count > 1) { Hashtable fcnState = _fcnState as Hashtable; foreach (string path in _filePaths) { if (path != null) { object state = fcnState[path]; if (state != null) { s_fcn.StopMonitoring(path, state); } } } } else { string path = _filePaths[0]; if (path != null && _fcnState != null) { s_fcn.StopMonitoring(path, _fcnState); } } } } } // // public and internal members // public override ReadOnlyCollection FilePaths { get { return _filePaths; } } public override String UniqueId { get { return _uniqueId; } } public override DateTimeOffset LastModified { get { return _lastModified; } } public HostFileChangeMonitor(IList filePaths) { if (filePaths == null) { throw new ArgumentNullException("filePaths"); } if (filePaths.Count == 0) { throw new ArgumentException(RH.Format(R.Empty_collection, "filePaths")); } // *SECURITY* - filePaths is untrusted and should not be consumed outside of the sanitization method. _filePaths = SanitizeFilePathsList(filePaths); InitFCN(); InitDisposableMembers(); } [SecuritySafeCritical] private static ReadOnlyCollection SanitizeFilePathsList(IList filePaths) { List newList = new List(filePaths.Count); foreach (string path in filePaths) { if (String.IsNullOrEmpty(path)) { throw new ArgumentException(RH.Format(R.Collection_contains_null_or_empty_string, "filePaths")); } else { // DevDiv #269534: When we use a user-provided string in the constructor to this FileIOPermission and demand the permission, // we need to be certain that we're adding *exactly that string* to the new list we're generating. The original code tried to // optimize by checking all of the permissions upfront then doing a List.AddRange at the end, but this opened the method // to a TOCTOU attack since a malicious user could have modified the original IList between the two calls. new FileIOPermission(FileIOPermissionAccess.PathDiscovery, path).Demand(); newList.Add(path); } } return newList.AsReadOnly(); } } }