diff --git a/src/FileCache/FileCacheManager.cs b/src/FileCache/FileCacheManager.cs index e662b61..5361cc4 100644 --- a/src/FileCache/FileCacheManager.cs +++ b/src/FileCache/FileCacheManager.cs @@ -14,8 +14,8 @@ public abstract class FileCacheManager { // Magic version for new sysfiles: 3.3.0 packed into a long. protected const ulong CACHE_VERSION = ( 3 << 16 - + 3 << 8 - + 0 << 0); + + 3 << 8 + + 0 << 0); public string CacheDir { get; set; } public string CacheSubFolder { get; set; } @@ -127,8 +127,21 @@ public virtual FileCachePayload ReadFile(FileCache.PayloadMode mode, string key, } try { - // TODO: In part of the merge it looked like the policy was force serialized with LocalCacheBinder(), is this intended? - payload.Policy = Deserialize(policyPath) as SerializableCacheItemPolicy; + if (File.Exists(policyPath)) + { + using (FileStream stream = GetStream(policyPath, FileMode.Open, FileAccess.Read, FileShare.Read)) + { + using (BinaryReader reader = new BinaryReader(stream)) + { + // TODO: In part of the merge it looked like the policy was force serialized with LocalCacheBinder(), is this intended? + payload.Policy = SerializableCacheItemPolicy.Deserialize(reader, stream.Length); + } + } + } + else + { + payload.Policy = new SerializableCacheItemPolicy(); + } } catch { @@ -268,13 +281,13 @@ public virtual long WriteFile(FileCache.PayloadMode mode, string key, FileCacheP //write the cache policy using (FileStream stream = GetStream(cachedPolicy, FileMode.Create, FileAccess.Write, FileShare.None)) { - BinaryFormatter formatter = new BinaryFormatter(); - formatter.Serialize(stream, data.Policy); - - // adjust cache size - cacheSizeDelta += new FileInfo(cachedPolicy).Length; + using (BinaryWriter writer = new BinaryWriter(stream)) + { + data.Policy.Serialize(writer); - stream.Close(); + // adjust cache size + cacheSizeDelta += new FileInfo(cachedPolicy).Length; + } } return cacheSizeDelta; @@ -555,7 +568,7 @@ public override Type BindToType(string assemblyName, string typeName) // Get the type using the typeName and assemblyName typeToDeserialize = Type.GetType(String.Format("{0}, {1}", - typeName, assemblyName)); + typeName, assemblyName)); return typeToDeserialize; } diff --git a/src/FileCache/SerializableCacheItemPolicy.cs b/src/FileCache/SerializableCacheItemPolicy.cs index 1eebbeb..a93f864 100644 --- a/src/FileCache/SerializableCacheItemPolicy.cs +++ b/src/FileCache/SerializableCacheItemPolicy.cs @@ -7,11 +7,19 @@ FileCache is distributed under the Apache License 2.0. Consult "LICENSE.txt" included in this package for the Apache License 2.0. */ +using System.IO; + + namespace System.Runtime.Caching { [Serializable] public class SerializableCacheItemPolicy { + // Magic version for new policies: 3.3.0 packed into a long. + protected const ulong CACHE_VERSION = ( 3 << 16 + + 3 << 8 + + 0 << 0); + public DateTimeOffset AbsoluteExpiration { get; set; } private TimeSpan _slidingExpiration; @@ -24,7 +32,7 @@ public TimeSpan SlidingExpiration set { _slidingExpiration = value; - if (_slidingExpiration > new TimeSpan()) + if (_slidingExpiration > TimeSpan.Zero) { AbsoluteExpiration = DateTimeOffset.Now.Add(_slidingExpiration); } @@ -45,5 +53,69 @@ public SerializableCacheItemPolicy() /// The cache key that this particular policy refers to /// public string Key { get; set; } + + /// + /// Serialize this policy to the supplied BinaryWriter. + /// + /// Older policies use the "[Serializable]" attribute and BinaryFormatter, which is a security risk: + /// https://docs.microsoft.com/nl-nl/dotnet/standard/serialization/binaryformatter-security-guide#preferred-alternatives + /// + /// The newer caches have a 'magic' header we'll look for and serialize their fields manually. + /// + public void Serialize(BinaryWriter writer) + { + writer.Write(CACHE_VERSION); + + writer.Write(AbsoluteExpiration.Date.ToBinary()); + writer.Write(AbsoluteExpiration.Offset.TotalMilliseconds); + + writer.Write(SlidingExpiration.TotalMilliseconds); + + writer.Write(Key); + } + + /// + /// Deserialize a policy from the supplied BinaryReader. + /// + /// Older policies use the "[Serializable]" attribute and BinaryFormatter, which is a security risk: + /// https://docs.microsoft.com/nl-nl/dotnet/standard/serialization/binaryformatter-security-guide#preferred-alternatives + /// + /// The newer caches have a 'magic' header we'll look for and deserialize their fields manually. + /// If the 'magic' header isn't found, this returns an empty policy. + /// + public static SerializableCacheItemPolicy Deserialize(BinaryReader reader, long streamLength) + { + // Can't even check for the magic version number; return empty policy. + if (streamLength < sizeof(ulong)) + return new SerializableCacheItemPolicy(); + + try + { + var version = reader.ReadUInt64(); + if (version != CACHE_VERSION) + // Just return an empty policy if we read an invalid one. + // This is likely the older "BinaryFormatter"-serialized policy. + return new SerializableCacheItemPolicy(); + + return new SerializableCacheItemPolicy { + AbsoluteExpiration = new DateTimeOffset(DateTime.FromBinary(reader.ReadInt64()), + TimeSpan.FromMilliseconds(reader.ReadDouble())), + SlidingExpiration = TimeSpan.FromMilliseconds(reader.ReadDouble()), + Key = reader.ReadString(), + }; + } + catch (Exception error) + { + if (error is EndOfStreamException + || error is IOException + || error is ObjectDisposedException) + { + // Just return an empty policy if we failed to read. + return new SerializableCacheItemPolicy(); + } + // Didn't expect this error type; rethrow it. + throw; + } + } } }