Index: imaps-frontend/node_modules/relateurl/lib/relate/absolutize.js
===================================================================
--- imaps-frontend/node_modules/relateurl/lib/relate/absolutize.js	(revision 79a03170cdde4e801d12257ee86723774970427b)
+++ imaps-frontend/node_modules/relateurl/lib/relate/absolutize.js	(revision 79a03170cdde4e801d12257ee86723774970427b)
@@ -0,0 +1,89 @@
+"use strict";
+
+var findRelation = require("./findRelation");
+var objUtils     = require("../util/object");
+var pathUtils    = require("../util/path");
+
+
+
+function absolutize(urlObj, siteUrlObj, options)
+{
+	findRelation.upToPath(urlObj, siteUrlObj, options);
+	
+	// Fill in relative URLs
+	if (urlObj.extra.relation.minimumScheme) urlObj.scheme = siteUrlObj.scheme;
+	if (urlObj.extra.relation.minimumAuth)   urlObj.auth   = siteUrlObj.auth;
+	if (urlObj.extra.relation.minimumHost)   urlObj.host   = objUtils.clone(siteUrlObj.host);
+	if (urlObj.extra.relation.minimumPort)   copyPort(urlObj, siteUrlObj);
+	if (urlObj.extra.relation.minimumScheme) copyPath(urlObj, siteUrlObj);
+	
+	// Check remaining relativeness now that path has been copied and/or resolved
+	findRelation.pathOn(urlObj, siteUrlObj, options);
+	
+	// Fill in relative URLs
+	if (urlObj.extra.relation.minimumResource) copyResource(urlObj, siteUrlObj);
+	if (urlObj.extra.relation.minimumQuery)    urlObj.query = objUtils.clone(siteUrlObj.query);
+	if (urlObj.extra.relation.minimumHash)     urlObj.hash  = siteUrlObj.hash;
+}
+
+
+
+/*
+	Get an absolute path that's relative to site url.
+*/
+function copyPath(urlObj, siteUrlObj)
+{
+	if (urlObj.extra.relation.maximumHost || !urlObj.extra.hrefInfo.minimumResourceOnly)
+	{
+		var pathArray = urlObj.path.absolute.array;
+		var pathString = "/";
+		
+		// If not erroneous URL
+		if (pathArray)
+		{
+			// If is relative path
+			if (urlObj.extra.hrefInfo.minimumPathOnly && urlObj.path.absolute.string.indexOf("/")!==0)
+			{
+				// Append path to site path
+				pathArray = siteUrlObj.path.absolute.array.concat(pathArray);
+			}
+			
+			pathArray   = pathUtils.resolveDotSegments(pathArray);
+			pathString += pathUtils.join(pathArray);
+		}
+		else
+		{
+			pathArray = [];
+		}
+		
+		urlObj.path.absolute.array  = pathArray;
+		urlObj.path.absolute.string = pathString;
+	}
+	else
+	{
+		// Resource-, query- or hash-only or empty
+		urlObj.path = objUtils.clone(siteUrlObj.path);
+	}
+}
+
+
+
+function copyPort(urlObj, siteUrlObj)
+{
+	urlObj.port = siteUrlObj.port;
+	
+	urlObj.extra.portIsDefault = siteUrlObj.extra.portIsDefault;
+}
+
+
+
+function copyResource(urlObj, siteUrlObj)
+{
+	urlObj.resource = siteUrlObj.resource;
+	
+	urlObj.extra.resourceIsIndex = siteUrlObj.extra.resourceIsIndex;
+}
+
+
+
+module.exports = absolutize;
Index: imaps-frontend/node_modules/relateurl/lib/relate/findRelation.js
===================================================================
--- imaps-frontend/node_modules/relateurl/lib/relate/findRelation.js	(revision 79a03170cdde4e801d12257ee86723774970427b)
+++ imaps-frontend/node_modules/relateurl/lib/relate/findRelation.js	(revision 79a03170cdde4e801d12257ee86723774970427b)
@@ -0,0 +1,79 @@
+"use strict";
+
+function findRelation_upToPath(urlObj, siteUrlObj, options)
+{
+	// Path- or root-relative URL
+	var pathOnly = urlObj.extra.hrefInfo.minimumPathOnly;
+	
+	// Matching scheme, scheme-relative or path-only
+	var minimumScheme = (urlObj.scheme===siteUrlObj.scheme || !urlObj.scheme);
+	
+	// Matching auth, ignoring auth or path-only
+	var minimumAuth = minimumScheme && (urlObj.auth===siteUrlObj.auth || options.removeAuth || pathOnly);
+	
+	// Matching host or path-only
+	var www = options.ignore_www ? "stripped" : "full";
+	var minimumHost = minimumAuth && (urlObj.host[www]===siteUrlObj.host[www] || pathOnly);
+	
+	// Matching port or path-only
+	var minimumPort = minimumHost && (urlObj.port===siteUrlObj.port || pathOnly);
+	
+	urlObj.extra.relation.minimumScheme = minimumScheme;
+	urlObj.extra.relation.minimumAuth   = minimumAuth;
+	urlObj.extra.relation.minimumHost   = minimumHost;
+	urlObj.extra.relation.minimumPort   = minimumPort;
+	
+	urlObj.extra.relation.maximumScheme = !minimumScheme || minimumScheme && !minimumAuth;
+	urlObj.extra.relation.maximumAuth   = !minimumScheme || minimumScheme && !minimumHost;
+	urlObj.extra.relation.maximumHost   = !minimumScheme || minimumScheme && !minimumPort;
+}
+
+
+
+function findRelation_pathOn(urlObj, siteUrlObj, options)
+{
+	var queryOnly = urlObj.extra.hrefInfo.minimumQueryOnly;
+	var hashOnly  = urlObj.extra.hrefInfo.minimumHashOnly;
+	var empty     = urlObj.extra.hrefInfo.empty;	// not required, but self-documenting
+	
+	// From upToPath()
+	var minimumPort   = urlObj.extra.relation.minimumPort;
+	var minimumScheme = urlObj.extra.relation.minimumScheme;
+	
+	// Matching port and path
+	var minimumPath = minimumPort && urlObj.path.absolute.string===siteUrlObj.path.absolute.string;
+	
+	// Matching resource or query/hash-only or empty
+	var matchingResource = (urlObj.resource===siteUrlObj.resource || !urlObj.resource && siteUrlObj.extra.resourceIsIndex) || (options.removeDirectoryIndexes && urlObj.extra.resourceIsIndex && !siteUrlObj.resource);
+	var minimumResource = minimumPath && (matchingResource || queryOnly || hashOnly || empty);
+	
+	// Matching query or hash-only/empty
+	var query = options.removeEmptyQueries ? "stripped" : "full";
+	var urlQuery = urlObj.query.string[query];
+	var siteUrlQuery = siteUrlObj.query.string[query];
+	var minimumQuery = (minimumResource && !!urlQuery && urlQuery===siteUrlQuery) || ((hashOnly || empty) && !urlObj.extra.hrefInfo.separatorOnlyQuery);
+	
+	var minimumHash = minimumQuery && urlObj.hash===siteUrlObj.hash;
+	
+	urlObj.extra.relation.minimumPath     = minimumPath;
+	urlObj.extra.relation.minimumResource = minimumResource;
+	urlObj.extra.relation.minimumQuery    = minimumQuery;
+	urlObj.extra.relation.minimumHash     = minimumHash;
+	
+	urlObj.extra.relation.maximumPort     = !minimumScheme || minimumScheme && !minimumPath;
+	urlObj.extra.relation.maximumPath     = !minimumScheme || minimumScheme && !minimumResource;
+	urlObj.extra.relation.maximumResource = !minimumScheme || minimumScheme && !minimumQuery;
+	urlObj.extra.relation.maximumQuery    = !minimumScheme || minimumScheme && !minimumHash;
+	urlObj.extra.relation.maximumHash     = !minimumScheme || minimumScheme && !minimumHash;	// there's nothing after hash, so it's the same as maximumQuery
+	
+	// Matching path and/or resource with existing but non-matching site query
+	urlObj.extra.relation.overridesQuery  = minimumPath && urlObj.extra.relation.maximumResource && !minimumQuery && !!siteUrlQuery;
+}
+
+
+
+module.exports =
+{
+	pathOn:   findRelation_pathOn,
+	upToPath: findRelation_upToPath
+};
Index: imaps-frontend/node_modules/relateurl/lib/relate/index.js
===================================================================
--- imaps-frontend/node_modules/relateurl/lib/relate/index.js	(revision 79a03170cdde4e801d12257ee86723774970427b)
+++ imaps-frontend/node_modules/relateurl/lib/relate/index.js	(revision 79a03170cdde4e801d12257ee86723774970427b)
@@ -0,0 +1,18 @@
+"use strict";
+
+var absolutize = require("./absolutize");
+var relativize = require("./relativize");
+
+
+
+function relateUrl(siteUrlObj, urlObj, options)
+{
+	absolutize(urlObj, siteUrlObj, options);
+	relativize(urlObj, siteUrlObj, options);
+	
+	return urlObj;
+}
+
+
+
+module.exports = relateUrl;
Index: imaps-frontend/node_modules/relateurl/lib/relate/relativize.js
===================================================================
--- imaps-frontend/node_modules/relateurl/lib/relate/relativize.js	(revision 79a03170cdde4e801d12257ee86723774970427b)
+++ imaps-frontend/node_modules/relateurl/lib/relate/relativize.js	(revision 79a03170cdde4e801d12257ee86723774970427b)
@@ -0,0 +1,67 @@
+"use strict";
+
+var pathUtils = require("../util/path");
+
+
+
+/*
+	Get a path relative to the site path.
+*/
+function relatePath(absolutePath, siteAbsolutePath)
+{
+	var relativePath = [];
+	
+	// At this point, it's related to the host/port
+	var related = true;
+	var parentIndex = -1;
+	
+	// Find parents
+	siteAbsolutePath.forEach( function(siteAbsoluteDir, i)
+	{
+		if (related)
+		{
+			if (absolutePath[i] !== siteAbsoluteDir)
+			{
+				related = false;
+			}
+			else
+			{
+				parentIndex = i;
+			}
+		}
+		
+		if (!related)
+		{
+			// Up one level
+			relativePath.push("..");
+		}
+	});
+	
+	// Form path
+	absolutePath.forEach( function(dir, i)
+	{
+		if (i > parentIndex)
+		{
+			relativePath.push(dir);
+		}
+	});
+	
+	return relativePath;
+}
+
+
+
+function relativize(urlObj, siteUrlObj, options)
+{
+	if (urlObj.extra.relation.minimumScheme)
+	{
+		var pathArray = relatePath(urlObj.path.absolute.array, siteUrlObj.path.absolute.array);
+		
+		urlObj.path.relative.array  = pathArray;
+		urlObj.path.relative.string = pathUtils.join(pathArray);
+	}
+}
+
+
+
+module.exports = relativize;
