FYI I have ended up creating a code solution to this as an HTTPModule. It won't be good for everyone but seems to be doing the job for us.
CDNModule.cs:
public class CDNModule : IHttpModule
{
void IHttpModule.Dispose()
{
}
void IHttpModule.Init(HttpApplication context)
{
context.ReleaseRequestState += new EventHandler(context_ReleaseRequestState);
}
void context_ReleaseRequestState(object sender, EventArgs e)
{
//Since it's DNN all requests (for pages!) will come in via the default.aspx handler so we only want to do this when we are processing web pages
//If this handler runs on other files types (like images for example) then we get in all kinds of trouble
if (HttpContext.Current.Handler != null && HttpContext.Current.Handler.GetType().FullName == "ASP.default_aspx")
HttpContext.Current.Response.Filter = new CdnFilter(HttpContext.Current.Response.Filter);
}
}
/// <summary>
/// Works on UTF-8 text/html requests only and will bork any other requests you attempt to put through it
/// </summary>
public class CdnFilter : MemoryStream
{
private readonly Stream _outputStream;
public CdnFilter(Stream outputStream)
{
_outputStream = outputStream;
}
public override void Write(byte[] buffer, int offset, int count)
{
var contentInBuffer = Encoding.UTF8.GetString(buffer);
string CDNUrl = ConfigurationManager.AppSettings["CDNUrl"];
if (CDNUrl.EndsWith("/"))
CDNUrl = CDNUrl.Substring(0, CDNUrl.Length - 1);
if (string.IsNullOrEmpty(CDNUrl) == false)
{
foreach (Match match in Regex.Matches(contentInBuffer,
@"(?:\<img|\<Img|\<IMG)(?:(?!\>).)*(?:src|Src|SRC)\=(?:'|"")((?!['""]).)*(?:'|"")" + "|" +
@"(?:\<link|\<Link|\<LINK)(?:(?!\>).)*(?:href|Href|HREF)\=(?:'|"")((?!['""]).)*(?:'|"")" + "|" +
@"(?:\<script|\<Script|\<SCRIPT)(?:(?!\>).)*(?:src|Src|SRC)\=(?:'|"")((?!['""]).)*(?:'|"")", RegexOptions.Compiled))
{
string source = "";
CaptureCollection caps = match.Groups[1].Captures;
if (caps.Count == 0)
caps = match.Groups[2].Captures;
if (caps.Count == 0)
caps = match.Groups[3].Captures;
if (caps.Count == 0)
continue;
foreach (Capture c in caps) source += c.Value;
string replace = source;
if (replace.StartsWith("//"))
replace = replace.Substring(1);
if (source.StartsWith("/") || source.StartsWith("//"))
{
//do twice for each type of quote (we want to match on the quotes to stop repeated replacements)
contentInBuffer = contentInBuffer.Replace("=\"" + source, "=\"" + CDNUrl + replace);
contentInBuffer = contentInBuffer.Replace("='" + source, "='" + CDNUrl + replace);
}
}
}
_outputStream.Write(Encoding.UTF8.GetBytes(contentInBuffer), offset, Encoding.UTF8.GetByteCount(contentInBuffer));
}
}
Web.config:
<system.webServer>
....
<modules>
<add name="CDNModule" type="Your.Namespace.Here.CDNModule, YourAssemblyName" />