Updates referencesource to .NET 4.7
[mono.git] / mcs / class / referencesource / System / net / System / Net / Cache / IERequestCache.cs
1 //------------------------------------------------------------------------------
2 // <copyright file="WinInetCache.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 //------------------------------------------------------------------------------
6
7 namespace Microsoft.Win32 {
8     using System;
9     using System.Net;
10     using System.Net.Cache;
11     using System.Globalization;
12     using System.IO;
13     using System.Threading;
14     using System.Collections.Specialized;
15     using System.Security.Permissions;
16     using System.Security.Principal;
17     using System.ComponentModel;
18     using System.Text;
19     using System.Runtime.Versioning;
20     using System.Diagnostics;
21
22     // The class implements a RequestCache class contract on top of WinInet provider
23     // Author: Alexei Vopilov    21-Dec-2002
24     //
25     // Revision History:
26     //
27     // Jan 25 2004  - Changed the visibility of the class from public to internal.
28
29     internal class WinInetCache: RequestCache {
30         private const int _MaximumResponseHeadersLength = Int32.MaxValue;
31         private bool async;
32         internal const string c_SPARSE_ENTRY_HACK = "~SPARSE_ENTRY:";
33
34         private  readonly static DateTime   s_MinDateTimeUtcForFileTimeUtc = DateTime.FromFileTimeUtc(0L);
35         internal readonly static TimeSpan   s_MaxTimeSpanForInt32 = TimeSpan.FromSeconds((double)int.MaxValue);
36
37 //        private  static readonly RequestCachePermission s_ReadPermission      = new RequestCachePermission(RequestCacheActions.CacheRead);
38 //        private  static readonly RequestCachePermission s_ReadWritePermission = new RequestCachePermission(RequestCacheActions.CacheReadWrite);
39
40         /// <summary> A public constructor that demands CacheReadWrite flag for RequestCachePermission  </summary>
41         internal WinInetCache(bool isPrivateCache, bool canWrite, bool async): base (isPrivateCache, canWrite)
42         {
43             /***********
44             if (canWrite) {
45                 s_ReadWritePermission.Demand();
46             }
47             else
48             {
49                 s_ReadPermission.Demand();
50             }
51             ***********/
52
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;
56             this.async = async;
57
58             /********
59             if (_MaximumResponseHeadersLength == 0) {
60                 NetConfiguration config = (NetConfiguration)System.Configuration.ConfigurationManager.GetSection("system.net/settings");
61                 if (config != null) {
62                     if (config.maximumResponseHeadersLength < 0 && config.maximumResponseHeadersLength != -1) {
63                         throw new ArgumentOutOfRangeException(SR.GetString(SR.net_toosmall));
64                     }
65                     _MaximumResponseHeadersLength = config.maximumResponseHeadersLength * 1024;
66                 }
67                 else {
68                     _MaximumResponseHeadersLength = 64 * 1024;
69                 }
70             }
71             ********/
72         }
73
74         /// <summary>
75         /// <para>
76         /// Gets the data stream and the metadata associated with a IE cache entry.
77         /// Returns Stream.Null if there is no entry found.
78         /// </para>
79         /// </summary>
80         internal override Stream Retrieve(string key, out RequestCacheEntry cacheEntry)
81         {
82             return Lookup(key, out cacheEntry, true);
83         }
84         //
85         internal override bool TryRetrieve(string key, out RequestCacheEntry cacheEntry, out Stream  readStream)
86         {
87             readStream = Lookup(key, out cacheEntry, false);
88             if (readStream == null)
89             {
90                 return false;
91             }
92             return true;
93         }
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)
98         {
99             return GetWriteStream(key, contentLength, expiresUtc, lastModifiedUtc, maxStale, entryMetadata, systemMetadata, true);
100         }
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)
103         {
104             writeStream = GetWriteStream(key, contentLength, expiresUtc, lastModifiedUtc, maxStale, entryMetadata, systemMetadata, false);
105             if (writeStream == null)
106             {
107                 return false;
108             }
109             return true;
110         }
111         /// <summary>
112         /// <para>
113         /// Removes an item from the IE cache. Throws Win32Excpetion if failed
114         /// </para>
115         /// </summary>
116         internal override void Remove(string key) {
117
118             if (key == null) {
119                 throw new ArgumentNullException("key");
120             }
121
122             if (!CanWrite)
123             {
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")));
125                 return ;
126             }
127
128             _WinInetCache.Entry entry = new _WinInetCache.Entry(key, _MaximumResponseHeadersLength);
129
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);
134             }
135
136             if(Logging.On)Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_key_status, "WinInetCache.Remove(), ", key, entry.Error.ToString()));
137         }
138         //
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)
142         {
143             return TryRemove(key, false);
144
145         }
146         //
147         // Purges Wininet Cache Entry by Unlocking it's file until zero count (if forceRemove is set).
148         //
149         internal bool TryRemove(string key, bool forceRemove) {
150
151             if (key == null) {
152                 throw new ArgumentNullException("key");
153             }
154
155             if (!CanWrite)
156             {
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")));
158                 return false;
159             }
160
161             _WinInetCache.Entry entry = new _WinInetCache.Entry(key, _MaximumResponseHeadersLength);
162
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()));
165                 return true;
166             }
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()));
169                 return false;
170             }
171
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)) {
178                         break;
179                     }
180                     status = _WinInetCache.LookupInfo(entry);
181                 }
182             }
183             _WinInetCache.Remove(entry);
184             if (entry.Error != _WinInetCache.Status.Success && _WinInetCache.LookupInfo(entry) == _WinInetCache.Status.FileNotFound) {
185                 entry.Error = _WinInetCache.Status.Success;
186             }
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;
189         }
190         /// <summary>
191         /// <para>
192         /// Updates only the metadata associated with IE cached entry.
193         /// </para>
194         /// </summary>
195         internal override void Update(string key, DateTime expiresUtc, DateTime lastModifiedUtc, DateTime lastSynchronizedUtc, TimeSpan maxStale, StringCollection entryMetadata, StringCollection systemMetadata)
196         {
197             UpdateInfo(key, expiresUtc, lastModifiedUtc, lastSynchronizedUtc, maxStale, entryMetadata, systemMetadata, true);
198
199         }
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)
202         {
203             return UpdateInfo(key, expiresUtc, lastModifiedUtc, lastSynchronizedUtc, maxStale, entryMetadata, systemMetadata, false);
204         }
205         //
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.
208         //
209         internal override void UnlockEntry(Stream stream)
210         {
211             ReadStream readStream = stream as ReadStream;
212
213             if(Logging.On) Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_stream, "WinInetCache.UnlockEntry",  (stream == null ? "<null>" : stream.GetType().FullName)));
214
215             // could be wrapped by some other stream, that's ok because the entry is unlocked on stream.Close anyway
216             if (readStream == null)
217                 return;
218             readStream.UnlockEntry();
219         }
220         //
221         //
222         //
223         private Stream Lookup(string key, out RequestCacheEntry cacheEntry, bool isThrow)
224         {
225             if(Logging.On) Logging.Enter(Logging.RequestCache, "WinInetCache.Retrieve", "key = " + key);
226
227             if (key == null) {
228                 throw new ArgumentNullException("key");
229             }
230
231             Stream result = Stream.Null;
232             SafeUnlockUrlCacheEntryFile handle = null;
233             _WinInetCache.Entry entry = new _WinInetCache.Entry(key, _MaximumResponseHeadersLength);
234             try {
235                 handle = _WinInetCache.LookupFile(entry);
236
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));
239
240                     cacheEntry = new RequestCacheEntry(entry, IsPrivateCache);
241
242                     if (entry.MetaInfo != null && entry.MetaInfo.Length != 0)
243                     {
244                         // convert metadata to upto two string collections
245                         unsafe
246                         {
247                             int start = 0;
248                             int length = entry.MetaInfo.Length;
249                             StringCollection sc = new StringCollection();
250                             fixed (char * ch = entry.MetaInfo)
251                             {
252                                 int i;
253                                 for (i = 0; i < length; ++i)
254                                 {
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)
258                                     {
259                                         if (ch[i] == '~' && (ch[i+1] == 'U' || ch[i+1] == 'u') && ch[i+2] == ':')
260                                         {
261                                             //Security: don't report what the username is
262                                             while(i < length && ch[++i] != '\n') {;}
263                                             start = i+1;
264                                             continue;
265                                         }
266
267                                     }
268
269                                     // note a metadata entry must terminate with \r\n
270
271                                     if ((i+1 == length) || (ch[i] == '\n'))
272                                     {
273                                         string value = entry.MetaInfo.Substring(start, (ch[i-1] == '\r'? (i-1):(i+1)) - start);
274
275                                         if (value.Length == 0 && cacheEntry.EntryMetadata == null)
276                                         {
277                                             // done with headers, prepare for system metadata
278                                             cacheEntry.EntryMetadata = sc;
279                                             sc = new StringCollection();
280                                         }
281                                         else
282                                         {
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;
288                                             else
289                                                 sc.Add(value);
290                                         }
291                                         start = i+1;
292                                     }
293                                 }
294                             }
295                             if (cacheEntry.EntryMetadata == null )
296                                 {cacheEntry.EntryMetadata = sc;}
297                             else
298                                 {cacheEntry.SystemMetadata = sc;}
299                         }
300                     }
301
302                     result = new ReadStream(entry, handle, async);
303
304                 }
305                 else {
306                     if (handle != null) {
307                         handle.Close();
308                     }
309
310                     cacheEntry = new RequestCacheEntry();
311                     cacheEntry.IsPrivateEntry = IsPrivateCache;
312
313                     if (entry.Error != _WinInetCache.Status.FileNotFound)
314                     {
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()");
317                         if(isThrow)
318                         {
319                             Win32Exception win32Exception = new Win32Exception((int)entry.Error);
320                             throw new IOException(SR.GetString(SR.net_cache_retrieve_failure, win32Exception.Message), win32Exception);
321                         }
322                         return null;
323                     }
324                 }
325             }
326             catch (Exception exception) {
327                 if (exception is ThreadAbortException || exception is StackOverflowException || exception is OutOfMemoryException) {
328                     throw;
329                 }
330
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()");
333
334                 if (handle != null) {
335                     handle.Close();
336                 }
337                 result.Close();
338                 result = Stream.Null;
339                 cacheEntry = new RequestCacheEntry();
340                 cacheEntry.IsPrivateEntry = IsPrivateCache;
341                 if (isThrow)
342                 {
343                     throw;
344                 }
345                 return null;
346             }
347             if(Logging.On)Logging.Exit(Logging.RequestCache, "WinInetCache.Retrieve()", "Status = " + entry.Error.ToString());
348             return result;
349         }
350         //
351         //
352         //
353         private string CombineMetaInfo(StringCollection entryMetadata, StringCollection systemMetadata)
354         {
355             if ((entryMetadata == null || entryMetadata.Count == 0) && (systemMetadata == null || systemMetadata.Count == 0))
356                 return string.Empty;
357
358             StringBuilder sb = new StringBuilder(100);
359             int i;
360             if (entryMetadata != null && entryMetadata.Count != 0)
361                 for (i = 0; i < entryMetadata.Count; ++i)
362                 {
363                     if (entryMetadata[i] == null || entryMetadata[i].Length == 0)
364                         continue;
365                     sb.Append(entryMetadata[i]).Append("\r\n");
366                 }
367
368             if (systemMetadata != null && systemMetadata.Count != 0)
369             {
370                 // mark a start for system metadata
371                 sb.Append("\r\n");
372                 for (i = 0; i < systemMetadata.Count; ++i)
373                 {
374                     if (systemMetadata[i] == null || systemMetadata[i].Length == 0)
375                         continue;
376                     sb.Append(systemMetadata[i]).Append("\r\n");}
377             }
378
379             return sb.ToString();
380         }
381         //
382         //
383         private Stream GetWriteStream(string key, long contentLength, DateTime expiresUtc, DateTime lastModifiedUtc, TimeSpan maxStale, StringCollection entryMetadata, StringCollection systemMetadata, bool isThrow)
384         {
385             if(Logging.On) Logging.Enter(Logging.RequestCache, "WinInetCache.Store()", "Key = " + key);
386
387             if (key == null) {
388                 throw new ArgumentNullException("key");
389             }
390
391             if (!CanWrite)
392             {
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");
395                 if(isThrow)
396                 {
397                     throw new InvalidOperationException(SR.GetString(SR.net_cache_access_denied, "Write"));
398                 }
399                 return null;
400             }
401
402
403             _WinInetCache.Entry entry = new _WinInetCache.Entry(key, _MaximumResponseHeadersLength);
404
405             entry.Key = key;
406             entry.OptionalLength = (contentLength < 0L)? 0: contentLength > Int32.MaxValue? Int32.MaxValue: (int)(contentLength);
407
408             entry.Info.ExpireTime = _WinInetCache.FILETIME.Zero;
409             if (expiresUtc != DateTime.MinValue && expiresUtc > s_MinDateTimeUtcForFileTimeUtc) {
410                 entry.Info.ExpireTime  = new _WinInetCache.FILETIME(expiresUtc.ToFileTimeUtc());
411             }
412
413             entry.Info.LastModifiedTime = _WinInetCache.FILETIME.Zero;
414             if (lastModifiedUtc != DateTime.MinValue && lastModifiedUtc > s_MinDateTimeUtcForFileTimeUtc) {
415                 entry.Info.LastModifiedTime = new _WinInetCache.FILETIME(lastModifiedUtc.ToFileTimeUtc());
416             }
417
418             entry.Info.EntryType = _WinInetCache.EntryType.NormalEntry;
419             if (maxStale > TimeSpan.Zero) {
420                 if (maxStale >= s_MaxTimeSpanForInt32) {
421                     maxStale = s_MaxTimeSpanForInt32;
422                 }
423                 entry.Info.U.ExemptDelta = (int)maxStale.TotalSeconds;
424                 entry.Info.EntryType = _WinInetCache.EntryType.StickyEntry;
425             }
426
427
428             entry.MetaInfo = CombineMetaInfo(entryMetadata, systemMetadata);
429
430             entry.FileExt = "cache";
431             if(Logging.On) {
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>");
440                     }
441                     else {
442                         if (entryMetadata != null) {
443                             foreach (string s in entryMetadata)
444                             {
445                                 Logging.PrintInfo(Logging.RequestCache, s.TrimEnd(LineSplits));
446                             }
447                         }
448                         Logging.PrintInfo(Logging.RequestCache, "------");
449                         if (systemMetadata != null) {
450                             foreach (string s in systemMetadata)
451                             {
452                                 Logging.PrintInfo(Logging.RequestCache, s.TrimEnd(LineSplits));
453                             }
454                         }
455                     }
456                 }
457             }
458
459             _WinInetCache.CreateFileName(entry);
460
461             Stream result = Stream.Null;
462             if (entry.Error != _WinInetCache.Status.Success) {
463                 if(Logging.On)
464                 {
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");
467                 }
468                 if (isThrow)
469                 {
470                     Win32Exception win32Exception = new Win32Exception((int)entry.Error);
471                     throw new IOException(SR.GetString(SR.net_cache_retrieve_failure, win32Exception.Message), win32Exception);
472                 }
473                 return null;
474             }
475
476
477             try {
478                 result = new WriteStream(entry, isThrow, contentLength, async);
479             }
480             catch (Exception exception) {
481                 if (exception is ThreadAbortException || exception is StackOverflowException || exception is OutOfMemoryException) {
482                     throw;
483                 }
484
485                 if(Logging.On)
486                 {
487                     Logging.PrintError(Logging.RequestCache, SR.GetString(SR.net_log_cache_exception, "WinInetCache.Store()", exception));
488                     Logging.Exit(Logging.RequestCache, "WinInetCache.Store");
489                 }
490                 if (isThrow)
491                 {
492                     throw;
493                 }
494                 return null;
495             }
496
497             if(Logging.On) Logging.Exit(Logging.RequestCache, "WinInetCache.Store", "Filename = " + entry.Filename);
498             return result;
499         }
500         //
501         //
502         private bool UpdateInfo(string key, DateTime expiresUtc, DateTime lastModifiedUtc, DateTime lastSynchronizedUtc, TimeSpan maxStale, StringCollection entryMetadata, StringCollection systemMetadata, bool isThrow)
503         {
504             if (key == null) {
505                 throw new ArgumentNullException("key");
506             }
507
508             if(Logging.On) Logging.Enter(Logging.RequestCache, "WinInetCache.Update", "Key = "+ key);
509
510             if (!CanWrite)
511             {
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()");
514                 if(isThrow)
515                 {
516                     throw new InvalidOperationException(SR.GetString(SR.net_cache_access_denied, "Write"));
517                 }
518                 return false;
519             }
520
521             _WinInetCache.Entry entry = new _WinInetCache.Entry(key, _MaximumResponseHeadersLength);
522             _WinInetCache.Entry_FC attributes =  _WinInetCache.Entry_FC.None;
523
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")));
528             }
529
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")));
534             }
535
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")));
540             }
541
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;
548                     }
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()));
552                 }
553                 else {
554                     entry.Info.U.ExemptDelta = 0;
555                     if(Logging.On)Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_disable_max_stale));
556                 }
557             }
558
559             entry.MetaInfo = CombineMetaInfo(entryMetadata, systemMetadata);
560             if (entry.MetaInfo.Length != 0) {
561                 attributes |= _WinInetCache.Entry_FC.Headerinfo;
562
563                 if(Logging.On) {
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)
569                             {
570                                 Logging.PrintInfo(Logging.RequestCache, s.TrimEnd(LineSplits));
571                             }
572                         }
573                         Logging.PrintInfo(Logging.RequestCache, "------");
574                         if (systemMetadata != null) {
575                             foreach (string s in systemMetadata)
576                             {
577                                 Logging.PrintInfo(Logging.RequestCache, s.TrimEnd(LineSplits));
578                             }
579                         }
580                     }
581                 }
582             }
583
584             _WinInetCache.Update(entry, attributes) ;
585
586             if (entry.Error != _WinInetCache.Status.Success) {
587                 if(Logging.On)
588                 {
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()");
591                 }
592                 if (isThrow)
593                 {
594                     Win32Exception win32Exception = new Win32Exception((int)entry.Error);
595                     throw new IOException(SR.GetString(SR.net_cache_retrieve_failure, win32Exception.Message), win32Exception);
596                 }
597                 return false;
598             }
599
600             if(Logging.On) Logging.Exit(Logging.RequestCache, "WinInetCache.Update()", "Status = " + entry.Error.ToString());
601             return true;
602         }
603
604         /// <summary>
605         ///    <para>
606         ///         This is a FileStream wrapper on top of WinInet cache entry.
607         //          The Close method will unlock the cached entry.
608         ///    </para>
609         ///</summary>
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;
620
621             //
622             // Construct a read stream out of WinInet given handle
623             //
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)
629             {
630                 m_Key = entry.Key;
631                 m_Handle = handle;
632                 m_ReadTimeout = m_WriteTimeout = System.Threading.Timeout.Infinite;
633             }
634             //
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()
637             //
638             internal void UnlockEntry()
639             {
640                 m_Handle.Close();
641             }
642
643             public override int Read(byte[] buffer, int offset, int count)
644             {
645                 lock (m_Handle)
646                 {
647                     try {
648                         if (m_CallNesting != 0)
649                             throw new NotSupportedException(SR.GetString(SR.net_no_concurrent_io_allowed));
650                         if (m_Aborted)
651                             throw ExceptionHelper.RequestAbortedException;
652                         if (m_Event != null)
653                             throw new ObjectDisposedException(GetType().FullName);
654
655                         m_CallNesting = 1;
656                         return base.Read(buffer, offset, count);
657                     }
658                     finally {
659                         m_CallNesting = 0;
660                         if (m_Event != null)
661                             m_Event.Set();
662                     }
663                 }
664             }
665
666             public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, Object state)
667             {
668                 lock (m_Handle)
669                 {
670                     if (m_CallNesting != 0)
671                         throw new NotSupportedException(SR.GetString(SR.net_no_concurrent_io_allowed));
672                     if (m_Aborted)
673                         throw ExceptionHelper.RequestAbortedException;
674                     if (m_Event != null)
675                         throw new ObjectDisposedException(GetType().FullName);
676
677                     m_CallNesting = 1;
678                     try {
679                         return base.BeginRead(buffer, offset, count, callback, state);
680                     }
681                     catch {
682                         m_CallNesting = 0;
683                         throw;
684                     }
685                 }
686             }
687
688             public override int EndRead(IAsyncResult asyncResult)
689             {
690                 lock (m_Handle)
691                 {
692                     try {
693                         return base.EndRead(asyncResult);
694                     }
695                     finally {
696                         m_CallNesting = 0;
697                         if (m_Event != null)
698                             try {m_Event.Set();} catch {}   // the problem is he WaitHandle cannot tell if it is disposed or not
699                     }
700                 }
701             }
702
703             public void CloseEx(CloseExState closeState)
704             {
705                 if ((closeState & CloseExState.Abort) != 0)
706                     m_Aborted = true;
707
708                 try {
709                     Close();
710                 }
711                 catch {
712                     if ((closeState & CloseExState.Silent) == 0)
713                         throw;
714                 }
715             }
716
717             protected override void Dispose(bool disposing) 
718             {                
719                 if (Interlocked.Exchange(ref m_Disposed, 1) == 0)
720                 {
721                     if (!disposing)
722                     {
723                         base.Dispose(false);
724                     }
725                     else
726                     {
727                         // if m_key is null, it means that the base constructor failed
728                         if (m_Key != null)
729                         {
730                             try
731                             {
732                                 lock (m_Handle)
733                                 {
734                                     if (m_CallNesting == 0)
735                                         base.Dispose(true);
736                                     else
737                                         m_Event = new ManualResetEvent(false);
738                                 }
739
740                                 RequestLifetimeSetter.Report(m_RequestLifetimeSetter);
741
742                                 if (m_Event != null)
743                                 {
744                                     using (m_Event)
745                                     {
746                                         // This assumes that FileStream will never hang on read
747                                         m_Event.WaitOne();
748                                         lock (m_Handle)
749                                         {
750                                             Debug.Assert(m_CallNesting == 0);
751                                         }
752                                     }
753                                     base.Dispose(true);
754                                 }
755                             }
756                             finally
757                             {
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.
760                                 m_Handle.Close();
761                             }
762                         }
763                     }
764                 }
765             }
766
767             public override bool CanTimeout {
768                 get {
769                     return true;
770                 }
771             }
772             public override int ReadTimeout {
773                 get {
774                     return m_ReadTimeout;
775                 }
776                 set {
777                     m_ReadTimeout = value;
778                 }
779             }
780             public override int WriteTimeout {
781                 get {
782                     return m_WriteTimeout;
783                 }
784                 set {
785                     m_WriteTimeout = value;
786                 }
787             }
788
789             void IRequestLifetimeTracker.TrackRequestLifetime(long requestStartTimestamp)
790             {
791                 Debug.Assert(m_RequestLifetimeSetter == null, "TrackRequestLifetime called more than once.");
792                 m_RequestLifetimeSetter = new RequestLifetimeSetter(requestStartTimestamp);
793             }
794         }
795         //
796         //
797         //
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;
809
810
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) {
816
817                 m_Entry = entry;
818                 m_IsThrow = isThrow;
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;
822             }
823             //
824             public override bool CanTimeout {
825                 get {
826                     return true;
827                 }
828             }
829             public override int ReadTimeout {
830                 get {
831                     return m_ReadTimeout;
832                 }
833                 set {
834                     m_ReadTimeout = value;
835                 }
836             }
837             public override int WriteTimeout {
838                 get {
839                     return m_WriteTimeout;
840                 }
841                 set {
842                     m_WriteTimeout = value;
843                 }
844             }
845             //
846             public override void Write(byte[] buffer, int offset, int count)
847             {
848                 lock (m_Entry)
849                 {
850                     if (m_Aborted)
851                         throw ExceptionHelper.RequestAbortedException;
852                     if (m_Event != null)
853                         throw new ObjectDisposedException(GetType().FullName);
854
855                     m_CallNesting = 1;
856                     try {
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;
862                     }
863                     catch {
864                         m_Aborted = true;
865                         throw;
866                     }
867                     finally {
868                         m_CallNesting = 0;
869                         if (m_Event != null)
870                             m_Event.Set();
871                     }
872                 }
873             }
874             //
875             public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, Object state)
876             {
877                 lock (m_Entry)
878                 {
879                     if (m_CallNesting != 0)
880                         throw new NotSupportedException(SR.GetString(SR.net_no_concurrent_io_allowed));
881                     if (m_Aborted)
882                         throw ExceptionHelper.RequestAbortedException;
883                     if (m_Event != null)
884                         throw new ObjectDisposedException(GetType().FullName);
885
886                     m_CallNesting = 1;
887
888                     try {
889                         if (m_StreamSize > 0)
890                             m_StreamSize -= count;
891                         return base.BeginWrite(buffer, offset, count, callback, state);
892                     }
893                     catch {
894                         m_Aborted = true;
895                         m_CallNesting = 0;
896                         throw;
897                     }
898                 }
899
900             }
901             //
902             public override void EndWrite(IAsyncResult asyncResult)
903             {
904                 lock (m_Entry)
905                 {
906                     try {
907                         base.EndWrite(asyncResult);
908                         if (!m_OneWriteSucceeded)
909                             m_OneWriteSucceeded = true;
910                     }
911                     catch {
912                         m_Aborted = true;
913                         throw;
914                     }
915                     finally {
916                         m_CallNesting = 0;
917                         if (m_Event != null)
918                             try {m_Event.Set();} catch {}   // the problem is he WaitHandle cannot tell if it is disposed or not
919                     }
920                 }
921             }
922             //
923             public void CloseEx(CloseExState closeState)
924             {
925                 // For abnormal stream termination we will commit a partial cache entry
926                 if ((closeState & CloseExState.Abort) != 0)
927                     m_Aborted = true;
928
929                 try {
930                     Close();
931                 }
932                 catch {
933                     if ((closeState & CloseExState.Silent) == 0)
934                         throw;
935                 }
936             }
937             //
938             [ResourceExposure(ResourceScope.None)]
939             [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
940             protected override void Dispose(bool disposing)
941             {
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) {
944
945                     lock (m_Entry)
946                     {
947                         if (m_CallNesting == 0)
948                             base.Dispose(disposing);
949                         else
950                             m_Event = new ManualResetEvent(false);
951                     }
952
953                     //
954                     // This assumes the FileStream will never hang on write
955                     //
956                     if (disposing && m_Event != null)
957                     {
958                         using (m_Event)
959                         {
960                             m_Event.WaitOne();
961                             lock (m_Entry) 
962                             {
963                                 Debug.Assert(m_CallNesting == 0);
964                             }
965                         }
966                         base.Dispose(disposing);
967                     }
968
969                     // We use TriState to indicate:
970                     //     False:   Delete
971                     //     Unknown: Partial
972                     //     True:    Full
973                     TriState cacheCommitAction;
974                     if (m_StreamSize < 0)
975                     {
976                         if (m_Aborted)
977                         {
978                             if (m_OneWriteSucceeded)
979                                 cacheCommitAction = TriState.Unspecified; // Partial
980                             else
981                                 cacheCommitAction = TriState.False; // Delete
982                         }
983                         else
984                         {
985                             cacheCommitAction = TriState.True; // Full
986                         }
987                     }
988                     else
989                     {
990                         if (!m_OneWriteSucceeded)
991                         {
992                             cacheCommitAction = TriState.False; // Delete
993                         }
994                         else
995                         {
996                             if (m_StreamSize > 0)
997                                 cacheCommitAction = TriState.Unspecified; // Partial
998                             else
999                                 cacheCommitAction = TriState.True; // Full
1000                         }
1001                     }
1002
1003                     if (cacheCommitAction == TriState.False)
1004                     {
1005                         try
1006                         {
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);
1010                         }
1011                         catch (Exception exception)
1012                         {
1013                             if (exception is ThreadAbortException || exception is StackOverflowException || exception is OutOfMemoryException) {
1014                                 throw;
1015                             }
1016                             if(Logging.On)Logging.PrintError(Logging.RequestCache, SR.GetString(SR.net_log_cache_error_deleting_filename, "WinInetWriteStream.Close()", m_Entry.Filename));
1017                         }
1018                         finally {
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)
1022                             {
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));
1024                             }
1025
1026                             m_Entry = null;
1027                         }
1028                         return;
1029                     }
1030
1031                     m_Entry.OriginalUrl = null;
1032
1033                     //
1034                     // ATTN: WinIent currently does NOT support _WinInetCache.EntryType.Sparse
1035                     // USING a workaround
1036                     //
1037                     if (cacheCommitAction == TriState.Unspecified)
1038                     {
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
1041
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))
1046                         {
1047                             m_Entry.MetaInfo = "\r\n"+ WinInetCache.c_SPARSE_ENTRY_HACK+"\r\n";
1048                         }
1049                         else
1050                         {
1051                             m_Entry.MetaInfo += WinInetCache.c_SPARSE_ENTRY_HACK+"\r\n";
1052                         }
1053                     }
1054
1055                     if (_WinInetCache.Commit(m_Entry) != _WinInetCache.Status.Success)
1056                     {
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));
1058                         try
1059                         {
1060                             // Delete temp cache file
1061                             File.Delete(m_Entry.Filename);
1062                         }
1063                         catch (Exception exception)
1064                         {
1065                             if (exception is ThreadAbortException || exception is StackOverflowException || exception is OutOfMemoryException) {
1066                                 throw;
1067                             }
1068                             if(Logging.On)Logging.PrintError(Logging.RequestCache, SR.GetString(SR.net_log_cache_error_deleting_filename, "WinInetWriteStream.Close()", m_Entry.Filename));
1069                         }
1070
1071                         if (m_IsThrow)
1072                         {
1073                             Win32Exception win32Exception = new Win32Exception((int)m_Entry.Error);
1074                             throw new IOException(SR.GetString(SR.net_cache_retrieve_failure, win32Exception.Message), win32Exception);
1075                         }
1076                         return;
1077                     }
1078
1079                     if(Logging.On)
1080                     {
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());
1084                     }
1085
1086
1087                     if ((m_Entry.Info.EntryType & _WinInetCache.EntryType.StickyEntry) == _WinInetCache.EntryType.StickyEntry)
1088                     {
1089                         if (_WinInetCache.Update(m_Entry, _WinInetCache.Entry_FC.ExemptDelta) != _WinInetCache.Status.Success)
1090                         {
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));
1092                             if (m_IsThrow)
1093                             {
1094                                 Win32Exception win32Exception = new Win32Exception((int)m_Entry.Error);
1095                                 throw new IOException(SR.GetString(SR.net_cache_retrieve_failure, win32Exception.Message), win32Exception);
1096                             }
1097                             return;
1098                         }
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()));
1100                     }
1101
1102                     base.Dispose(disposing);
1103                 }
1104             }
1105         }
1106     }
1107 }
1108
1109
1110