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