Injecting content to every page on a site
App Dev Manager Roberto Peña explores a creative approach to injecting content into every page on a web site.
A lot of our customers in Premier Support have large legacy web applications that are costly to replace, migrate or update; to make the matters more complex, these application normally support mission critical business processes that require them to evolve in order to adapt to modern needs…
As part of the ADM role, customers normally reach out when they are looking for a new angle to solve a problem, last year a couple of my customers curiously came to me with the same question:
How can I extend the functionality of my application without touching the code?
Let me explain a little further. In both cases the solutions were composed of multiple applications arranged on a folder hierarchy. One of them was a combination of ASP.Net and traditional ASP. We needed to find a way to add a script to these solutions without having to change the code on each application, or even worse, on every page for the traditional ASP.
So basically I had to come up with a way to magically inject a script on every response, regardless of what technology was used to create the application or web page… believe it or not, this was not only possible but it is quick and simple!
Before I go into a solution, it’s important to understand some risks.
There are very good reasons to avoid this approach, as it will help you quickly solve a problem, but it will add to your technical debt. If you decide to use this technique, use it only as a temporary solution and establish a plan to migrate your legacy solution to a more modern technology. That said, there are scenarios where you need to buy a little time plan a proper migration and/or solve a short-term problem creatively. Agree on the right way to solve a problem and make every effort to do that. If you take a short cut, it’s important to be transparent about the debt and risk associated with that decision and agree it’s a temporary step toward a permanent solution that is defined and documented.
As this is a proof of concept, I will use a simple script that can pop an alert after a few seconds:
<script>window.setTimeout("alert('this was injected!!')", 3000);</script>
Any script will work, for complex scenarios an external script can be referenced with the href attribute, this is a simple example but it can be expanded to fill any need.
The first problem, we need to figure out is where to insert our script, we need to find a place that will be present on all the pages, my choice is the closing body tag </body>, we need to be careful though, we cannot assume it will always be written the same way… line feeds, spaces, casing… we have to match the tag regardless of how it is formed, luckily for us a simple regular expression can take care of this problem.
Instead of a complex approach I decided to use a simple trick, URL Rewrite is an IIS extension that allows the manipulation of requests and responses for IIS; all we need is to install this extension and then we can simply instruct IIS to inject the script at the chosen target!
In order to do this you will need to add a web.config file to your directory, it doesn’t matter if your application is not based on ASP.Net, you can put the file on a directory hosting a PHP application and IIS will still work as expected. The following is a complete example of a web.config file that will inject our script:
<?xml version="1.0" encoding="UTF-8"?> <configuration> <system.webServer> <rewrite> <outboundRules> <rule name="ScriptInjection" stopProcessing="true"> <match pattern="<\/(\s)?body>" /> <!-- our regular expression --> <action type="Rewrite" value="<script>window.setTimeout("alert('this was injected!!')", 3000);</script></body>" /> </rule> </outboundRules> </rewrite> </system.webServer> </configuration>
Notice how both the search pattern and the script have been html encoded, this is required and if it is not done correctly the response fails without a clear reason.
You might be wondering “What about Azure App Services? I don’t have access to install components there…” Well, Url Rewrite is already installed on App Services so, the procedure is exactly the same!
There is always a catch isn’t it? In this case, as we are “editing” a response after it has been processed, we could face a problem if HTTP Compression is enabled in your server (basically we would be trying to find some text on a content that has been previously compressed and it would fail). You can disable compression at the server level but it is better to disable it only for the applications were you are injecting your script, to do this you can simply add the following line right before the <rewrite> node:
<urlCompression doStaticCompression="false" doDynamicCompression="false" />
And… That’s it! Those are all the steps required to accomplish our task, this is a real simple solution to a complex problem. I tested with ASP, ASP.Net, PHP, static content… it doesn’t really matter as we are modifying the response after the request has been completely processed. You may still need to validate the behavior with template or output cache but most of time this approach will simply work.