2008-04-23 Marek Habersack <mhabersack@novell.com>
[mono.git] / mcs / class / System.Web / System.Web / HttpCachePolicy.cs
1 // 
2 // System.Web.HttpCachePolicy
3 //
4 // Authors:
5 //      Tim Coleman (tim@timcoleman.com)
6 //      Gonzalo Paniagua Javier (gonzalo@ximian.com)
7 //
8 //
9 //
10 // Permission is hereby granted, free of charge, to any person obtaining
11 // a copy of this software and associated documentation files (the
12 // "Software"), to deal in the Software without restriction, including
13 // without limitation the rights to use, copy, modify, merge, publish,
14 // distribute, sublicense, and/or sell copies of the Software, and to
15 // permit persons to whom the Software is furnished to do so, subject to
16 // the following conditions:
17 // 
18 // The above copyright notice and this permission notice shall be
19 // included in all copies or substantial portions of the Software.
20 // 
21 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28 //
29
30 using System.Collections;
31 using System.Globalization;
32 using System.IO;
33 using System.Security.Permissions;
34 using System.Text;
35 using System.Web.UI;
36 using System.Web.Util;
37
38 namespace System.Web {
39
40         class CacheabilityUpdatedEventArgs : EventArgs {
41
42                 public readonly HttpCacheability Cacheability;
43
44                 public CacheabilityUpdatedEventArgs (HttpCacheability cacheability)
45                 {
46                         Cacheability = cacheability;
47                 }
48         }
49         
50         internal delegate void CacheabilityUpdatedCallback (object sender, CacheabilityUpdatedEventArgs args);
51         
52         // CAS - no InheritanceDemand here as the class is sealed
53         [AspNetHostingPermission (SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
54         public sealed class HttpCachePolicy {
55
56                 internal HttpCachePolicy ()
57                 {
58                 }
59
60 #region Fields
61                 
62 #if NET_2_0
63                 HttpCacheVaryByContentEncodings vary_by_content_encodings = new HttpCacheVaryByContentEncodings ();
64 #endif
65                 HttpCacheVaryByHeaders vary_by_headers = new HttpCacheVaryByHeaders ();
66                 HttpCacheVaryByParams vary_by_params = new HttpCacheVaryByParams ();
67                 ArrayList validation_callbacks;
68                 StringBuilder cache_extension;
69                 internal HttpCacheability Cacheability;
70                 string etag;
71                 bool etag_from_file_dependencies;
72                 bool last_modified_from_file_dependencies;
73                 
74                 //
75                 // Used externally
76                 //
77                 internal bool have_expire_date;
78                 internal DateTime expire_date;
79                 internal bool have_last_modified;
80                 internal DateTime last_modified;
81                 
82                 //bool LastModifiedFromFileDependencies;
83                 HttpCacheRevalidation revalidation;
84                 string vary_by_custom;
85                 bool HaveMaxAge;
86                 TimeSpan MaxAge;
87                 bool HaveProxyMaxAge;
88                 TimeSpan ProxyMaxAge;
89                 ArrayList fields;
90                 bool sliding_expiration;
91                 int duration;
92                 bool allow_response_in_browser_history;
93                 bool allow_server_caching = true;
94                 bool set_no_store = false;
95                 bool set_no_transform = false;
96                 bool valid_until_expires = false;
97                 
98                 // always false in 1.x
99                 bool omit_vary_star = false;
100                 
101 #endregion
102
103                 internal event CacheabilityUpdatedCallback CacheabilityUpdated;
104                 
105 #region Properties
106
107 #if NET_2_0
108                 public HttpCacheVaryByContentEncodings VaryByContentEncodings {
109                         get { return vary_by_content_encodings; }
110                 }
111 #endif
112                 
113                 public HttpCacheVaryByHeaders VaryByHeaders {
114                         get { return vary_by_headers; }
115                 }
116
117                 public HttpCacheVaryByParams VaryByParams {
118                         get { return vary_by_params; }
119                 }
120
121                 internal bool AllowServerCaching {
122                         get { return allow_server_caching; }
123                 }
124                 
125                 internal int Duration {
126                         get { return duration; }
127                         set { duration = value; }
128                 }
129
130                 internal bool Sliding {
131                         get { return sliding_expiration; }
132                 }
133                 
134                 internal DateTime Expires {
135                         get { return expire_date; }
136                 }
137
138                 internal ArrayList ValidationCallbacks {
139                         get { return validation_callbacks; }
140                 }
141
142                 // always false in 1.x
143                 internal bool OmitVaryStar {
144                         get { return omit_vary_star; }
145                 }
146
147                 internal bool ValidUntilExpires {
148                         get { return valid_until_expires; }
149                 }
150                 
151 #endregion // Properties
152
153 #region Methods
154
155                 internal int ExpireMinutes ()
156                 {
157                         if (!have_expire_date)
158                                 return 0;
159                         
160                         return (expire_date - DateTime.Now).Minutes;
161                 }
162                 
163                 public void AddValidationCallback (HttpCacheValidateHandler handler, object data)
164                 {
165                         if (handler == null)
166                                 throw new ArgumentNullException ("handler");
167
168                         if (validation_callbacks == null)
169                                 validation_callbacks = new ArrayList ();
170
171                         validation_callbacks.Add (new Pair (handler, data));
172                 }
173
174                 public void AppendCacheExtension (string extension)
175                 {
176                         if (extension == null)
177                                 throw new ArgumentNullException ("extension");
178
179                         if (cache_extension == null)
180                                 cache_extension = new StringBuilder (extension);
181                         else
182                                 cache_extension.Append (", " + extension);
183                 }
184
185                 //
186                 // This one now allows the full range of Cacheabilities.
187                 //
188                 public void SetCacheability (HttpCacheability cacheability)
189                 {
190                         if (cacheability < HttpCacheability.NoCache || cacheability > HttpCacheability.ServerAndPrivate)
191                                 throw new ArgumentOutOfRangeException ("cacheability");
192
193                         if (Cacheability > 0 && cacheability > Cacheability)
194                                 return;
195                         
196                         Cacheability = cacheability;
197
198                         if (CacheabilityUpdated != null)
199                                 CacheabilityUpdated (this, new CacheabilityUpdatedEventArgs (cacheability));
200                 }
201
202                 public void SetCacheability (HttpCacheability cacheability, string field)
203                 {
204                         if (field == null)
205                                 throw new ArgumentNullException ("field");
206
207                         if (cacheability != HttpCacheability.NoCache && cacheability != HttpCacheability.Private)
208                                 throw new ArgumentException ("Must be NoCache or Private", "cacheability");
209
210                         if (fields == null)
211                                 fields = new ArrayList ();
212
213                         fields.Add (new Pair (cacheability, field));
214                 }
215
216                 public void SetETag (string etag)
217                 {
218                         if (etag == null)
219                                 throw new ArgumentNullException ("etag");
220
221                         if (this.etag != null)
222                                 throw new InvalidOperationException ("The ETag header has already been set");
223
224                         if (etag_from_file_dependencies)
225                                 throw new InvalidOperationException ("SetEtagFromFileDependencies has already been called");
226
227                         this.etag = etag;
228                 }
229
230                 public void SetETagFromFileDependencies ()
231                 {
232                         if (this.etag != null)
233                                 throw new InvalidOperationException ("The ETag header has already been set");
234
235                         etag_from_file_dependencies = true;
236                 }
237
238                 public void SetExpires (DateTime date)
239                 {
240                         if (have_expire_date && date > expire_date)
241                                 return;
242
243                         have_expire_date = true;
244                         expire_date = date;
245                 }
246
247                 public void SetLastModified (DateTime date)
248                 {
249                         if (date > DateTime.Now)
250                                 throw new ArgumentOutOfRangeException ("date");
251
252                         if (have_last_modified && date < last_modified)
253                                 return;
254
255                         have_last_modified = true;
256                         last_modified = date;
257                 }
258
259                 public void SetLastModifiedFromFileDependencies ()
260                 {
261                         last_modified_from_file_dependencies = true;
262                 }
263
264                 public void SetMaxAge (TimeSpan date)
265                 {
266                         if (date < TimeSpan.Zero)
267                                 throw new ArgumentOutOfRangeException ("date");
268                         
269                         if (HaveMaxAge && MaxAge < date)
270                                 return;
271
272                         MaxAge = date;
273                         HaveMaxAge = true;
274                 }
275
276                 public void SetNoServerCaching ()
277                 {
278                         allow_server_caching = false;
279                 }
280
281                 public void SetNoStore ()
282                 {
283                         set_no_store = true;
284                 }
285
286                 public void SetNoTransforms ()
287                 {
288                         set_no_transform = true;
289                 }
290
291                 public void SetProxyMaxAge (TimeSpan delta)
292                 {
293                         if (delta < TimeSpan.Zero)
294                                 throw new ArgumentOutOfRangeException ("delta");
295
296                         if (HaveProxyMaxAge && ProxyMaxAge < delta)
297                                 return;
298
299                         ProxyMaxAge = delta;
300                 }
301
302                 public void SetRevalidation (HttpCacheRevalidation revalidation)
303                 {
304                         if (revalidation < HttpCacheRevalidation.AllCaches ||
305                             revalidation > HttpCacheRevalidation.None)
306                                 throw new ArgumentOutOfRangeException ("revalidation");
307
308                         if (this.revalidation > revalidation)
309                                 this.revalidation = revalidation;
310                 }
311
312                 public void SetSlidingExpiration (bool slide)
313                 {
314                         sliding_expiration = slide;
315                 }
316
317                 public void SetValidUntilExpires (bool validUntilExpires)
318                 {
319                         valid_until_expires = validUntilExpires;
320                 }
321
322                 public void SetVaryByCustom (string custom)
323                 {
324                         if (custom == null)
325                                 throw new ArgumentNullException ("custom");
326
327                         if (vary_by_custom != null)
328                                 throw new InvalidOperationException ("VaryByCustom has already been set.");
329
330                         vary_by_custom = custom;
331                 }
332
333                 internal string GetVaryByCustom ()
334                 {
335                         return vary_by_custom;
336                 }
337
338                 public void SetAllowResponseInBrowserHistory (bool allow)
339                 {
340                         if (Cacheability == HttpCacheability.NoCache || Cacheability == HttpCacheability.ServerAndNoCache) 
341                                 allow_response_in_browser_history = allow;
342                 }
343
344                 internal void SetHeaders (HttpResponse response, ArrayList headers)
345                 {
346                         bool noCache = false;
347                         string cc = null;
348
349                         switch (Cacheability) {
350                         case HttpCacheability.Public:
351                                 cc = "public";
352                                 break;
353
354                         case HttpCacheability.Private:
355                         case HttpCacheability.ServerAndPrivate:
356                                 cc = "private";
357                                 break;
358
359                         case HttpCacheability.NoCache:
360                         case HttpCacheability.ServerAndNoCache:
361                         default:
362                                 noCache = true;
363                                 cc = "no-cache";
364                                 break;
365                         }
366
367                         if (noCache) {
368                                 response.CacheControl = cc;
369                                 if (!allow_response_in_browser_history) {
370                                         headers.Add (new UnknownResponseHeader ("Expires", "-1"));
371                                         headers.Add (new UnknownResponseHeader ("Pragma", "no-cache"));
372                                 }
373                         } else {
374                                 if (MaxAge.TotalSeconds != 0)
375                                         cc = String.Concat (cc, ", max-age=", ((long) MaxAge.TotalSeconds).ToString ());
376
377                                 string expires = TimeUtil.ToUtcTimeString (expire_date);
378                                 headers.Add (new UnknownResponseHeader ("Expires", expires));
379                         }
380
381                         if (set_no_store)
382                                 cc = String.Concat (cc, ", no-store");
383                         if (set_no_transform)
384                                 cc = String.Concat (cc, ", no-transform");
385                         
386                         headers.Add (new UnknownResponseHeader ("Cache-Control", cc));
387
388                         if (last_modified_from_file_dependencies || etag_from_file_dependencies)
389                                 HeadersFromFileDependencies (response);
390                         
391                         if (etag != null)
392                                 headers.Add (new UnknownResponseHeader ("ETag", etag));
393
394                         if (have_last_modified)
395                                 headers.Add (new UnknownResponseHeader ("Last-Modified",
396                                                              TimeUtil.ToUtcTimeString (last_modified)));
397
398                         if (!vary_by_params.IgnoreParams) {
399                                 BaseResponseHeader vb = vary_by_params.GetResponseHeader ();
400                                 if (vb != null)
401                                         headers.Add (vb);
402                         }
403
404                 }
405
406                 void HeadersFromFileDependencies (HttpResponse response)
407                 {
408                         string [] fileDeps = response.FileDependencies;
409                         
410                         if (fileDeps == null || fileDeps.Length == 0)
411                                 return;
412
413                         bool doEtag = etag != null && etag_from_file_dependencies;
414                         if (!doEtag && !last_modified_from_file_dependencies)
415                                 return;
416
417                         DateTime latest_mod = DateTime.MinValue, mod;
418                         StringBuilder etagsb = new StringBuilder ();
419                         
420                         foreach (string f in fileDeps) {
421                                 if (!File.Exists (f))
422                                         continue;
423                                 try {
424                                         mod = File.GetLastWriteTime (f);
425                                 } catch {
426                                         // ignore
427                                         continue;
428                                 }
429
430                                 if (last_modified_from_file_dependencies && mod > latest_mod)
431                                         latest_mod = mod;
432                                 if (doEtag)
433                                         etagsb.AppendFormat ("{0}", mod.Ticks.ToString ("x"));
434                         }
435
436                         if (last_modified_from_file_dependencies && latest_mod > DateTime.MinValue) {
437                                 last_modified = latest_mod;
438                                 have_last_modified = true;
439                         }
440
441                         if (doEtag && etagsb.Length > 0)
442                                 etag = etagsb.ToString ();
443                 }
444                 
445 #if NET_2_0
446                 public void SetOmitVaryStar (bool omit)
447                 {
448                         omit_vary_star = omit;
449                 }
450 #endif
451                 
452 #endregion // Methods
453         }
454 }
455