Using UrlRewrite to inject scripts into web pages
Tags: todayilearned, javascript, urlrewrite
Published: 2020-08-19 13:48:51

Today I learned that there's a gotcha when injecting script into pages using IIS's UrlRewrite: JavaScript scripts with braces. 

Braces have a purpose in rewrite rules, to introduce those things captured in the match clause. Notice in the following outbound rule, that the text captured by the regular expression in the `match pattern` is reinserted in the `action`.
```xml
<rule name="Inject GTM After /TITLE" preCondition="ResponseIsHtml1" enabled="true" stopProcessing="true">
<match filterByTags="None" pattern="&lt;/title>" />
<action type="Rewrite" value="{R:0}&lt;!-- Google Tag Manager (noscript) --> &lt;noscript>&lt;iframe src=&quot;https://www.googletagmanager.com/ns.html?id=GTM-XXXX&quot; height=&quot;0&quot; width=&quot;0&quot; style='display:none;visibility:hidden'>&lt;/iframe>&lt;/noscript> &lt;!-- End Google Tag Manager (noscript) -->" />
</rule>
```
So there we were trying to embed a GTM script into a page via a rewrite rule, viz
```xml
<rule name="Inject GTM After /TITLE" preCondition="ResponseIsHtml1" enabled="true">
<match filterByTags="None" pattern="&lt;/title>" />
<action type="Rewrite" value="{R:0}&lt;!-- Google Tag Manager --> &lt;script>(function(w,d,s,l,i) { w[l]=w[l]||[];w[l].push( { 'gtm.start': new Date().getTime(),event:'gtm.js' } );var f=d.getElementsByTagName(s)[0], j=d.createElement(s),dl=l!='dataLayer'?'&amp;l='+l:'';j.async=true;j.src= 'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f); } )(window,document,'script','dataLayer','GTM-XXXX');&lt;/script> &lt;!-- End Google Tag Manager -->" />
</rule>
```
That looked like it would work but it does *NOT* as the JavaScript contains `{` and IIS's UrlRewrite tool immediately complains with a yellow box reading `The rewrite provider " w[l]=w[l]||[];w[l].push( { 'gtm.start'" does not exist` (note that it terminates at the : which would separate the usual R or C from the numeric qualifier.)

We tried swapping `{` with `&#123;` but that doesn't work well, giving us the following in the generated web page:
```html
<!DOCTYPE HTML>
<html lang="en">
    <head><title>The Page</title><!-- Google Tag Manager --> <script>(function(w,d,s,l,i) &#123; w[l]=w[l]||[];w[l].push( &#123; 'gtm.start': new Date().getTime(),event:'gtm.js' } );var f=d.getElementsByTagName(s)[0], j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src= 'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f); } )(window,document,'script','dataLayer','GTM-XXXX');</script> <!-- End Google Tag Manager -->
    <meta charset="UTF-8"/>
```
Naturally, the browser's JavaScript interpreter complains about bad code.

What we finally ended up doing was to create a js file in another of our websites and then embed a reference to it in the rule, viz
```xml
<rule name="Inject GTM After /TITLE" preCondition="ResponseIsHtml1" enabled="true" stopProcessing="true">
<match filterByTags="None" pattern="&lt;/title>" />
<action type="Rewrite" value="{R:0}&lt;!-- Google Tag Manager --> &lt;script type=&quot;text/javascript&quot; src=&quot;https://sub.domain.com.au/js/GTM-XXXX.js&quot;>&lt;/script> &lt;!-- End Google Tag Manager -->" />
</rule>
```
Maybe this is the better thing to do. It'll do for now.