2009-07-11 Michael Barker <mike@middlesoft.co.uk>
[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.Collections.Specialized;
32 using System.ComponentModel;
33 using System.Globalization;
34 using System.IO;
35 using System.Security.Permissions;
36 using System.Text;
37 using System.Web.UI;
38 using System.Web.Util;
39
40 namespace System.Web
41 {
42         // CAS - no InheritanceDemand here as the class is sealed
43         [AspNetHostingPermission (SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
44         public sealed class HttpCachePolicy
45         {
46                 internal HttpCachePolicy ()
47                 {
48                 }
49
50 #if NET_2_0
51                 HttpCacheVaryByContentEncodings vary_by_content_encodings = new HttpCacheVaryByContentEncodings ();
52 #endif
53                 HttpCacheVaryByHeaders vary_by_headers = new HttpCacheVaryByHeaders ();
54                 HttpCacheVaryByParams vary_by_params = new HttpCacheVaryByParams ();
55                 ArrayList validation_callbacks;
56                 StringBuilder cache_extension;
57                 internal HttpCacheability Cacheability;
58                 string etag;
59                 bool etag_from_file_dependencies;
60                 bool last_modified_from_file_dependencies;
61
62                 //
63                 // Used externally
64                 //
65                 internal bool have_expire_date;
66                 internal DateTime expire_date;
67                 internal bool have_last_modified;
68                 internal DateTime last_modified;
69
70                 //bool LastModifiedFromFileDependencies;
71                 HttpCacheRevalidation revalidation;
72                 string vary_by_custom;
73                 bool HaveMaxAge;
74                 TimeSpan MaxAge;
75                 bool HaveProxyMaxAge;
76                 TimeSpan ProxyMaxAge;
77                 ArrayList fields;
78                 bool sliding_expiration;
79                 int duration;
80                 bool allow_response_in_browser_history;
81                 bool allow_server_caching = true;
82                 bool set_no_store;
83                 bool set_no_transform;
84                 bool valid_until_expires;
85 #if NET_2_0
86                 bool omit_vary_star;
87                 
88                 public HttpCacheVaryByContentEncodings VaryByContentEncodings {
89                         get { return vary_by_content_encodings; }
90                 }
91 #endif
92
93                 public HttpCacheVaryByHeaders VaryByHeaders {
94                         get { return vary_by_headers; }
95                 }
96
97                 public HttpCacheVaryByParams VaryByParams {
98                         get { return vary_by_params; }
99                 }
100
101                 internal bool AllowServerCaching {
102                         get { return allow_server_caching; }
103                 }
104
105                 internal int Duration {
106                         get { return duration; }
107                         set { duration = value; }
108                 }
109
110                 internal bool Sliding {
111                         get { return sliding_expiration; }
112                 }
113
114                 internal DateTime Expires {
115                         get { return expire_date; }
116                 }
117
118                 internal ArrayList ValidationCallbacks {
119                         get { return validation_callbacks; }
120                 }
121
122                 // always false in 1.x
123                 internal bool OmitVaryStar {
124                         get {
125 #if NET_2_0
126                                 return omit_vary_star;
127 #else
128                                 return false;
129 #endif
130                         }
131                 }
132
133                 internal bool ValidUntilExpires {
134                         get { return valid_until_expires; }
135                 }
136
137                 internal int ExpireMinutes ()
138                 {
139                         if (!have_expire_date)
140                                 return 0;
141
142                         return (expire_date - DateTime.Now).Minutes;
143                 }
144
145                 public void AddValidationCallback (HttpCacheValidateHandler handler, object data)
146                 {
147                         if (handler == null)
148                                 throw new ArgumentNullException ("handler");
149
150                         if (validation_callbacks == null)
151                                 validation_callbacks = new ArrayList ();
152
153                         validation_callbacks.Add (new Pair (handler, data));
154                 }
155
156                 public void AppendCacheExtension (string extension)
157                 {
158                         if (extension == null)
159                                 throw new ArgumentNullException ("extension");
160
161                         if (cache_extension == null)
162                                 cache_extension = new StringBuilder (extension);
163                         else
164                                 cache_extension.Append (", " + extension);
165                 }
166
167                 //
168                 // This one now allows the full range of Cacheabilities.
169                 //
170                 public void SetCacheability (HttpCacheability cacheability)
171                 {
172                         if (cacheability < HttpCacheability.NoCache || cacheability > HttpCacheability.ServerAndPrivate)
173                                 throw new ArgumentOutOfRangeException ("cacheability");
174
175                         if (Cacheability > 0 && cacheability > Cacheability)
176                                 return;
177
178                         Cacheability = cacheability;
179                 }
180
181                 public void SetCacheability (HttpCacheability cacheability, string field)
182                 {
183                         if (field == null)
184                                 throw new ArgumentNullException ("field");
185
186                         if (cacheability != HttpCacheability.NoCache && cacheability != HttpCacheability.Private)
187                                 throw new ArgumentException ("Must be NoCache or Private", "cacheability");
188
189                         if (fields == null)
190                                 fields = new ArrayList ();
191
192                         fields.Add (new Pair (cacheability, field));
193                 }
194
195                 public void SetETag (string etag)
196                 {
197                         if (etag == null)
198                                 throw new ArgumentNullException ("etag");
199
200                         if (this.etag != null)
201                                 throw new InvalidOperationException ("The ETag header has already been set");
202
203                         if (etag_from_file_dependencies)
204                                 throw new InvalidOperationException ("SetEtagFromFileDependencies has already been called");
205
206                         this.etag = etag;
207                 }
208
209                 public void SetETagFromFileDependencies ()
210                 {
211                         if (this.etag != null)
212                                 throw new InvalidOperationException ("The ETag header has already been set");
213
214                         etag_from_file_dependencies = true;
215                 }
216
217                 public void SetExpires (DateTime date)
218                 {
219                         if (have_expire_date && date > expire_date)
220                                 return;
221
222                         have_expire_date = true;
223                         expire_date = date;
224                 }
225
226                 public void SetLastModified (DateTime date)
227                 {
228                         if (date > DateTime.Now)
229                                 throw new ArgumentOutOfRangeException ("date");
230
231                         if (have_last_modified && date < last_modified)
232                                 return;
233
234                         have_last_modified = true;
235                         last_modified = date;
236                 }
237
238                 public void SetLastModifiedFromFileDependencies ()
239                 {
240                         last_modified_from_file_dependencies = true;
241                 }
242
243                 public void SetMaxAge (TimeSpan date)
244                 {
245                         if (date < TimeSpan.Zero)
246                                 throw new ArgumentOutOfRangeException ("date");
247
248                         if (HaveMaxAge && MaxAge < date)
249                                 return;
250
251                         MaxAge = date;
252                         HaveMaxAge = true;
253                 }
254
255                 public void SetNoServerCaching ()
256                 {
257                         allow_server_caching = false;
258                 }
259
260                 public void SetNoStore ()
261                 {
262                         set_no_store = true;
263                 }
264
265                 public void SetNoTransforms ()
266                 {
267                         set_no_transform = true;
268                 }
269
270                 public void SetProxyMaxAge (TimeSpan delta)
271                 {
272                         if (delta < TimeSpan.Zero)
273                                 throw new ArgumentOutOfRangeException ("delta");
274
275                         if (HaveProxyMaxAge && ProxyMaxAge < delta)
276                                 return;
277
278                         ProxyMaxAge = delta;
279                         HaveProxyMaxAge = true;
280                 }
281
282                 public void SetRevalidation (HttpCacheRevalidation revalidation)
283                 {
284                         if (revalidation < HttpCacheRevalidation.AllCaches ||
285                             revalidation > HttpCacheRevalidation.None)
286                                 throw new ArgumentOutOfRangeException ("revalidation");
287
288                         if (this.revalidation > revalidation)
289                                 this.revalidation = revalidation;
290                 }
291
292                 public void SetSlidingExpiration (bool slide)
293                 {
294                         sliding_expiration = slide;
295                 }
296
297                 public void SetValidUntilExpires (bool validUntilExpires)
298                 {
299                         valid_until_expires = validUntilExpires;
300                 }
301
302                 public void SetVaryByCustom (string custom)
303                 {
304                         if (custom == null)
305                                 throw new ArgumentNullException ("custom");
306
307                         if (vary_by_custom != null)
308                                 throw new InvalidOperationException ("VaryByCustom has already been set.");
309
310                         vary_by_custom = custom;
311                 }
312
313                 internal string GetVaryByCustom ()
314                 {
315                         return vary_by_custom;
316                 }
317
318                 public void SetAllowResponseInBrowserHistory (bool allow)
319                 {
320                         if (Cacheability == HttpCacheability.NoCache || Cacheability == HttpCacheability.ServerAndNoCache) 
321                                 allow_response_in_browser_history = allow;
322                 }
323
324                 internal void SetHeaders (HttpResponse response, NameValueCollection headers)
325                 {
326                         bool noCache = false;
327                         string cc = null;
328
329                         switch (Cacheability) {
330                         case HttpCacheability.Public:
331                                 cc = "public";
332                                 break;
333
334                         case HttpCacheability.NoCache:
335                         case HttpCacheability.ServerAndNoCache:
336                                 noCache = true;
337                                 cc = "no-cache";
338                                 break;
339
340                         case HttpCacheability.Private:
341                         case HttpCacheability.ServerAndPrivate:
342                         default:
343                                 cc = "private";
344                                 break;
345                         }
346
347                         if (noCache) {
348                                 response.CacheControl = cc;
349                                 if (!allow_response_in_browser_history) {
350                                         headers.Add ("Expires", "-1");
351                                         headers.Add ("Pragma", "no-cache");
352                                 }
353                         } else {
354                                 if (HaveMaxAge)
355                                         cc = String.Concat (cc, ", max-age=", ((long) MaxAge.TotalSeconds).ToString ());
356
357                                 if (have_expire_date) {
358                                         string expires = TimeUtil.ToUtcTimeString (expire_date);
359                                         headers.Add ("Expires", expires);
360                                 }
361                         }
362
363                         if (set_no_store)
364                                 cc = String.Concat (cc, ", no-store");
365                         if (set_no_transform)
366                                 cc = String.Concat (cc, ", no-transform");
367
368                         headers.Add ("Cache-Control", cc);
369
370                         if (last_modified_from_file_dependencies || etag_from_file_dependencies)
371                                 HeadersFromFileDependencies (response);
372
373                         if (etag != null)
374                                 headers.Add ("ETag", etag);
375
376                         if (have_last_modified)
377                                 headers.Add ("Last-Modified", TimeUtil.ToUtcTimeString (last_modified));
378
379                         if (!vary_by_params.IgnoreParams) {
380                                 string vb = vary_by_params.GetResponseHeaderValue ();
381                                 if (vb != null)
382                                         headers.Add ("Vary", vb);
383                         }
384                 }
385
386                 void HeadersFromFileDependencies (HttpResponse response)
387                 {
388                         string [] fileDeps = response.FileDependencies;
389
390                         if (fileDeps == null || fileDeps.Length == 0)
391                                 return;
392
393                         bool doEtag = etag != null && etag_from_file_dependencies;
394                         if (!doEtag && !last_modified_from_file_dependencies)
395                                 return;
396
397                         DateTime latest_mod = DateTime.MinValue, mod;
398                         StringBuilder etagsb = new StringBuilder ();
399                         
400                         foreach (string f in fileDeps) {
401                                 if (!File.Exists (f))
402                                         continue;
403                                 try {
404                                         mod = File.GetLastWriteTime (f);
405                                 } catch {
406                                         // ignore
407                                         continue;
408                                 }
409
410                                 if (last_modified_from_file_dependencies && mod > latest_mod)
411                                         latest_mod = mod;
412                                 if (doEtag)
413                                         etagsb.AppendFormat ("{0}", mod.Ticks.ToString ("x"));
414                         }
415
416                         if (last_modified_from_file_dependencies && latest_mod > DateTime.MinValue) {
417                                 last_modified = latest_mod;
418                                 have_last_modified = true;
419                         }
420
421                         if (doEtag && etagsb.Length > 0)
422                                 etag = etagsb.ToString ();
423                 }
424
425 #if NET_2_0
426                 public void SetOmitVaryStar (bool omit)
427                 {
428                         omit_vary_star = omit;
429                 }
430 #endif
431         }
432 }