Dynamic inclusion of javascript in SVG by means of server side filtering inside apache server

324 views Asked by At

Years back I wrote some code to automatically include some javascript code inside html pages delivered by an http server. The approach is documented here in a blog entry and in this StackOverflow question. The solution served me well over the last years.

The approach in two sentences:

  • for every full html page delivered by the http server some placeholder is added to the bottom of the page by means of server side substitution
  • that placeholder again is replaced by the javascript code to be included by means of server side inclusion

Now I wanted to extend that solution and apply the same logic to pages based on SVG instead of HTML as document format. Note: not SVGs included in html pages, but pages in SVG format to be directly referenced by links.

The extension itself was not an issue, the javascript code is included inside the SVG markup. The issue is that the code is not executed. Unfortunately I have little knowledge of scripting in SVG which is why I am somewhat lost. Maybe someone can give me a hint what is wrong here :-)

The filter chain as included in various apache vhosts definitions:

# ----------
# internal requests to include the piwik tracking code at the bottom of every html page

FilterDeclare PIWIK_token
FilterProvider PIWIK_token SUBSTITUTE resp=Content-Type $text/html
SUBSTITUTE 's|^\s*</body>|<!--#include virtual="/piwik"--></body>|iq'
FilterProvider PIWIK_token SUBSTITUTE resp=Content-Type $image/svg+xml
SUBSTITUTE 's|^\s*</svg>|<!--#include virtual="/piwik"--></svg>|iq'

FilterDeclare PIWIK_code
FilterProvider PIWIK_code INCLUDES resp=Content-Type $text/html
FilterProvider PIWIK_code INCLUDES resp=Content-Type $image/svg+xml

FilterChain PIWIK_token PIWIK_code

# map virtual request to the file system
Alias /piwik /srv/www/internal/piwik.php

# all that is left for the virtual host is to do two things:
#        SetEnv PIWIK_ID 15
#        Include /etc/apache2/vhosts.d/_internal.inc
# note: the '15' above is an example piwik site id

The javascript code to be included is a slightly modified version of the piwik tracking snippet:

<?php
define('piwikBase','https://some.domain.xxx/stats/');
define('piwikSite',apache_getenv('PIWIK_ID'));
if(is_numeric(piwikSite)){
?>

<script type="text/javascript">
  var _paq = _paq || [];
  _paq.push(["trackPageView"]);
  _paq.push(["enableLinkTracking"]);

  (function() {
    var u="<?= piwikBase ?>";
    _paq.push(["setTrackerUrl", u+"piwik.php"]);
    _paq.push(["setSiteId", "<?= piwikSite ?>"]);
    var d=document, g=d.createElement("script"), s=d.getElementsByTagName("script")[0]; g.type="text/javascript";
    g.defer=true; g.async=true; g.src=u+"piwik.js"; s.parentNode.insertBefore(g,s);
  })();
</script>

<?php } else { ?>
<!-- invalid piwik site id: <?php echo piwikSite;?> -->
<?php } ?>

As said: the extension works, the javascript code is successfully included into the SVG documents. However it is apparently not activated and I failed to see the cause for that...

The final SVG as delivered looks like this:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg
    xmlns:dc="http://purl.org/dc/elements/1.1/"
    xmlns:cc="http://creativecommons.org/ns#"
    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
    xmlns:svg="http://www.w3.org/2000/svg"
    xmlns="http://www.w3.org/2000/svg"
    xmlns:xlink="http://www.w3.org/1999/xlink"
    version="1.2"
    width="100%"
    height="100%"
    viewBox="0 0 640 400"
    docname="somedocument.svg">

[... content of the SVG ...]

<script type="text/javascript">
  var _paq = _paq || [];
  _paq.push(["trackPageView"]);
  _paq.push(["enableLinkTracking"]);

  (function() {
    var u="https://some.domain.xxx/stats/";
    _paq.push(["setTrackerUrl", u+"piwik.php"]);
    _paq.push(["setSiteId", "15"]);
    var d=document, g=d.createElement("script"), s=d.getElementsByTagName("script")[0]; g.type="text/javascript";
    g.defer=true; g.async=true; g.src=u+"piwik.js"; s.parentNode.insertBefore(g,s);
  })();
</script>

</svg>
2

There are 2 answers

2
myf On BEST ANSWER

Just guess, but I'd try to change the code so dynamic script injection is replaced with inline script tag with xlink:href (I've tested doing it dynamically as your code does with seemingly "more correct" createAttibuteNS('xlink','href','...') but result wasn't executed either)

Resulting code would look like this (haven't tested in real environment, so wath for typos):

<?php
define('piwikBase','https://some.domain.xxx/stats/');
define('piwikSite',apache_getenv('PIWIK_ID'));
if(is_numeric(piwikSite)){
?>
<script type="text/javascript"><![CDATA[
  var _paq = _paq || [];
  _paq.push(["trackPageView"]);
  _paq.push(["enableLinkTracking"]);
  _paq.push(["setTrackerUrl", "<?= piwikBase ?>piwik.php"]);
  _paq.push(["setSiteId", "<?= piwikSite ?>"]);    
]]></script>
<script type="text/javascript"
 xmlns:xlink="http://www.w3.org/1999/xlink"
 xlink:href="<?= piwikBase ?>piwik.js"
 defer="defer" async="async"></script>

<?php } else { ?>
<!-- invalid piwik site id: <?php echo piwikSite;?> -->
<?php } ?>

I'm not sure about those defer and async stuff, whether it has any imact in SVG. I've tested this with simple datauri sample and it seemed to work well.

Notes:

  • xmlns:xlink="http://www.w3.org/1999/xlink" on the script or some of it's parent is necessary, but most probably your SVG has it already
  • CDATA aren't necessary in this case, but if you plan to use < or & there in future … you know what.
1
Robert Longson On

You can't create an SVG script element with

g=d.createElement("script")

you need to write

g=d.createElementNS("http://www.w3.org/2000/svg", "script")