mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2025-07-15 14:54:57 +08:00
parent
6d0a29af53
commit
230dfc30da
@ -5,6 +5,7 @@ using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Web;
|
||||
using System.Web.Mvc;
|
||||
@ -16,6 +17,7 @@ using Orchard.Environment.Configuration;
|
||||
using Orchard.Logging;
|
||||
using Orchard.Mvc.Extensions;
|
||||
using Orchard.Mvc.Filters;
|
||||
using Orchard.Mvc.Html;
|
||||
using Orchard.OutputCache.Helpers;
|
||||
using Orchard.OutputCache.Models;
|
||||
using Orchard.OutputCache.Services;
|
||||
@ -26,7 +28,8 @@ using Orchard.Utility.Extensions;
|
||||
|
||||
namespace Orchard.OutputCache.Filters {
|
||||
public class OutputCacheFilter : FilterProvider, IActionFilter, IResultFilter, IDisposable {
|
||||
|
||||
private const string REQUEST_VERIFICATION_TOKEN_BEACON_TAG = "<$request-verification-token-beacon-for-hidden-field />";
|
||||
private const string REQUEST_VERIFICATION_TOKEN_INVARIANT_TAG = "<input name=\"__RequestVerificationToken\"";
|
||||
private static string _refreshKey = "__r";
|
||||
private static long _epoch = new DateTime(2014, DateTimeKind.Utc).Ticks;
|
||||
|
||||
@ -80,6 +83,7 @@ namespace Orchard.OutputCache.Filters {
|
||||
private string _invariantCacheKey;
|
||||
private bool _transformRedirect;
|
||||
private bool _isCachingRequest;
|
||||
private bool _etagNeedsRefresh;
|
||||
private IEnumerable<string> _ignoredRelativePaths;
|
||||
|
||||
public void OnActionExecuting(ActionExecutingContext filterContext) {
|
||||
@ -192,7 +196,7 @@ namespace Orchard.OutputCache.Filters {
|
||||
|
||||
Logger.Debug("Item '{0}' was rendered.", _cacheKey);
|
||||
|
||||
|
||||
|
||||
if (!ResponseIsCacheable(filterContext)) {
|
||||
filterContext.HttpContext.Response.Cache.SetCacheability(HttpCacheability.NoCache);
|
||||
filterContext.HttpContext.Response.Cache.SetNoStore();
|
||||
@ -233,12 +237,14 @@ namespace Orchard.OutputCache.Filters {
|
||||
return;
|
||||
}
|
||||
|
||||
var cachedOutput = ReplaceRequestVerificationTokenWithBeaconTag(output, response.ContentEncoding);
|
||||
|
||||
using (var scope = _workContextAccessor.CreateWorkContextScope()) {
|
||||
var cacheItem = new CacheItem() {
|
||||
CachedOnUtc = _now,
|
||||
Duration = cacheDuration,
|
||||
GraceTime = cacheGraceTime,
|
||||
Output = output,
|
||||
Output = cachedOutput,
|
||||
ContentType = response.ContentType,
|
||||
QueryString = filterContext.HttpContext.Request.Url.Query,
|
||||
CacheKey = _cacheKey,
|
||||
@ -394,7 +400,7 @@ namespace Orchard.OutputCache.Filters {
|
||||
// Vary by configured request headers.
|
||||
var requestHeaders = filterContext.RequestContext.HttpContext.Request.Headers;
|
||||
foreach (var varyByRequestHeader in CacheSettings.VaryByRequestHeaders) {
|
||||
if (requestHeaders[varyByRequestHeader]!=null)
|
||||
if (requestHeaders[varyByRequestHeader] != null)
|
||||
result["HEADER:" + varyByRequestHeader] = requestHeaders[varyByRequestHeader];
|
||||
}
|
||||
|
||||
@ -516,27 +522,42 @@ namespace Orchard.OutputCache.Filters {
|
||||
response.AddHeader("X-Cached-On", cacheItem.CachedOnUtc.ToString("r"));
|
||||
response.AddHeader("X-Cached-Until", cacheItem.ValidUntilUtc.ToString("r"));
|
||||
}
|
||||
|
||||
|
||||
// Shorcut action execution.
|
||||
filterContext.Result = new FileContentResult(cacheItem.Output, cacheItem.ContentType);
|
||||
filterContext.Result = new FileContentResult(
|
||||
ReplaceBeaconTagWithFreshRequestVerificationToken(cacheItem.Output, response.ContentEncoding), // replace the beacon created by the ReplaceRequestVerificationTokenWithBeacon method witha fresh new one
|
||||
cacheItem.ContentType);
|
||||
response.StatusCode = cacheItem.StatusCode;
|
||||
|
||||
// Add ETag header
|
||||
var itemETag = cacheItem.ETag;
|
||||
if (HttpRuntime.UsingIntegratedPipeline && response.Headers.Get("ETag") == null && cacheItem.ETag != null) {
|
||||
response.Headers["ETag"] = cacheItem.ETag;
|
||||
if (_etagNeedsRefresh) {
|
||||
// Add ETag header for the newly created item
|
||||
var newEtag = Guid.NewGuid().ToString("n");
|
||||
itemETag = "";
|
||||
if (HttpRuntime.UsingIntegratedPipeline) {
|
||||
if (response.Headers.Get("ETag") == null) {
|
||||
response.Headers["ETag"] = newEtag;
|
||||
itemETag = newEtag;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
response.Headers["ETag"] = itemETag;
|
||||
}
|
||||
}
|
||||
|
||||
// Check ETag in request
|
||||
// https://www.w3.org/2005/MWI/BPWG/techs/CachingWithETag.html
|
||||
var etag = request.Headers["If-None-Match"];
|
||||
if (!String.IsNullOrEmpty(etag)) {
|
||||
if (String.Equals(etag, cacheItem.ETag, StringComparison.Ordinal)) {
|
||||
if (!String.IsNullOrEmpty(etag) && !_etagNeedsRefresh) {
|
||||
if (String.Equals(etag, itemETag, StringComparison.Ordinal)) {
|
||||
// ETag matches the cached item, we return a 304
|
||||
filterContext.Result = new HttpStatusCodeResult(HttpStatusCode.NotModified);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ApplyCacheControl(response);
|
||||
}
|
||||
|
||||
@ -588,6 +609,48 @@ namespace Orchard.OutputCache.Filters {
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] ReplaceRequestVerificationTokenWithBeaconTag(byte[] source, Encoding encoding) {
|
||||
// Because of the __RequestVerificationToken hidden field vary by the user, before caching the output, we need to replace the
|
||||
// __RequestVerificationToken hidden field with a "beacon" text that will be replaced before rendering the page
|
||||
// with a fresh new __RequestVerificationToken hidden field.
|
||||
// What we do is to replace every <input name="__RequestVerificationToken" value="{the-value}" /> with
|
||||
// <$request-verification-token-beacon-for-hidden-field />
|
||||
if (PreventCachingRequestVerificationToken()) {
|
||||
var outputString = encoding.GetString(source);
|
||||
var resultString = new StringBuilder();
|
||||
var startIndex = 0;
|
||||
var verificationTokenTagStartIndex = outputString.IndexOf(REQUEST_VERIFICATION_TOKEN_INVARIANT_TAG, startIndex); //searches in the outputString first byte of RequestVerificationToken input tag
|
||||
while (verificationTokenTagStartIndex >= 0) {
|
||||
resultString.Append(outputString.Substring(startIndex, verificationTokenTagStartIndex - startIndex)); //appends to resultString the text before RequestVerificationToken input tag
|
||||
resultString.Append(REQUEST_VERIFICATION_TOKEN_BEACON_TAG); //appends the beacon placeholder tag
|
||||
startIndex = outputString.IndexOf("/>", verificationTokenTagStartIndex) + 2; // set the new starting index after the replaced RequestVerificationToken input tag
|
||||
verificationTokenTagStartIndex = outputString.IndexOf(REQUEST_VERIFICATION_TOKEN_INVARIANT_TAG, startIndex);// searches in the outputString next first byte of RequestVerificationToken input tag
|
||||
}
|
||||
resultString.Append(outputString.Substring(startIndex)); // completes the resultString appending the remaining characters
|
||||
return encoding.GetBytes(resultString.ToString());
|
||||
}
|
||||
return source;
|
||||
}
|
||||
|
||||
private byte[] ReplaceBeaconTagWithFreshRequestVerificationToken(byte[] source, Encoding encoding) {
|
||||
// Because of the __RequestVerificationToken hidden field vary by the user, we replace the beacon generated by the ReplaceRequestVerificationTokenWithBeacon method
|
||||
// with a fresh new __RequestVerificationToken hidden field.
|
||||
// What we do is to replace every <$request-verification-token-beacon-for-hidden-field /> with
|
||||
// <input name="__RequestVerificationToken " value="{the-fresh-new-value}" />
|
||||
if (PreventCachingRequestVerificationToken()) {
|
||||
var outputString = encoding.GetString(source);
|
||||
var antiForgeyToken = new HtmlHelper(new ViewContext(), new ViewDataContainer()).AntiForgeryTokenOrchard();
|
||||
var resultString = outputString.Replace(REQUEST_VERIFICATION_TOKEN_BEACON_TAG, antiForgeyToken.ToString());
|
||||
_etagNeedsRefresh = outputString != resultString;
|
||||
return encoding.GetBytes(resultString);
|
||||
}
|
||||
return source;
|
||||
}
|
||||
|
||||
private bool PreventCachingRequestVerificationToken() {
|
||||
return _cacheSettings.CacheAuthenticatedRequests && (!_cacheSettings.VaryByAuthenticationState || _workContext.CurrentUser != null);
|
||||
}
|
||||
|
||||
protected virtual bool IsIgnoredUrl(string url) {
|
||||
if (IgnoredRelativePaths == null || !IgnoredRelativePaths.Any()) {
|
||||
return false;
|
||||
@ -601,7 +664,8 @@ namespace Orchard.OutputCache.Filters {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
// if there is a RequestUrlPrefix, we want to check by also removing it from the
|
||||
// url we are verifying, because the configuration might have been done without it
|
||||
var tmp = url.TrimStart(new[] { '/' });
|
||||
@ -722,7 +786,7 @@ namespace Orchard.OutputCache.Filters {
|
||||
// Ensure locks are released even after an unexpected exception
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public class ViewDataContainer : IViewDataContainer {
|
||||
|
Loading…
Reference in New Issue
Block a user