2017-01-21

Enhancing the XML generated by the DAV SVN module

Suppose you set up Apache to serve Subversion repositories. You might do this:

<Location "/svn-repos/">
  DAV svn
  SVNParentPath /var/svn-repos
  SVNIndexXSLT "/templates/svn-dir.xsl"
</Location>

The SVNIndexXSLT directive tells the Subversion module to serve any directory in a repository as XML to ordinary browsers (not SVN clients, which talk WebDAV). The XML will include a link to the XSLT stylesheet under the virtual path /templates/svn-dir.xsl, and the browser is expected to fetch that too, and use it to transform the XML into HTML. A sample XML output could be:

<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="/templates/svn-dir.xsl"?>
<!DOCTYPE svn [
  <!ELEMENT svn   (index)>
  <!ATTLIST svn   version CDATA #REQUIRED
                  href    CDATA #REQUIRED>
  <!ELEMENT index (updir?, (file | dir)*)>
  <!ATTLIST index name    CDATA #IMPLIED
                  path    CDATA #IMPLIED
                  rev     CDATA #IMPLIED
                  base    CDATA #IMPLIED>
  <!ELEMENT updir EMPTY>
  <!ATTLIST updir href    CDATA #REQUIRED>
  <!ELEMENT file  EMPTY>
  <!ATTLIST file  name    CDATA #REQUIRED
                  href    CDATA #REQUIRED>
  <!ELEMENT dir   EMPTY>
  <!ATTLIST dir   name    CDATA #REQUIRED
                  href    CDATA #REQUIRED>
]>
<svn version="1.9.3 (r1718519)"
     href="http://subversion.apache.org/">
  <index rev="16" path="/" base="cakes-test">
    <dir name="café" href="caf%c3%a9/" />
    <file name="test" href="test" />
  </index>
</svn>

This is fine if you only want to browse the rest of the repository in the same manner, as it contains properly escaped relative links to files and other directories in the repository. However, if you want to jump to any other pages on the site, ones that aren't under DAV control, you have to hard-wire absolute (local) links to them.

When I generate XSLT-transformable XML dynamically, I get the script to include an attribute with a configurable path prefix, and the XSLT can then generate absolute URLs from relative ones simple by attaching the prefix. But you can't do the same with the DAV/SVN-generated XML, because you don't generate it! There also seems to be a (reasonable) unwillingness to develop the DAV/SVN Apache module, so a feature request would likely be ignored.

Time for an Apache rewrite rule. It has to match only GETs within the Subversion hierarchy (/svn-repos/ in our example), and only if the first path element after the repository name is not !svn, the special marker used by WebDAV SVN clients:

RewriteEngine On
RewriteCond %{REQUEST_METHOD} =GET
RewriteCond %{REQUEST_URI} ^/svn-repos/
RewriteCond %{REQUEST_URI} !^/svn-repos/[^/]+/!svn/
RewriteRule ^/svn-repos(/.*)$ /scripts/svn-web.php$1 [PT]

This now intercepts (say) /svn-repos/cakes-test/, and internally redirects it to /scripts/svn-web.php/cakes-test/. The script will run with PATH_INFO set to /cakes-test/, so it will know what file or directory to look at. Then it can do its own authorization checks, and serve enhanced XML.

It would be nice if /scripts/svn-web.php could be an internal file path, rather than an exposed virtual path. RewriteRule can be given a filesystem path instead of a virtual path, and the server identifies as such by seeing if the first path element exists in the filesystem. However, this doesn't seem to work:

RewriteEngine On
RewriteCond %{REQUEST_METHOD} =GET
RewriteCond %{REQUEST_URI} ^/svn-repos/
RewriteCond %{REQUEST_URI} !^/svn-repos/[^/]+/!svn/
RewriteRule ^/svn-repos(/.*)$ /usr/local/share/scripts/svn-web.php$1 [PT]

The server will respond with a 404, claiming that /usr/local/share/scripts/svn-web.php doesn't exist. It seems to require an alternative set of flags to work:

RewriteEngine On
RewriteRule ^/svn-repos(/.*)$ /usr/local/share/scripts/svn-web.php$1 [L,H=application/x-httpd-php]

So now L turns off further rewriting, and H explicitly sets the handler to PHP.

No comments:

Post a Comment