1 //------------------------------------------------------------------------------
2 // <copyright file="WinInetCache.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 //------------------------------------------------------------------------------
7 namespace Microsoft.Win32 {
10 using System.Net.Cache;
11 using System.Globalization;
13 using System.Threading;
14 using System.Collections.Specialized;
15 using System.Security.Permissions;
16 using System.Security.Principal;
17 using System.ComponentModel;
19 using System.Runtime.Versioning;
20 using System.Diagnostics;
22 // The class implements a RequestCache class contract on top of WinInet provider
23 // Author: Alexei Vopilov 21-Dec-2002
27 // Jan 25 2004 - Changed the visibility of the class from public to internal.
29 internal class WinInetCache: RequestCache {
30 private const int _MaximumResponseHeadersLength = Int32.MaxValue;
32 internal const string c_SPARSE_ENTRY_HACK = "~SPARSE_ENTRY:";
34 private readonly static DateTime s_MinDateTimeUtcForFileTimeUtc = DateTime.FromFileTimeUtc(0L);
35 internal readonly static TimeSpan s_MaxTimeSpanForInt32 = TimeSpan.FromSeconds((double)int.MaxValue);
37 // private static readonly RequestCachePermission s_ReadPermission = new RequestCachePermission(RequestCacheActions.CacheRead);
38 // private static readonly RequestCachePermission s_ReadWritePermission = new RequestCachePermission(RequestCacheActions.CacheReadWrite);
40 /// <summary> A public constructor that demands CacheReadWrite flag for RequestCachePermission </summary>
41 internal WinInetCache(bool isPrivateCache, bool canWrite, bool async): base (isPrivateCache, canWrite)
45 s_ReadWritePermission.Demand();
49 s_ReadPermission.Demand();
53 // Per VsWhidbey#88276 it was decided to not enforce any cache metadata limits for WinInet cache provider.
54 // (Microsoft 7/17 made this a const to avoid threading issues)
55 //_MaximumResponseHeadersLength = Int32.MaxValue;
59 if (_MaximumResponseHeadersLength == 0) {
60 NetConfiguration config = (NetConfiguration)System.Configuration.ConfigurationManager.GetSection("system.net/settings");
62 if (config.maximumResponseHeadersLength < 0 && config.maximumResponseHeadersLength != -1) {
63 throw new ArgumentOutOfRangeException(SR.GetString(SR.net_toosmall));
65 _MaximumResponseHeadersLength = config.maximumResponseHeadersLength * 1024;
68 _MaximumResponseHeadersLength = 64 * 1024;
76 /// Gets the data stream and the metadata associated with a IE cache entry.
77 /// Returns Stream.Null if there is no entry found.
80 internal override Stream Retrieve(string key, out RequestCacheEntry cacheEntry)
82 return Lookup(key, out cacheEntry, true);
85 internal override bool TryRetrieve(string key, out RequestCacheEntry cacheEntry, out Stream readStream)
87 readStream = Lookup(key, out cacheEntry, false);
88 if (readStream == null)
94 // Returns a write stream associated with the IE cache string Key.
95 // Passed parameters allow cache to update an entry metadata accordingly.
96 // <remarks> The commit operation will happen upon the stream closure. </remarks>
97 internal override Stream Store(string key, long contentLength, DateTime expiresUtc, DateTime lastModifiedUtc, TimeSpan maxStale, StringCollection entryMetadata, StringCollection systemMetadata)
99 return GetWriteStream(key, contentLength, expiresUtc, lastModifiedUtc, maxStale, entryMetadata, systemMetadata, true);
101 // Does not throw on an error
102 internal override bool TryStore(string key, long contentLength, DateTime expiresUtc, DateTime lastModifiedUtc, TimeSpan maxStale, StringCollection entryMetadata, StringCollection systemMetadata, out Stream writeStream)
104 writeStream = GetWriteStream(key, contentLength, expiresUtc, lastModifiedUtc, maxStale, entryMetadata, systemMetadata, false);
105 if (writeStream == null)
113 /// Removes an item from the IE cache. Throws Win32Excpetion if failed
116 internal override void Remove(string key) {
119 throw new ArgumentNullException("key");
124 if(Logging.On)Logging.PrintError(Logging.RequestCache, SR.GetString(SR.net_log_operation_failed_with_error, "WinInetCache.Remove()", SR.GetString(SR.net_cache_access_denied, "Write")));
128 _WinInetCache.Entry entry = new _WinInetCache.Entry(key, _MaximumResponseHeadersLength);
130 if (_WinInetCache.Remove(entry) != _WinInetCache.Status.Success && entry.Error != _WinInetCache.Status.FileNotFound) {
131 Win32Exception win32Exception = new Win32Exception((int)entry.Error);
132 if(Logging.On)Logging.PrintError(Logging.RequestCache, SR.GetString(SR.net_log_cache_cannot_remove, "WinInetCache.Remove()", key, win32Exception.Message));
133 throw new IOException(SR.GetString(SR.net_cache_retrieve_failure, win32Exception.Message), win32Exception);
136 if(Logging.On)Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_key_status, "WinInetCache.Remove(), ", key, entry.Error.ToString()));
139 // Tries to remove an item from the cache, possible by applying unsafe entry unlocking.
140 // Returns true if successful, false otherwise
141 internal override bool TryRemove(string key)
143 return TryRemove(key, false);
147 // Purges Wininet Cache Entry by Unlocking it's file until zero count (if forceRemove is set).
149 internal bool TryRemove(string key, bool forceRemove) {
152 throw new ArgumentNullException("key");
157 if(Logging.On)Logging.PrintError(Logging.RequestCache, SR.GetString(SR.net_log_operation_failed_with_error, "WinInetCache.TryRemove()", SR.GetString(SR.net_cache_access_denied, "Write")));
161 _WinInetCache.Entry entry = new _WinInetCache.Entry(key, _MaximumResponseHeadersLength);
163 if (_WinInetCache.Remove(entry) == _WinInetCache.Status.Success || entry.Error == _WinInetCache.Status.FileNotFound) {
164 if(Logging.On)Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_key_status, "WinInetCache.TryRemove()", key, entry.Error.ToString()));
167 else if (!forceRemove) {
168 if(Logging.On)Logging.PrintWarning(Logging.RequestCache, SR.GetString(SR.net_log_cache_key_remove_failed_status, "WinInetCache.TryRemove()", key, entry.Error.ToString()));
172 _WinInetCache.Status status = _WinInetCache.LookupInfo(entry);
173 if (status == _WinInetCache.Status.Success) {
174 while (entry.Info.UseCount != 0) {
175 if(Logging.On)Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_key_status, "WinInetCache.TryRemove()", key, entry.Error.ToString()));
176 if(Logging.On)Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_usecount_file, "WinInetCache.TryRemove()", entry.Info.UseCount, entry.Filename));
177 if (!UnsafeNclNativeMethods.UnsafeWinInetCache.UnlockUrlCacheEntryFileW(key, 0)) {
180 status = _WinInetCache.LookupInfo(entry);
183 _WinInetCache.Remove(entry);
184 if (entry.Error != _WinInetCache.Status.Success && _WinInetCache.LookupInfo(entry) == _WinInetCache.Status.FileNotFound) {
185 entry.Error = _WinInetCache.Status.Success;
187 if(Logging.On)Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_key_status, "WinInetCache.TryRemove()", key, entry.Error.ToString()));
188 return entry.Error == _WinInetCache.Status.Success;
192 /// Updates only the metadata associated with IE cached entry.
195 internal override void Update(string key, DateTime expiresUtc, DateTime lastModifiedUtc, DateTime lastSynchronizedUtc, TimeSpan maxStale, StringCollection entryMetadata, StringCollection systemMetadata)
197 UpdateInfo(key, expiresUtc, lastModifiedUtc, lastSynchronizedUtc, maxStale, entryMetadata, systemMetadata, true);
200 // Does not throw on an error
201 internal override bool TryUpdate(string key, DateTime expiresUtc, DateTime lastModifiedUtc, DateTime lastSynchronizedUtc, TimeSpan maxStale, StringCollection entryMetadata, StringCollection systemMetadata)
203 return UpdateInfo(key, expiresUtc, lastModifiedUtc, lastSynchronizedUtc, maxStale, entryMetadata, systemMetadata, false);
206 // Once the entry is unlocked it must not be updated
207 // There is a design flaw in current RequestCache contract, it should allow detection of already replaced entry when updating one.
209 internal override void UnlockEntry(Stream stream)
211 ReadStream readStream = stream as ReadStream;
213 if(Logging.On) Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_stream, "WinInetCache.UnlockEntry", (stream == null ? "<null>" : stream.GetType().FullName)));
215 // could be wrapped by some other stream, that's ok because the entry is unlocked on stream.Close anyway
216 if (readStream == null)
218 readStream.UnlockEntry();
223 private Stream Lookup(string key, out RequestCacheEntry cacheEntry, bool isThrow)
225 if(Logging.On) Logging.Enter(Logging.RequestCache, "WinInetCache.Retrieve", "key = " + key);
228 throw new ArgumentNullException("key");
231 Stream result = Stream.Null;
232 SafeUnlockUrlCacheEntryFile handle = null;
233 _WinInetCache.Entry entry = new _WinInetCache.Entry(key, _MaximumResponseHeadersLength);
235 handle = _WinInetCache.LookupFile(entry);
237 if (entry.Error == _WinInetCache.Status.Success) {
238 if(Logging.On) Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_filename, "WinInetCache.Retrieve()", entry.Filename, entry.Error));
240 cacheEntry = new RequestCacheEntry(entry, IsPrivateCache);
242 if (entry.MetaInfo != null && entry.MetaInfo.Length != 0)
244 // convert metadata to upto two string collections
248 int length = entry.MetaInfo.Length;
249 StringCollection sc = new StringCollection();
250 fixed (char * ch = entry.MetaInfo)
253 for (i = 0; i < length; ++i)
255 // WinInet specific block!!
256 // The point here is that wininet scans for ~U: throughly with no regard to \r\n so we mimic the same behavior
257 if (i == start && i+2 < length)
259 if (ch[i] == '~' && (ch[i+1] == 'U' || ch[i+1] == 'u') && ch[i+2] == ':')
261 //Security: don't report what the username is
262 while(i < length && ch[++i] != '\n') {;}
269 // note a metadata entry must terminate with \r\n
271 if ((i+1 == length) || (ch[i] == '\n'))
273 string value = entry.MetaInfo.Substring(start, (ch[i-1] == '\r'? (i-1):(i+1)) - start);
275 if (value.Length == 0 && cacheEntry.EntryMetadata == null)
277 // done with headers, prepare for system metadata
278 cacheEntry.EntryMetadata = sc;
279 sc = new StringCollection();
283 //WinInet specific block!!
284 // HACK: if we are parsing system metadata and have found our hack,
285 // then convert it to a sparse entry type (entry.Info.EntryType & _WinInetCache.EntryType.Sparse)
286 if (cacheEntry.EntryMetadata != null && value.StartsWith(c_SPARSE_ENTRY_HACK, StringComparison.Ordinal))
287 cacheEntry.IsPartialEntry = true;
295 if (cacheEntry.EntryMetadata == null )
296 {cacheEntry.EntryMetadata = sc;}
298 {cacheEntry.SystemMetadata = sc;}
302 result = new ReadStream(entry, handle, async);
306 if (handle != null) {
310 cacheEntry = new RequestCacheEntry();
311 cacheEntry.IsPrivateEntry = IsPrivateCache;
313 if (entry.Error != _WinInetCache.Status.FileNotFound)
315 if(Logging.On)Logging.PrintError(Logging.RequestCache, SR.GetString(SR.net_log_cache_lookup_failed, "WinInetCache.Retrieve()", new Win32Exception((int)entry.Error).Message));
316 if(Logging.On)Logging.Exit(Logging.RequestCache, "WinInetCache.Retrieve()");
319 Win32Exception win32Exception = new Win32Exception((int)entry.Error);
320 throw new IOException(SR.GetString(SR.net_cache_retrieve_failure, win32Exception.Message), win32Exception);
326 catch (Exception exception) {
327 if (exception is ThreadAbortException || exception is StackOverflowException || exception is OutOfMemoryException) {
331 if(Logging.On)Logging.PrintError(Logging.RequestCache, SR.GetString(SR.net_log_cache_exception, "WinInetCache.Retrieve()", exception.ToString()));
332 if(Logging.On)Logging.Exit(Logging.RequestCache, "WinInetCache.Retrieve()");
334 if (handle != null) {
338 result = Stream.Null;
339 cacheEntry = new RequestCacheEntry();
340 cacheEntry.IsPrivateEntry = IsPrivateCache;
347 if(Logging.On)Logging.Exit(Logging.RequestCache, "WinInetCache.Retrieve()", "Status = " + entry.Error.ToString());
353 private string CombineMetaInfo(StringCollection entryMetadata, StringCollection systemMetadata)
355 if ((entryMetadata == null || entryMetadata.Count == 0) && (systemMetadata == null || systemMetadata.Count == 0))
358 StringBuilder sb = new StringBuilder(100);
360 if (entryMetadata != null && entryMetadata.Count != 0)
361 for (i = 0; i < entryMetadata.Count; ++i)
363 if (entryMetadata[i] == null || entryMetadata[i].Length == 0)
365 sb.Append(entryMetadata[i]).Append("\r\n");
368 if (systemMetadata != null && systemMetadata.Count != 0)
370 // mark a start for system metadata
372 for (i = 0; i < systemMetadata.Count; ++i)
374 if (systemMetadata[i] == null || systemMetadata[i].Length == 0)
376 sb.Append(systemMetadata[i]).Append("\r\n");}
379 return sb.ToString();
383 private Stream GetWriteStream(string key, long contentLength, DateTime expiresUtc, DateTime lastModifiedUtc, TimeSpan maxStale, StringCollection entryMetadata, StringCollection systemMetadata, bool isThrow)
385 if(Logging.On) Logging.Enter(Logging.RequestCache, "WinInetCache.Store()", "Key = " + key);
388 throw new ArgumentNullException("key");
393 if(Logging.On)Logging.PrintError(Logging.RequestCache, SR.GetString(SR.net_log_operation_failed_with_error, "WinInetCache.Store()", SR.GetString(SR.net_cache_access_denied, "Write")));
394 if(Logging.On) Logging.Exit(Logging.RequestCache, "WinInetCache.Store");
397 throw new InvalidOperationException(SR.GetString(SR.net_cache_access_denied, "Write"));
403 _WinInetCache.Entry entry = new _WinInetCache.Entry(key, _MaximumResponseHeadersLength);
406 entry.OptionalLength = (contentLength < 0L)? 0: contentLength > Int32.MaxValue? Int32.MaxValue: (int)(contentLength);
408 entry.Info.ExpireTime = _WinInetCache.FILETIME.Zero;
409 if (expiresUtc != DateTime.MinValue && expiresUtc > s_MinDateTimeUtcForFileTimeUtc) {
410 entry.Info.ExpireTime = new _WinInetCache.FILETIME(expiresUtc.ToFileTimeUtc());
413 entry.Info.LastModifiedTime = _WinInetCache.FILETIME.Zero;
414 if (lastModifiedUtc != DateTime.MinValue && lastModifiedUtc > s_MinDateTimeUtcForFileTimeUtc) {
415 entry.Info.LastModifiedTime = new _WinInetCache.FILETIME(lastModifiedUtc.ToFileTimeUtc());
418 entry.Info.EntryType = _WinInetCache.EntryType.NormalEntry;
419 if (maxStale > TimeSpan.Zero) {
420 if (maxStale >= s_MaxTimeSpanForInt32) {
421 maxStale = s_MaxTimeSpanForInt32;
423 entry.Info.U.ExemptDelta = (int)maxStale.TotalSeconds;
424 entry.Info.EntryType = _WinInetCache.EntryType.StickyEntry;
428 entry.MetaInfo = CombineMetaInfo(entryMetadata, systemMetadata);
430 entry.FileExt = "cache";
432 Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_expected_length, entry.OptionalLength));
433 Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_last_modified, (entry.Info.LastModifiedTime.IsNull? "0": DateTime.FromFileTimeUtc(entry.Info.LastModifiedTime.ToLong()).ToString("r"))));
434 Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_expires, (entry.Info.ExpireTime.IsNull? "0": DateTime.FromFileTimeUtc(entry.Info.ExpireTime.ToLong()).ToString("r"))));
435 Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_max_stale, (maxStale > TimeSpan.Zero? ((int)maxStale.TotalSeconds).ToString():"n/a")));
436 if (Logging.IsVerbose(Logging.RequestCache)) {
437 Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_dumping_metadata));
438 if (entry.MetaInfo.Length == 0) {
439 Logging.PrintInfo(Logging.RequestCache, "<null>");
442 if (entryMetadata != null) {
443 foreach (string s in entryMetadata)
445 Logging.PrintInfo(Logging.RequestCache, s.TrimEnd(LineSplits));
448 Logging.PrintInfo(Logging.RequestCache, "------");
449 if (systemMetadata != null) {
450 foreach (string s in systemMetadata)
452 Logging.PrintInfo(Logging.RequestCache, s.TrimEnd(LineSplits));
459 _WinInetCache.CreateFileName(entry);
461 Stream result = Stream.Null;
462 if (entry.Error != _WinInetCache.Status.Success) {
465 Logging.PrintWarning(Logging.RequestCache, SR.GetString(SR.net_log_cache_create_failed, new Win32Exception((int)entry.Error).Message));
466 Logging.Exit(Logging.RequestCache, "WinInetCache.Store");
470 Win32Exception win32Exception = new Win32Exception((int)entry.Error);
471 throw new IOException(SR.GetString(SR.net_cache_retrieve_failure, win32Exception.Message), win32Exception);
478 result = new WriteStream(entry, isThrow, contentLength, async);
480 catch (Exception exception) {
481 if (exception is ThreadAbortException || exception is StackOverflowException || exception is OutOfMemoryException) {
487 Logging.PrintError(Logging.RequestCache, SR.GetString(SR.net_log_cache_exception, "WinInetCache.Store()", exception));
488 Logging.Exit(Logging.RequestCache, "WinInetCache.Store");
497 if(Logging.On) Logging.Exit(Logging.RequestCache, "WinInetCache.Store", "Filename = " + entry.Filename);
502 private bool UpdateInfo(string key, DateTime expiresUtc, DateTime lastModifiedUtc, DateTime lastSynchronizedUtc, TimeSpan maxStale, StringCollection entryMetadata, StringCollection systemMetadata, bool isThrow)
505 throw new ArgumentNullException("key");
508 if(Logging.On) Logging.Enter(Logging.RequestCache, "WinInetCache.Update", "Key = "+ key);
512 if(Logging.On)Logging.PrintError(Logging.RequestCache, SR.GetString(SR.net_log_operation_failed_with_error, "WinInetCache.Update()", SR.GetString(SR.net_cache_access_denied, "Write")));
513 if(Logging.On) Logging.Exit(Logging.RequestCache, "WinInetCache.Update()");
516 throw new InvalidOperationException(SR.GetString(SR.net_cache_access_denied, "Write"));
521 _WinInetCache.Entry entry = new _WinInetCache.Entry(key, _MaximumResponseHeadersLength);
522 _WinInetCache.Entry_FC attributes = _WinInetCache.Entry_FC.None;
524 if (expiresUtc != DateTime.MinValue && expiresUtc > s_MinDateTimeUtcForFileTimeUtc) {
525 attributes |= _WinInetCache.Entry_FC.Exptime;
526 entry.Info.ExpireTime = new _WinInetCache.FILETIME(expiresUtc.ToFileTimeUtc());
527 if(Logging.On)Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_set_expires, expiresUtc.ToString("r")));
530 if (lastModifiedUtc != DateTime.MinValue && lastModifiedUtc > s_MinDateTimeUtcForFileTimeUtc) {
531 attributes |= _WinInetCache.Entry_FC.Modtime;
532 entry.Info.LastModifiedTime = new _WinInetCache.FILETIME(lastModifiedUtc.ToFileTimeUtc());
533 if(Logging.On)Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_set_last_modified, lastModifiedUtc.ToString("r")));
536 if (lastSynchronizedUtc != DateTime.MinValue && lastSynchronizedUtc > s_MinDateTimeUtcForFileTimeUtc) {
537 attributes |= _WinInetCache.Entry_FC.Synctime;
538 entry.Info.LastSyncTime = new _WinInetCache.FILETIME(lastSynchronizedUtc.ToFileTimeUtc());
539 if(Logging.On)Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_set_last_synchronized, lastSynchronizedUtc.ToString("r")));
542 if (maxStale != TimeSpan.MinValue) {
543 attributes |= _WinInetCache.Entry_FC.ExemptDelta|_WinInetCache.Entry_FC.Attribute;
544 entry.Info.EntryType = _WinInetCache.EntryType.NormalEntry;
545 if (maxStale >= TimeSpan.Zero) {
546 if (maxStale >= s_MaxTimeSpanForInt32) {
547 maxStale = s_MaxTimeSpanForInt32;
549 entry.Info.EntryType = _WinInetCache.EntryType.StickyEntry;
550 entry.Info.U.ExemptDelta = (int)maxStale.TotalSeconds;
551 if(Logging.On)Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_enable_max_stale, ((int)maxStale.TotalSeconds).ToString()));
554 entry.Info.U.ExemptDelta = 0;
555 if(Logging.On)Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_disable_max_stale));
559 entry.MetaInfo = CombineMetaInfo(entryMetadata, systemMetadata);
560 if (entry.MetaInfo.Length != 0) {
561 attributes |= _WinInetCache.Entry_FC.Headerinfo;
564 Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_dumping));
565 if (Logging.IsVerbose(Logging.RequestCache)) {
566 Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_dumping));
567 if (entryMetadata != null) {
568 foreach (string s in entryMetadata)
570 Logging.PrintInfo(Logging.RequestCache, s.TrimEnd(LineSplits));
573 Logging.PrintInfo(Logging.RequestCache, "------");
574 if (systemMetadata != null) {
575 foreach (string s in systemMetadata)
577 Logging.PrintInfo(Logging.RequestCache, s.TrimEnd(LineSplits));
584 _WinInetCache.Update(entry, attributes) ;
586 if (entry.Error != _WinInetCache.Status.Success) {
589 Logging.PrintWarning(Logging.RequestCache, SR.GetString(SR.net_log_cache_update_failed, "WinInetCache.Update()", entry.Key, new Win32Exception((int)entry.Error).Message));
590 Logging.Exit(Logging.RequestCache, "WinInetCache.Update()");
594 Win32Exception win32Exception = new Win32Exception((int)entry.Error);
595 throw new IOException(SR.GetString(SR.net_cache_retrieve_failure, win32Exception.Message), win32Exception);
600 if(Logging.On) Logging.Exit(Logging.RequestCache, "WinInetCache.Update()", "Status = " + entry.Error.ToString());
606 /// This is a FileStream wrapper on top of WinInet cache entry.
607 // The Close method will unlock the cached entry.
610 private class ReadStream: FileStream, ICloseEx, IRequestLifetimeTracker {
611 private string m_Key;
612 private int m_ReadTimeout;
613 private int m_WriteTimeout;
614 private SafeUnlockUrlCacheEntryFile m_Handle;
615 private int m_Disposed;
616 private int m_CallNesting;
617 private ManualResetEvent m_Event;
618 private bool m_Aborted;
619 private RequestLifetimeSetter m_RequestLifetimeSetter;
622 // Construct a read stream out of WinInet given handle
624 [FileIOPermission(SecurityAction.Assert, Unrestricted=true)]
625 [ResourceExposure(ResourceScope.Machine)]
626 [ResourceConsumption(ResourceScope.Machine)]
627 internal ReadStream(_WinInetCache.Entry entry, SafeUnlockUrlCacheEntryFile handle, bool async)
628 : base(entry.Filename, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete, 4096, async)
632 m_ReadTimeout = m_WriteTimeout = System.Threading.Timeout.Infinite;
635 // The stream will remain valid but after that call the entry can be replaced.
636 // If the entry has been replaced then the physical file that this stream points to may be deleted on stream.Close()
638 internal void UnlockEntry()
643 public override int Read(byte[] buffer, int offset, int count)
648 if (m_CallNesting != 0)
649 throw new NotSupportedException(SR.GetString(SR.net_no_concurrent_io_allowed));
651 throw ExceptionHelper.RequestAbortedException;
653 throw new ObjectDisposedException(GetType().FullName);
656 return base.Read(buffer, offset, count);
666 public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, Object state)
670 if (m_CallNesting != 0)
671 throw new NotSupportedException(SR.GetString(SR.net_no_concurrent_io_allowed));
673 throw ExceptionHelper.RequestAbortedException;
675 throw new ObjectDisposedException(GetType().FullName);
679 return base.BeginRead(buffer, offset, count, callback, state);
688 public override int EndRead(IAsyncResult asyncResult)
693 return base.EndRead(asyncResult);
698 try {m_Event.Set();} catch {} // the problem is he WaitHandle cannot tell if it is disposed or not
703 public void CloseEx(CloseExState closeState)
705 if ((closeState & CloseExState.Abort) != 0)
712 if ((closeState & CloseExState.Silent) == 0)
717 protected override void Dispose(bool disposing)
719 if (Interlocked.Exchange(ref m_Disposed, 1) == 0)
727 // if m_key is null, it means that the base constructor failed
734 if (m_CallNesting == 0)
737 m_Event = new ManualResetEvent(false);
740 RequestLifetimeSetter.Report(m_RequestLifetimeSetter);
746 // This assumes that FileStream will never hang on read
750 Debug.Assert(m_CallNesting == 0);
758 if (Logging.On) Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_key, "WinInetReadStream.Close()", m_Key));
759 // note, the handle may have been closed earlier if CacheProtocol knew that cache metadata update will not happen.
767 public override bool CanTimeout {
772 public override int ReadTimeout {
774 return m_ReadTimeout;
777 m_ReadTimeout = value;
780 public override int WriteTimeout {
782 return m_WriteTimeout;
785 m_WriteTimeout = value;
789 void IRequestLifetimeTracker.TrackRequestLifetime(long requestStartTimestamp)
791 Debug.Assert(m_RequestLifetimeSetter == null, "TrackRequestLifetime called more than once.");
792 m_RequestLifetimeSetter = new RequestLifetimeSetter(requestStartTimestamp);
798 private class WriteStream: FileStream, ICloseEx {
799 private _WinInetCache.Entry m_Entry;
800 private bool m_IsThrow;
801 private long m_StreamSize;
802 private bool m_Aborted;
803 private int m_ReadTimeout;
804 private int m_WriteTimeout;
805 private int m_Disposed;
806 private int m_CallNesting;
807 private ManualResetEvent m_Event;
808 private bool m_OneWriteSucceeded;
811 [FileIOPermission(SecurityAction.Assert, Unrestricted=true)]
812 [ResourceExposure(ResourceScope.Machine)]
813 [ResourceConsumption(ResourceScope.Machine)]
814 internal WriteStream(_WinInetCache.Entry entry, bool isThrow, long streamSize, bool async):
815 base(entry.Filename, FileMode.Create, FileAccess.ReadWrite, FileShare.None, 4096, async) {
819 m_StreamSize = streamSize;
820 m_OneWriteSucceeded = streamSize == 0; //if 0 is expected or the lenght is unknonw we will commit even an emtpy stream.
821 m_ReadTimeout = m_WriteTimeout = System.Threading.Timeout.Infinite;
824 public override bool CanTimeout {
829 public override int ReadTimeout {
831 return m_ReadTimeout;
834 m_ReadTimeout = value;
837 public override int WriteTimeout {
839 return m_WriteTimeout;
842 m_WriteTimeout = value;
846 public override void Write(byte[] buffer, int offset, int count)
851 throw ExceptionHelper.RequestAbortedException;
853 throw new ObjectDisposedException(GetType().FullName);
857 base.Write(buffer, offset, count);
858 if (m_StreamSize > 0)
859 m_StreamSize -= count;
860 if (!m_OneWriteSucceeded && count != 0)
861 m_OneWriteSucceeded = true;
875 public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, Object state)
879 if (m_CallNesting != 0)
880 throw new NotSupportedException(SR.GetString(SR.net_no_concurrent_io_allowed));
882 throw ExceptionHelper.RequestAbortedException;
884 throw new ObjectDisposedException(GetType().FullName);
889 if (m_StreamSize > 0)
890 m_StreamSize -= count;
891 return base.BeginWrite(buffer, offset, count, callback, state);
902 public override void EndWrite(IAsyncResult asyncResult)
907 base.EndWrite(asyncResult);
908 if (!m_OneWriteSucceeded)
909 m_OneWriteSucceeded = true;
918 try {m_Event.Set();} catch {} // the problem is he WaitHandle cannot tell if it is disposed or not
923 public void CloseEx(CloseExState closeState)
925 // For abnormal stream termination we will commit a partial cache entry
926 if ((closeState & CloseExState.Abort) != 0)
933 if ((closeState & CloseExState.Silent) == 0)
938 [ResourceExposure(ResourceScope.None)]
939 [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
940 protected override void Dispose(bool disposing)
942 //if m_Entry is null, it means that the base constructor failed
943 if (Interlocked.Exchange(ref m_Disposed, 1) == 0 && m_Entry != null) {
947 if (m_CallNesting == 0)
948 base.Dispose(disposing);
950 m_Event = new ManualResetEvent(false);
954 // This assumes the FileStream will never hang on write
956 if (disposing && m_Event != null)
963 Debug.Assert(m_CallNesting == 0);
966 base.Dispose(disposing);
969 // We use TriState to indicate:
973 TriState cacheCommitAction;
974 if (m_StreamSize < 0)
978 if (m_OneWriteSucceeded)
979 cacheCommitAction = TriState.Unspecified; // Partial
981 cacheCommitAction = TriState.False; // Delete
985 cacheCommitAction = TriState.True; // Full
990 if (!m_OneWriteSucceeded)
992 cacheCommitAction = TriState.False; // Delete
996 if (m_StreamSize > 0)
997 cacheCommitAction = TriState.Unspecified; // Partial
999 cacheCommitAction = TriState.True; // Full
1003 if (cacheCommitAction == TriState.False)
1007 if(Logging.On)Logging.PrintWarning(Logging.RequestCache, SR.GetString(SR.net_log_cache_no_commit, "WinInetWriteStream.Close()"));
1008 // Delete temp cache file
1009 File.Delete(m_Entry.Filename);
1011 catch (Exception exception)
1013 if (exception is ThreadAbortException || exception is StackOverflowException || exception is OutOfMemoryException) {
1016 if(Logging.On)Logging.PrintError(Logging.RequestCache, SR.GetString(SR.net_log_cache_error_deleting_filename, "WinInetWriteStream.Close()", m_Entry.Filename));
1019 //Delete an old entry if there was one
1020 _WinInetCache.Status errorStatus = _WinInetCache.Remove(m_Entry);
1021 if (errorStatus != _WinInetCache.Status.Success && errorStatus != _WinInetCache.Status.FileNotFound)
1023 if(Logging.On)Logging.PrintWarning(Logging.RequestCache, SR.GetString(SR.net_log_cache_delete_failed, "WinInetWriteStream.Close()", m_Entry.Key, new Win32Exception((int)m_Entry.Error).Message));
1031 m_Entry.OriginalUrl = null;
1034 // ATTN: WinIent currently does NOT support _WinInetCache.EntryType.Sparse
1035 // USING a workaround
1037 if (cacheCommitAction == TriState.Unspecified)
1039 // WinInet will not report this entry back we set this flag
1040 // m_Entry.Info.EntryType |= _WinInetCache.EntryType.Sparse; // does not work for now
1042 // HACK: WinInet does not support SPARSE_ENTRY bit
1043 // We want to add c_SPARSE_ENTRY_HACK into the systemmetadata i.e. to the second block of strings separated by an empty line (\r\n).
1044 if (m_Entry.MetaInfo == null || m_Entry.MetaInfo.Length == 0 ||
1045 (m_Entry.MetaInfo != "\r\n" && m_Entry.MetaInfo.IndexOf("\r\n\r\n", StringComparison.Ordinal) == -1))
1047 m_Entry.MetaInfo = "\r\n"+ WinInetCache.c_SPARSE_ENTRY_HACK+"\r\n";
1051 m_Entry.MetaInfo += WinInetCache.c_SPARSE_ENTRY_HACK+"\r\n";
1055 if (_WinInetCache.Commit(m_Entry) != _WinInetCache.Status.Success)
1057 if(Logging.On)Logging.PrintError(Logging.RequestCache, SR.GetString(SR.net_log_cache_commit_failed, "WinInetWriteStream.Close()", m_Entry.Key, new Win32Exception((int)m_Entry.Error).Message));
1060 // Delete temp cache file
1061 File.Delete(m_Entry.Filename);
1063 catch (Exception exception)
1065 if (exception is ThreadAbortException || exception is StackOverflowException || exception is OutOfMemoryException) {
1068 if(Logging.On)Logging.PrintError(Logging.RequestCache, SR.GetString(SR.net_log_cache_error_deleting_filename, "WinInetWriteStream.Close()", m_Entry.Filename));
1073 Win32Exception win32Exception = new Win32Exception((int)m_Entry.Error);
1074 throw new IOException(SR.GetString(SR.net_cache_retrieve_failure, win32Exception.Message), win32Exception);
1081 if (m_StreamSize > 0 || (m_StreamSize < 0 && m_Aborted))
1082 Logging.PrintWarning(Logging.RequestCache, SR.GetString(SR.net_log_cache_committed_as_partial, "WinInetWriteStream.Close()", m_Entry.Key, (m_StreamSize > 0 ? m_StreamSize.ToString(CultureInfo.CurrentCulture) : SR.GetString(SR.net_log_unknown))));
1083 Logging.PrintInfo(Logging.RequestCache, "WinInetWriteStream.Close(), Key = " + m_Entry.Key + ", Commit Status = " + m_Entry.Error.ToString());
1087 if ((m_Entry.Info.EntryType & _WinInetCache.EntryType.StickyEntry) == _WinInetCache.EntryType.StickyEntry)
1089 if (_WinInetCache.Update(m_Entry, _WinInetCache.Entry_FC.ExemptDelta) != _WinInetCache.Status.Success)
1091 if(Logging.On)Logging.PrintError(Logging.RequestCache, SR.GetString(SR.net_log_cache_update_failed, "WinInetWriteStream.Close(), Key = " + m_Entry.Key, new Win32Exception((int)m_Entry.Error).Message));
1094 Win32Exception win32Exception = new Win32Exception((int)m_Entry.Error);
1095 throw new IOException(SR.GetString(SR.net_cache_retrieve_failure, win32Exception.Message), win32Exception);
1099 if(Logging.On)Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_max_stale_and_update_status, "WinInetWriteFile.Close()", m_Entry.Info.U.ExemptDelta, m_Entry.Error.ToString()));
1102 base.Dispose(disposing);