2 // System.Web.HttpCachePolicy
5 // Tim Coleman (tim@timcoleman.com)
6 // Gonzalo Paniagua Javier (gonzalo@ximian.com)
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:
18 // The above copyright notice and this permission notice shall be
19 // included in all copies or substantial portions of the Software.
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.
30 using System.Collections;
31 using System.Globalization;
33 using System.Security.Permissions;
36 using System.Web.Util;
38 namespace System.Web {
40 class CacheabilityUpdatedEventArgs : EventArgs {
42 public readonly HttpCacheability Cacheability;
44 public CacheabilityUpdatedEventArgs (HttpCacheability cacheability)
46 Cacheability = cacheability;
50 internal delegate void CacheabilityUpdatedCallback (object sender, CacheabilityUpdatedEventArgs args);
52 // CAS - no InheritanceDemand here as the class is sealed
53 [AspNetHostingPermission (SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
54 public sealed class HttpCachePolicy {
56 internal HttpCachePolicy ()
63 HttpCacheVaryByContentEncodings vary_by_content_encodings = new HttpCacheVaryByContentEncodings ();
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;
71 bool etag_from_file_dependencies;
72 bool last_modified_from_file_dependencies;
77 internal bool have_expire_date;
78 internal DateTime expire_date;
79 internal bool have_last_modified;
80 internal DateTime last_modified;
82 //bool LastModifiedFromFileDependencies;
83 HttpCacheRevalidation revalidation;
84 string vary_by_custom;
90 bool sliding_expiration;
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;
98 // always false in 1.x
99 bool omit_vary_star = false;
103 internal event CacheabilityUpdatedCallback CacheabilityUpdated;
108 public HttpCacheVaryByContentEncodings VaryByContentEncodings {
109 get { return vary_by_content_encodings; }
113 public HttpCacheVaryByHeaders VaryByHeaders {
114 get { return vary_by_headers; }
117 public HttpCacheVaryByParams VaryByParams {
118 get { return vary_by_params; }
121 internal bool AllowServerCaching {
122 get { return allow_server_caching; }
125 internal int Duration {
126 get { return duration; }
127 set { duration = value; }
130 internal bool Sliding {
131 get { return sliding_expiration; }
134 internal DateTime Expires {
135 get { return expire_date; }
138 internal ArrayList ValidationCallbacks {
139 get { return validation_callbacks; }
142 // always false in 1.x
143 internal bool OmitVaryStar {
144 get { return omit_vary_star; }
147 internal bool ValidUntilExpires {
148 get { return valid_until_expires; }
151 #endregion // Properties
155 internal int ExpireMinutes ()
157 if (!have_expire_date)
160 return (expire_date - DateTime.Now).Minutes;
163 public void AddValidationCallback (HttpCacheValidateHandler handler, object data)
166 throw new ArgumentNullException ("handler");
168 if (validation_callbacks == null)
169 validation_callbacks = new ArrayList ();
171 validation_callbacks.Add (new Pair (handler, data));
174 public void AppendCacheExtension (string extension)
176 if (extension == null)
177 throw new ArgumentNullException ("extension");
179 if (cache_extension == null)
180 cache_extension = new StringBuilder (extension);
182 cache_extension.Append (", " + extension);
186 // This one now allows the full range of Cacheabilities.
188 public void SetCacheability (HttpCacheability cacheability)
190 if (cacheability < HttpCacheability.NoCache || cacheability > HttpCacheability.ServerAndPrivate)
191 throw new ArgumentOutOfRangeException ("cacheability");
193 if (Cacheability > 0 && cacheability > Cacheability)
196 Cacheability = cacheability;
198 if (CacheabilityUpdated != null)
199 CacheabilityUpdated (this, new CacheabilityUpdatedEventArgs (cacheability));
202 public void SetCacheability (HttpCacheability cacheability, string field)
205 throw new ArgumentNullException ("field");
207 if (cacheability != HttpCacheability.NoCache && cacheability != HttpCacheability.Private)
208 throw new ArgumentException ("Must be NoCache or Private", "cacheability");
211 fields = new ArrayList ();
213 fields.Add (new Pair (cacheability, field));
216 public void SetETag (string etag)
219 throw new ArgumentNullException ("etag");
221 if (this.etag != null)
222 throw new InvalidOperationException ("The ETag header has already been set");
224 if (etag_from_file_dependencies)
225 throw new InvalidOperationException ("SetEtagFromFileDependencies has already been called");
230 public void SetETagFromFileDependencies ()
232 if (this.etag != null)
233 throw new InvalidOperationException ("The ETag header has already been set");
235 etag_from_file_dependencies = true;
238 public void SetExpires (DateTime date)
240 if (have_expire_date && date > expire_date)
243 have_expire_date = true;
247 public void SetLastModified (DateTime date)
249 if (date > DateTime.Now)
250 throw new ArgumentOutOfRangeException ("date");
252 if (have_last_modified && date < last_modified)
255 have_last_modified = true;
256 last_modified = date;
259 public void SetLastModifiedFromFileDependencies ()
261 last_modified_from_file_dependencies = true;
264 public void SetMaxAge (TimeSpan date)
266 if (date < TimeSpan.Zero)
267 throw new ArgumentOutOfRangeException ("date");
269 if (HaveMaxAge && MaxAge < date)
276 public void SetNoServerCaching ()
278 allow_server_caching = false;
281 public void SetNoStore ()
286 public void SetNoTransforms ()
288 set_no_transform = true;
291 public void SetProxyMaxAge (TimeSpan delta)
293 if (delta < TimeSpan.Zero)
294 throw new ArgumentOutOfRangeException ("delta");
296 if (HaveProxyMaxAge && ProxyMaxAge < delta)
302 public void SetRevalidation (HttpCacheRevalidation revalidation)
304 if (revalidation < HttpCacheRevalidation.AllCaches ||
305 revalidation > HttpCacheRevalidation.None)
306 throw new ArgumentOutOfRangeException ("revalidation");
308 if (this.revalidation > revalidation)
309 this.revalidation = revalidation;
312 public void SetSlidingExpiration (bool slide)
314 sliding_expiration = slide;
317 public void SetValidUntilExpires (bool validUntilExpires)
319 valid_until_expires = validUntilExpires;
322 public void SetVaryByCustom (string custom)
325 throw new ArgumentNullException ("custom");
327 if (vary_by_custom != null)
328 throw new InvalidOperationException ("VaryByCustom has already been set.");
330 vary_by_custom = custom;
333 internal string GetVaryByCustom ()
335 return vary_by_custom;
338 public void SetAllowResponseInBrowserHistory (bool allow)
340 if (Cacheability == HttpCacheability.NoCache || Cacheability == HttpCacheability.ServerAndNoCache)
341 allow_response_in_browser_history = allow;
344 internal void SetHeaders (HttpResponse response, ArrayList headers)
346 bool noCache = false;
349 switch (Cacheability) {
350 case HttpCacheability.Public:
354 case HttpCacheability.Private:
355 case HttpCacheability.ServerAndPrivate:
359 case HttpCacheability.NoCache:
360 case HttpCacheability.ServerAndNoCache:
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"));
374 if (MaxAge.TotalSeconds != 0)
375 cc = String.Concat (cc, ", max-age=", ((long) MaxAge.TotalSeconds).ToString ());
377 string expires = TimeUtil.ToUtcTimeString (expire_date);
378 headers.Add (new UnknownResponseHeader ("Expires", expires));
382 cc = String.Concat (cc, ", no-store");
383 if (set_no_transform)
384 cc = String.Concat (cc, ", no-transform");
386 headers.Add (new UnknownResponseHeader ("Cache-Control", cc));
388 if (last_modified_from_file_dependencies || etag_from_file_dependencies)
389 HeadersFromFileDependencies (response);
392 headers.Add (new UnknownResponseHeader ("ETag", etag));
394 if (have_last_modified)
395 headers.Add (new UnknownResponseHeader ("Last-Modified",
396 TimeUtil.ToUtcTimeString (last_modified)));
398 if (!vary_by_params.IgnoreParams) {
399 BaseResponseHeader vb = vary_by_params.GetResponseHeader ();
406 void HeadersFromFileDependencies (HttpResponse response)
408 string [] fileDeps = response.FileDependencies;
410 if (fileDeps == null || fileDeps.Length == 0)
413 bool doEtag = etag != null && etag_from_file_dependencies;
414 if (!doEtag && !last_modified_from_file_dependencies)
417 DateTime latest_mod = DateTime.MinValue, mod;
418 StringBuilder etagsb = new StringBuilder ();
420 foreach (string f in fileDeps) {
421 if (!File.Exists (f))
424 mod = File.GetLastWriteTime (f);
430 if (last_modified_from_file_dependencies && mod > latest_mod)
433 etagsb.AppendFormat ("{0}", mod.Ticks.ToString ("x"));
436 if (last_modified_from_file_dependencies && latest_mod > DateTime.MinValue) {
437 last_modified = latest_mod;
438 have_last_modified = true;
441 if (doEtag && etagsb.Length > 0)
442 etag = etagsb.ToString ();
446 public void SetOmitVaryStar (bool omit)
448 omit_vary_star = omit;
452 #endregion // Methods