Skip to content

Instantly share code, notes, and snippets.

@grantmacken
Last active March 8, 2023 00:13
Show Gist options
  • Save grantmacken/756e0247e753129404c5 to your computer and use it in GitHub Desktop.
Save grantmacken/756e0247e753129404c5 to your computer and use it in GitHub Desktop.
xQuery helper functions for dealing with URLs
xquery version "3.0";
(:~
This module contains helper functions for dealing with URLs.
When receiving webmention the post parameter 'source' is a URL pointing to the
origin of the mention.
Before we get and store a sanitized version of an HTML document the url links to
we want to able to
* check if the URL meets our acceptance criteria ( no use wasting bandwidth )
* extract the base URL for the html document so we can resolve any relative or
absolute URLs contained in document to the base URL
* be able to hash the url to get a unique identifier that can be used as a file
name to store the document
@author Grant MacKenzie
@version 0.01
@see http://timezra.blogspot.co.nz/2010/05/regex-to-validate-uris.html
@see http://greenbytes.de/tech/tc/uris/
@see http://greenbytes.de/tech/webdav/rfc3986.html#examples
@see http://greenbytes.de/tech/webdav/draft-reschke-ref-parsing-latest.xml
@see http://rxr.whitequark.org/mri/source/lib/uri/common.rb
@see http://stackoverflow.com/questions/1547899/which-characters-make-a-url-invalid
@see http://www.ietf.org/rfc/rfc1738.txt
@see http://esupport.trendmicro.com/solution/en-US/1054481.aspx
@see https://www.owasp.org/index.php/Blind_XPath_Injection
@see http://en.wikibooks.org/wiki/XQuery/Special_Characters
:)
module namespace muURL="http://markup.co.nz/#muURL";
import module namespace util="http://exist-db.org/xquery/util";
import module namespace xmldb="http://exist-db.org/xquery/xmldb";
import module namespace system="http://exist-db.org/xquery/system";
import module namespace http = "http://expath.org/ns/http-client";
(: DEPENDENCIES: import my libs :)
import module namespace muCache = "http://markup.co.nz/#muCache" at 'muCache.xqm';
(:
http://www.danielmiessler.com/study/encoding/
http://esupport.trendmicro.com/solution/en-US/1054481.aspx
:)
declare variable $muURL:cpIllegalChars :=
(000,001,002,003,004,005,006,007,008,009,010,011,012,013,014,015,016,017,018,
019,020,021,022,023,024,025,026,027,028,029,030,031,032,035,039,060,062,091,
093,094,096,123,124,125);
(:~
hash the url to get a unique identifier that can be used as a file name
@param $URL as xs:string
@return xs:string
:)
declare
function muURL:urlHash( $url as xs:string ) as xs:string {
let $base64flag := true()
let $alogo := 'md5'
let $hash := replace(util:hash( $url , $alogo, $base64flag), '(=+$)', '')
return
translate( $hash, '+/', '-_')
};
(:
before storing a html doc we want to be able to resolve links in the doc
to do this we need to get the base URL for the doc
@see http://www.ietf.org/rfc/rfc2396.txt
5.1.3. Base URI from the Retrieval URI
If no base URI is embedded and the document is not encapsulated
within some other entity (e.g., the top level of a composite entity),
then, look for base in html doc
if a URI was used to retrieve the base document, that URI shall
be considered the base URI. Note that if the retrieval was the
result of a redirected request, the last URI used (i.e., that which
resulted in the actual retrieval of the document) is the base URI.
:)
declare
function muURL:isBaseInDoc( $documentElement as element() ) as xs:boolean {
exists( $documentElement//*[local-name(.) eq 'base' ][@href] )
};
declare
function muURL:getBase($url) {
let $node := muCache:fetch( $url )
return
if(muURL:isBaseInDoc($node))
then ( $node//*[local-name(.) eq 'base' ][@href]/@href/string() )
else( $url )
};
(:
URL ACCEPTANCE CRITERIA
before we do a GET URL must meet necessary acceptance criteria
canCaste
hasHttpScheme
hasAcceptableAuthority
:)
declare
function muURL:meetsAcceptableCriteria( $url as xs:string ) as xs:boolean{
every $item in ( muURL:hasNoIllegalChars( $url ),
muURL:canCaste( $url ),
muURL:hasHttpScheme( $url ),
muURL:hasAcceptableAuthority( $url )
) satisfies $item = true()
};
(:~
Check if URL has No Illegal Characters:
@param $url as xs:string
@return xs:boolean
:)
declare
function muURL:hasNoIllegalChars( $url as xs:string ) as xs:boolean{
every $char in ( string-to-codepoints( $url ) )
satisfies not( $char = $muURL:cpIllegalChars or $char gt 127 )
};
(:
The 'lexical space' of anyURI is finite-length character sequences
which, when the algorithm defined in Section 5.4 of [XML Linking
Language] is applied to them, result in strings which are legal URIs
according to [RFC 2396], as amended by [RFC 2732]
:)
declare
function muURL:canCaste( $url as xs:string ) as xs:boolean{
let $u :=
try {
$url cast as xs:anyURI
} catch * {()}
return
if( empty( $u ) )
then ( false() )
else( true() )
};
(:
A string that may or may not be a valid URI scheme component according to
Section 3.1 of [RFC3986].
3.1. Scheme Component
scheme = alpha *( alpha | digit | "+" | "-" | "." )
:)
declare
function muURL:getScheme( $u ) {
let $input :=
if( not( contains( $u, ':')) )
then (
fn:error(fn:QName('http://markup.co.nz/#muURL',
'urlHasNoScheme'),
'url has no scheme component')
)
else(substring-before( $u, ':' ))
let $pattern := '^([a-zA-Z][a-zA-Z0-9\+\-\.]+)$'
let $flags := ''
return
if( matches( $input, $pattern ))
then ( $input )
else(
fn:error(fn:QName('http://markup.co.nz/#muURL',
'urlHasBadScheme'),
'url has bad scheme component')
)
};
declare
function muURL:hasHttpScheme( $u as xs:string ) as xs:boolean{
try{
matches( muURL:getScheme( $u ) ,'^https?$')
} catch * {
false()
}
};
declare
function muURL:url-hier_part( $u ) {
substring-after( $u, ':' )
};
(:
The authority component is preceded by a double slash "//" and is
terminated by the next slash "/", question-mark "?", or by the end of
the URI. Within the authority component, the characters ";", ":",
"@", "?", and "/" are reserved.
authority = [ userinfo "@" ] host [ ":" port ]
we are after the host part and will drop 'userinfo' and 'port'
:)
declare
function muURL:getAuthority( $u as xs:string ) {
let $start :=
if( not( contains( $u, '//')) )
then (
fn:error(fn:QName('http://markup.co.nz/#muURL',
'urlHasNoAuthority'),
'url has no authority component: the authority component is preceded by a double slash "//" ')
)
else( substring-after( $u, '//' ) )
let $input :=
if( matches($start,'^.+/') ) then ( substring-before( $start , '/' ))
else if( matches($start,'^.+\?') ) then ( substring-before( $start , '?' ))
else($start)
let $IPv4address := '[0-9]+((\.[0-9]+){3})'
let $toplabel := '[a-zA-Z](([a-zA-Z0-9\-])*[a-zA-Z0-9])?'
let $domainlabel := '[a-zA-Z0-9](([a-zA-Z0-9\-])*[a-zA-Z0-9])?'
let $pattern := $domainlabel || '(\.' || $toplabel || '){1,2}'
let $flags := ''
return
if( matches( $input, $pattern))
then ( $input )
else(
fn:error(fn:QName('http://markup.co.nz/#muURL',
'urlHasBadAuthority'),
'url has bad authority component')
)
};
(:
/(?:\\@[_a-zA-Z0-9]{1,17})|(?:(?:(?:(?:http|https|irc)?:\\/\\/(?:(?:[!$&-.0-9;=?A-Z_a-z]|(?:\\%[a-fA-F0-9]{2}))+(?:\\:(?:[!$&-.0-9;=?A-Z_a-z]|(?:\\%[a-fA-F0-9]{2}))+)?\\@)?)?(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*\\.)+(?:(?:aero|arpa|asia|a[cdefgilmnoqrstuwxz])|(?:biz|b[abdefghijmnorstvwyz])|(?:cat|com|coop|c[acdfghiklmnoruvxyz])|d[ejkmoz]|(?:edu|e[cegrstu])|f[ijkmor]|(?:gov|g[abdefghilmnpqrstuwy])|h[kmnrtu]|(?:info|int|i[delmnoqrst])|j[emop]|k[eghimnrwyz]|l[abcikrstuvy]|(?:mil|museum|m[acdeghklmnopqrstuvwxyz])|(?:name|net|n[acefgilopruz])|(?:org|om)|(?:pro|p[aefghklmnrstwy])|qa|r[eouw]|s[abcdeghijklmnortuvyz]|(?:tel|travel|t[cdfghjklmnoprtvwz])|u[agkmsyz]|v[aceginu]|w[fs]|y[etu]|z[amw]))|(?:(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\.(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[0-9])\\.(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[0-9])\\.(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[0-9])))(?:\\:\\d{1,5})?)(?:\\/(?:(?:[!#&-;=?-Z_a-z~])|(?:\\%[a-fA-F0-9]{2}))*)?)(?=\\b|\\s|$)/
accept a registered domain name
not sure about this, what about subdomains
:)
declare
function muURL:hasAcceptableAuthority( $u as xs:string ) as xs:boolean {
let $input :=
try{ muURL:getAuthority( $u )
} catch * {
()
}
let $toplabel := '[a-zA-Z](([a-zA-Z0-9\-])*[a-zA-Z0-9])?'
let $domainlabel := '[a-zA-Z0-9](([a-zA-Z0-9\-])*[a-zA-Z0-9])?'
let $pattern := '^' || $domainlabel || '(\.' || $toplabel || '){1,3}' || '$'
return
if( empty($input) ) then ( false() )
else( matches( $input , $pattern) )
};
declare
function muURL:urlAuthority( $base ) {
let $start := substring-after( $base, '//' )
return
if( matches($start,'^.+/') ) then ( substring-before( $start , '/' ))
else if( matches($start,'^.+\?') ) then ( substring-before( $start , '?' ))
else($start)
};
declare
function muURL:urlAbsolutePath( $base ) {
let $start := substring-after( $base, '//' )
return
if ( matches($start,'^.+\?') ) then (
substring-before(substring-after( $base, '//' || muURL:urlAuthority( $base ) ) , '?')
)
else if ( matches($start,'^.+/') ) then (
substring-after( $base, '//' || muURL:urlAuthority( $base ) )
)
else('/')
};
declare
function muURL:urlQuery( $base ) {
if ( contains( $base , '?') ) then (
substring-after($base, '?')
)
else()
};
(:~
Detirmine if url is relative: A relative reference that does not begin with a scheme name or a slash character is termed a relative-path reference.
@see http://www.ietf.org/rfc/rfc2396.txt
@param $path path component of a URL
@return bool
:)
declare
function muURL:isRelative( $path ) {
not( starts-with( $path ,'/') )
};
(:
if ( starts-with( $relative , '/' ) and matches($base,'^[a-z]+://') ) then resolve-uri( substring-after($relative , '/') , substring-before($base, substring-after(substring-after($base, '://' ), '/') ))
else( )
:)
declare
function muURL:resolve( $base , $relative ) {
let $normalized-url :=
muURL:getScheme( $base ) ||
'://' ||
muURL:getAuthority($base ) ||
muURL:urlAbsolutePath( $base )
return
resolve-uri($relative, $normalized-url)
};
xquery version "3.0";
(:~
This module provides the functions that test my url functions
@author Grant MacKenzie
@version 1.0
:)
module namespace st="http://markup.co.nz/#st";
import module namespace muURL="http://markup.co.nz/#muURL" at "../muURL.xqm";
declare namespace test="http://exist-db.org/xquery/xqsuite";
declare function test:setup(){
()
};
declare function test:testdown(){
()
};
(: HAS NO ILLEGAL CHARS :)
declare
%test:name("given URL ( http://example.com/maori) should be TRUE when we check it has No Illegal Chars")
%test:args("http://example.com/maori")
%test:assertTrue
function st:hasNoIllegalChars_4($url){
muURL:hasNoIllegalChars($url )
};
declare
%test:name("given URL ( http://example.com/${duh!} )should be False when we check it has No Illegal Chars")
%test:args('http://markup.co.nz/{duh!}')
%test:assertFalse
function st:hasNoIllegalChars_2($url){
muURL:hasNoIllegalChars($url )
};
declare
%test:name("given URL ( http://example.com ) with XSS attack, should be FALSE when we check it has No Illegal Chars")
%test:args("http://example.com/page.asp?title=Section%20Title</h1><script>alert(‘XSS%20attack’)</script>")
%test:assertFalse
function st:hasNoIllegalChars_3($url){
muURL:hasNoIllegalChars($url )
};
declare
%test:name("given URL ( http://example.com/māori with codepoint above 127 )should be FALSE when we check if has No Illegal Chars")
%test:args("http://example.com/māori")
%test:assertFalse
function st:hasNoIllegalChars_5($url){
muURL:hasNoIllegalChars($url )
};
declare
%test:name(" given URL (http://markup.co.nz/) should check if can caste anyURL")
%test:args('http://markup.co.nz')
%test:assertTrue
function st:hasNoIllegalChars_1( $url ){
muURL:hasNoIllegalChars( $url )
};
(: CAN CASTE TO anyURL :)
declare
%test:name(" given bad URL (http*://markup.co.nz) should should be true when we check if can caste to anyURL")
%test:args('http*://markup.co.nz')
%test:assertFalse
function st:canCaste_2( $u ){
muURL:canCaste( $u )
};
declare
%test:name(" given good URL (http://markup.co.nz?q=a) should be true when we check if can caste to anyURL")
%test:args('http://markup.co.nz?q=a')
%test:assertTrue
function st:canCaste_3( $u ){
muURL:canCaste( $u )
};
declare
%test:name(" given bad URL (http://markup.co.nz?q=/a|b) should check if can caste anyURL")
%test:args('http://markup.co.nz?q=a|b')
%test:assertTrue
function st:canCaste_4( $u ){
muURL:canCaste( $u )
};
declare
%test:name("given URL ( http://markup.co.nz ) check if meets Acceptable Criteria")
%test:args('http://markup.co.nz')
%test:assertTrue
function st:meetsAcceptableCriteria_1($url){
muURL:meetsAcceptableCriteria($url )
};
(: url safe hash :)
declare
%test:name("given URL get a url safe hash that can stored as a file name")
%test:args('http://markup.co.nz')
%test:assertEquals('QqMFMuqvFRbgiL97n4Km_A')
function st:urlHash($url as xs:anyURI){
muURL:urlHash($url )
};
declare
%test:name("check is 'Base' In Doc given document node with no base element")
%test:args('<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>markup.co.nz</title>
<link href="/resources/styles/style.css" rel="Stylesheet" type="text/css" />
<meta name="author" content="Grant MacKenzie" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script type="text/javascript" src="/resources/scripts/lib/sarissa.js"></script>
<script type="text/javascript" src="/resources/scripts/main.js"></script>
</head>' )
%test:assertFalse
function st:isBaseInDoc_1($node as element()) as xs:boolean{
muURL:isBaseInDoc( $node )
};
declare
%test:name("check is 'Base' In Doc given document node with base element ")
%test:args('<head>
<base href="http://markup.co.nz"/>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>markup.co.nz</title>
<link href="/resources/styles/style.css" rel="Stylesheet" type="text/css" />
<meta name="author" content="Grant MacKenzie" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script type="text/javascript" src="/resources/scripts/lib/sarissa.js"></script>
<script type="text/javascript" src="/resources/scripts/main.js"></script>
</head>' )
%test:assertTrue
function st:isBaseInDoc_2($node as element()) as xs:boolean{
muURL:isBaseInDoc( $node )
};
(: URL PARTS
http://www.ietf.org/rfc/rfc2396.txt
"generic URI" syntax consists of a sequence of four main components:
<scheme>://<authority><path>?<query>
:)
declare
%test:name(" given URL (http://markup.co.nz) should return scheme component 'http'")
%test:args('http://markup.co.nz/archive/2014/03/16/141619')
%test:assertEquals('http')
function st:scheme_1( $u ){
muURL:getScheme( $u )
};
declare
%test:name(" given URL (https://bear.im/) get 'scheme' should return scheme component 'https'")
%test:args('https://bear.im/')
%test:assertEquals('https')
function st:scheme_2( $u ){
muURL:getScheme( $u )
};
declare
%test:name(" given URL (htt#p://markup.co.nz) get scheme should throw ERROR ")
%test:args('htt#p://markup.co.nz')
%test:assertError('muURL:urlHasBadScheme')
function st:scheme_3( $u ){
muURL:getScheme( $u )
};
declare
%test:name(" given URL ( //markup.co.nz ) ) get scheme should throw ERROR ")
%test:args('//markup.co.nz')
%test:assertError('')
function st:scheme_4( $u ){
muURL:getScheme( $u )
};
declare
%test:name(" given URL(http://markup.co.nz) has HTTP Scheme should be true")
%test:args('http://markup.co.nz')
%test:assertTrue
function st:scheme_5( $u ){
muURL:hasHttpScheme( $u )
};
declare
%test:name(" given URL(https://bear.im/) has HTTP Scheme should be true ")
%test:args('https://bear.im/')
%test:assertTrue
function st:scheme_6( $u ){
muURL:hasHttpScheme( $u )
};
declare
%test:name(" given URL( ftp ) has HTTP Scheme should be false" )
%test:args('ftp://markup.co.nz')
%test:assertFalse
function st:scheme_7( $u ){
muURL:hasHttpScheme( $u )
};
declare
%test:name(" given URL (with path and query ) should get 'authority' component ")
%test:args('http://markup.co.nz/webmention?target=http://markup.co.nz')
%test:assertEquals('markup.co.nz')
function st:getAuthority_1($u ){
muURL:getAuthority( $u )
};
declare
%test:name("given URL( http://localhost ) should throw ERROR ")
%test:args('http://localhost')
%test:assertError
function st:getAuthority_2($u ){
muURL:getAuthority( $u )
};
declare
%test:name("given URL ( with / ) should get 'authority' component ")
%test:args('http://markup.co.nz/')
%test:assertEquals('markup.co.nz')
function st:getAuthority_3($base ){
muURL:getAuthority( $base )
};
declare
%test:name(" given URL (scheme + authority only) should get 'authority' component ")
%test:args('http://markup.co.nz')
%test:assertEquals('markup.co.nz')
function st:getAuthority_4($base ){
muURL:getAuthority( $base )
};
declare
%test:name("given URL (scheme + authority + query) should get 'authority' component ")
%test:args('http://markup.co.nz?login=joe')
%test:assertEquals('markup.co.nz')
function st:url-authority_4($base ){
muURL:urlAuthority( $base )
};
declare
%test:name("given 'base url' ( base ends with /, url is absolute) resolve 'url' ")
%test:args('http://waterpigs.co.uk/notes/1083/' ,'/mentions/webmention/' )
%test:assertEquals('http://waterpigs.co.uk/mentions/webmention/')
function st:url-resolve_0($url , $absolute ){
muURL:resolve( $url , $absolute )
};
(::)
declare
%test:name("get 'absolute path' component given base URL (without query)")
%test:args('http://markup.co.nz/archive/2014/03/16/141619')
%test:assertEquals('/archive/2014/03/16/141619')
function st:url-abs_path_1($base ){
muURL:urlAbsolutePath( $base )
};
declare
%test:name("get 'absolute path' component given base URL (with query)")
%test:args('http://markup.co.nz/webmention?target=http://markup.co.nz')
%test:assertEquals('/webmention')
function st:url-abs_path_2($base ){
muURL:urlAbsolutePath( $base )
};
declare
%test:name("get 'absolute path' given base URL (no path)")
%test:args('http://markup.co.nz')
%test:assertEquals('/')
function st:url-abs_path_3($base ){
muURL:urlAbsolutePath( $base )
};
declare
%test:name("get 'query' component given base URL (with query)")
%test:args('http://markup.co.nz/webmention?target=http://markup.co.nz')
%test:assertEquals('target=http://markup.co.nz')
function st:url-query($base ){
muURL:urlQuery( $base )
};
declare
%test:name("check url-is-relative should be false given absolute path ")
%test:args('/archive/2014/03/16/141619')
%test:assertFalse
function st:url-is-relative_1($path ){
muURL:isRelative( $path )
};
declare
%test:name("check url-is-relative should be true given relative path")
%test:args('archive/2014/03/16/141619')
%test:assertTrue
function st:url-is-relative_2($path ){
muURL:isRelative( $path )
};
declare
%test:name("given 'base' resolve 'url' ( base ends with path, url is absolute) ")
%test:args('http://markup.co.nz/archive/2014/03/16/141619' ,'/cards/me' )
%test:assertEquals('http://markup.co.nz/cards/me')
function st:url-resolve_1($base , $relative ){
muURL:resolve( $base , $relative )
};
declare
%test:name("given 'base' resolve 'url' ( base ends with '/' , url is absolute )")
%test:args('http://markup.co.nz/archive/2014/03/16/141619/' ,'/cards/me' )
%test:assertEquals('http://markup.co.nz/cards/me')
function st:url-resolve_2($base , $relative ){
muURL:resolve( $base , $relative )
};
declare
%test:name("given 'base' resolve 'url' ( 'base' ends with 'authority' component, url is 'absolute' )")
%test:args('http://markup.co.nz' ,'/cards/me' )
%test:assertEquals('http://markup.co.nz/cards/me')
function st:url-resolve_3($base , $relative ){
muURL:resolve( $base , $relative )
};
declare
%test:name("given 'base' resolve 'url' ( url has scheme and authority so base is ignored ) ")
%test:args('http://markup.co.nz/archive/2014/03/16/141619' ,'http://markup.co.nz/cards/me' )
%test:assertEquals('http://markup.co.nz/cards/me')
function st:url-resolve_4($base , $relative ){
muURL:resolve( $base , $relative )
};
declare
%test:name("given 'base' resolve 'url' ( base path only a /, url is absolute)" )
%test:args('http://markup.co.nz/' ,'/cards/me' )
%test:assertEquals('http://markup.co.nz/cards/me')
function st:url-resolve_5($base , $relative ){
muURL:resolve( $base , $relative )
};
declare
%test:name("given 'base' resolve 'url' ( base with path, url is relative path with parent steps ../../ ) ")
%test:args('http://markup.co.nz/archive/2014/03/16/141619' ,'../../../../resources/images' )
%test:assertEquals('http://markup.co.nz/resources/images')
function st:url-resolve_6($base , $relative ){
muURL:resolve( $base , $relative )
};
declare
%test:name("given 'base' resolve 'url' ( base with path, url is relative)")
%test:args('http://markup.co.nz/archive/2014/03/16/141619' ,'141620' )
%test:assertEquals('http://markup.co.nz/archive/2014/03/16/141620')
function st:url-resolve_7($base , $relative ){
muURL:resolve( $base , $relative )
};
declare
%test:name("given 'base' resolve 'url' ( base with path, url is relative with current context './' ")
%test:args('http://markup.co.nz/archive/2014/03/16/141619' ,'./141620' )
%test:assertEquals('http://markup.co.nz/archive/2014/03/16/141620')
function st:url-resolve_8($base , $relative ){
muURL:resolve( $base , $relative )
};
declare
%test:name("given 'base' resolve 'url' ( base, url is only current context '.' ")
%test:args('http://markup.co.nz' ,'.' )
%test:assertEquals('http://markup.co.nz/')
function st:url-resolve_8($base , $relative ){
muURL:resolve( $base , $relative )
};
declare
%test:name("given 'base' resolve 'url' ( base no path, url is current context './' with path ")
%test:args('http://markup.co.nz' ,'./cards/me' )
%test:assertEquals('http://markup.co.nz/cards/me')
function st:url-resolve_10($base , $relative ){
muURL:resolve( $base , $relative )
};
declare
%test:name("given 'base' resolve 'url' ( base no path, url is relative ")
%test:args('http://markup.co.nz' ,'cards/me' )
%test:assertEquals('http://markup.co.nz/cards/me')
function st:url-resolve_11($base , $relative ){
muURL:resolve( $base , $relative )
};
(::)
@grantmacken
Copy link
Author

It is a while ago since I created this. I was part of a bigger project working with webmentions
It contain URL related stuff and a xQuery xqsuite test plan for testing the functions.

@joewiz
Copy link

joewiz commented Mar 11, 2016

Wonderful and exemplary work! Thank you for posting it, Grant!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment