jsdav-ext
Version:
jsDAV allows you to easily add WebDAV support to a NodeJS application. jsDAV is meant to cover the entire standard, and attempts to allow integration using an easy to understand API.
262 lines (241 loc) • 11.2 kB
JavaScript
/*
* @package jsDAV
* @subpackage DAV
* @copyright Copyright(c) 2013 Mike de Boer. <info AT mikedeboer DOT nl>
* @author Mike de Boer <info AT mikedeboer DOT nl>
* @license http://github.com/mikedeboer/jsDAV/blob/master/LICENSE MIT License
*/
var Exc = require("./exceptions");
/**
* XML namespace for all jsDAV related elements
*/
exports.NS_AJAXORG = "http://ajax.org/2005/aml";
/**
* This is a default list of namespaces.
*
* If you are defining your own custom namespace, add it here to reduce
* bandwidth and improve legibility of xml bodies.
*
* @var array
*/
exports.xmlNamespaces = {
"DAV:": "d",
"http://ajax.org/2005/aml": "a"
};
/**
* Returns the 'clark notation' for an element.
*
* For example, and element encoded as:
* <b:myelem xmlns:b="http://www.example.org/" />
* will be returned as:
* {http://www.example.org}myelem
*
* This format is used throughout the jsDAV sourcecode.
* Elements encoded with the urn:DAV namespace will
* be returned as if they were in the DAV: namespace. This is to avoid
* compatibility problems.
*
* This function will return null if a nodetype other than an Element is passed.
*
* @param DOMElement dom
* @return string
*/
exports.toClarkNotation = function(dom) {
if (!dom)
return null;
if (!dom.nodeType)
dom = {namespaceURI: dom, localName: arguments[1], nodeType: 1};
if (dom.nodeType !== 1 || !dom.localName)
return null;
// Mapping back to the real namespace, in case it was dav
var ns = dom.namespaceURI == "urn:DAV" ? "DAV:" : dom.namespaceURI;
// Mapping to clark notation
return "{" + ns + "}" + dom.localName.toLowerCase();
};
/**
* This method takes an XML document (as string) and converts all instances of the
* DAV: namespace to urn:DAV
*
* This is unfortunately needed, because the DAV: namespace violates the xml namespaces
* spec, and causes the DOM to throw errors
*/
exports.convertDAVNamespace = function(xmlDocument) {
// This is used to map the DAV: namespace to urn:DAV. This is needed, because the DAV:
// namespace is actually a violation of the XML namespaces specification, and will cause errors
return xmlDocument.replace(/xmlns(:[A-Za-z0-9_]*)?=("|')DAV:("|')/g, "xmlns$1=$2urn:DAV$2");
};
exports.xmlEntityMap = {
"quot": "34", "amp": "38", "apos": "39", "lt": "60", "gt": "62",
"nbsp": "160", "iexcl": "161", "cent": "162", "pound": "163", "curren": "164",
"yen": "165", "brvbar": "166", "sect": "167", "uml": "168", "copy": "169",
"ordf": "170", "laquo": "171", "not": "172", "shy": "173", "reg": "174",
"macr": "175", "deg": "176", "plusmn": "177", "sup2": "178", "sup3": "179",
"acute": "180", "micro": "181", "para": "182", "middot": "183", "cedil": "184",
"sup1": "185", "ordm": "186", "raquo": "187", "frac14": "188", "frac12": "189",
"frac34": "190", "iquest": "191", "agrave": ["192", "224"], "aacute": ["193", "225"],
"acirc": ["194", "226"], "atilde": ["195", "227"], "auml": ["196", "228"],
"aring": ["197", "229"], "aelig": ["198", "230"], "ccedil": ["199", "231"],
"egrave": ["200", "232"], "eacute": ["201", "233"], "ecirc": ["202", "234"],
"euml": ["203", "235"], "igrave": ["204", "236"], "iacute": ["205", "237"],
"icirc": ["206", "238"], "iuml": ["207", "239"], "eth": ["208", "240"],
"ntilde": ["209", "241"], "ograve": ["210", "242"], "oacute": ["211", "243"],
"ocirc": ["212", "244"], "otilde": ["213", "245"], "ouml": ["214", "246"],
"times": "215", "oslash": ["216", "248"], "ugrave": ["217", "249"],
"uacute": ["218", "250"], "ucirc": ["219", "251"], "uuml": ["220", "252"],
"yacute": ["221", "253"], "thorn": ["222", "254"], "szlig": "223", "divide": "247",
"yuml": ["255", "376"], "oelig": ["338", "339"], "scaron": ["352", "353"],
"fnof": "402", "circ": "710", "tilde": "732", "alpha": ["913", "945"],
"beta": ["914", "946"], "gamma": ["915", "947"], "delta": ["916", "948"],
"epsilon": ["917", "949"], "zeta": ["918", "950"], "eta": ["919", "951"],
"theta": ["920", "952"], "iota": ["921", "953"], "kappa": ["922", "954"],
"lambda": ["923", "955"], "mu": ["924", "956"], "nu": ["925", "957"],
"xi": ["926", "958"], "omicron": ["927", "959"], "pi": ["928", "960"],
"rho": ["929", "961"], "sigma": ["931", "963"], "tau": ["932", "964"],
"upsilon": ["933", "965"], "phi": ["934", "966"], "chi": ["935", "967"],
"psi": ["936", "968"], "omega": ["937", "969"], "sigmaf": "962", "thetasym": "977",
"upsih": "978", "piv": "982", "ensp": "8194", "emsp": "8195", "thinsp": "8201",
"zwnj": "8204", "zwj": "8205", "lrm": "8206", "rlm": "8207", "ndash": "8211",
"mdash": "8212", "lsquo": "8216", "rsquo": "8217", "sbquo": "8218", "ldquo": "8220",
"rdquo": "8221", "bdquo": "8222", "dagger": ["8224", "8225"], "bull": "8226",
"hellip": "8230", "permil": "8240", "prime": ["8242", "8243"], "lsaquo": "8249",
"rsaquo": "8250", "oline": "8254", "frasl": "8260", "euro": "8364",
"image": "8465", "weierp": "8472", "real": "8476", "trade": "8482",
"alefsym": "8501", "larr": ["8592", "8656"], "uarr": ["8593", "8657"],
"rarr": ["8594", "8658"], "darr": ["8595", "8659"], "harr": ["8596", "8660"],
"crarr": "8629", "forall": "8704", "part": "8706", "exist": "8707", "empty": "8709",
"nabla": "8711", "isin": "8712", "notin": "8713", "ni": "8715", "prod": "8719",
"sum": "8721", "minus": "8722", "lowast": "8727", "radic": "8730", "prop": "8733",
"infin": "8734", "ang": "8736", "and": "8743", "or": "8744", "cap": "8745",
"cup": "8746", "int": "8747", "there4": "8756", "sim": "8764", "cong": "8773",
"asymp": "8776", "ne": "8800", "equiv": "8801", "le": "8804", "ge": "8805",
"sub": "8834", "sup": "8835", "nsub": "8836", "sube": "8838", "supe": "8839",
"oplus": "8853", "otimes": "8855", "perp": "8869", "sdot": "8901", "lceil": "8968",
"rceil": "8969", "lfloor": "8970", "rfloor": "8971", "lang": "9001", "rang": "9002",
"loz": "9674", "spades": "9824", "clubs": "9827", "hearts": "9829", "diams": "9830"
};
/**
* Escape an xml string making it ascii compatible.
* @param {String} str the xml {String} to escape.
* @return {String} the escaped string.
*/
exports.escapeXml = function(str) {
if (typeof str != "string")
return str;
return (str || "")
.replace(/&/g, "&")
.replace(/"/g, """)
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/'/g, "'")
.replace(/&([a-z]+);/gi, function(a, m) {
var x = exports.xmlEntityMap[m.toLowerCase()];
if (x)
return "&#" + (Array.isArray(x) ? x[0] : x) + ";";
return a;
});
};
/**
* This method provides a generic way to load a DOMDocument for WebDAV use.
*
* This method throws a Exc.BadRequest exception for any xml errors.
* It does not preserve whitespace, and it converts the DAV: namespace to urn:DAV.
*
* @param {String} xml
* @param {String} which 'libxml' or 'xmldom'. Default: 'xmldom'
* @throws Exc.BadRequest
* @return DOMDocument
*/
exports.loadDOMDocument = function(xml, which, callback) {
if (!xml)
return callback(new Exc.BadRequest("Empty XML document sent"));
which = which || "xmldom";
var root;
var parser = which == "xmldom" ? require("xmldom").DOMParser : require("libxml/lib/libxml");
xml = exports.convertDAVNamespace(xml);
try {
if (which = "xmldom") {
function reportError(msg) {
throw new Error(msg);
}
root = new parser({
errorHandler: {
warning: reportError,
error: reportError,
fatalError: reportError
}
}).parseFromString(xml, "text/xml").documentElement;
}
else {
root = exports.xmlParseError(parser.parseFromString(xml).documentElement);
}
}
catch (ex) {
return callback(new Exc.BadRequest("The request body had an invalid XML body. (message: " +
ex.message + ")"));
}
callback(null, root);
};
exports.xmlParseError = function(xml){
//if (xml.documentElement.tagName == "parsererror") {
if (xml.getElementsByTagName("parsererror").length) {
exports.log("ATTENTION::: we actually HAVE an XML error :) ", "warn");
var str = xml.documentElement.firstChild.nodeValue.split("\n"),
linenr = str[2].match(/\w+ (\d+)/)[1],
message = str[0].replace(/\w+ \w+ \w+: (.*)/, "$1"),
srcText = xml.documentElement.lastChild.firstChild.nodeValue;//.split("\n")[0];
throw new Error("XML Parse Error on line " + linenr, message +
"\nSource Text : " + srcText.replace(/\t/gi, " "));
}
return xml;
};
/**
* Parses all WebDAV properties out of a DOM Element
*
* Generally WebDAV properties are encloded in {DAV:}prop elements. This
* method helps by going through all these and pulling out the actual
* propertynames, making them array keys and making the property values,
* well.. the array values.
*
* If no value was given (self-closing element) null will be used as the
* value. This is used in for example PROPFIND requests.
*
* Complex values are supported through the propertyMap argument. The
* propertyMap should have the clark-notation properties as it's keys, and
* classnames as values.
*
* When any of these properties are found, the unserialize() method will be
* (statically) called. The result of this method is used as the value.
*
* @param {DOMElement} parentNode
* @param {Object} propertyMap
* @return array
*/
exports.parseProperties = function(parentNode, propertyMap) {
propertyMap = propertyMap || [];
var propNode, propNodeData, propertyName, j, k, c;
var propList = {};
var childNodes = parentNode.childNodes;
var i = 0;
var l = childNodes.length;
for (; i < l; ++i) {
propNode = childNodes[i];
if (exports.toClarkNotation(propNode) !== "{DAV:}prop")
continue;
for (j = 0, c = propNode.childNodes, k = c.length; j < k; ++j) {
propNodeData = c[j];
// If there are no elements in here, we actually get 1 text node,
// this special case is dedicated to netdrive
if (propNodeData.nodeType != 1) continue;
propertyName = exports.toClarkNotation(propNodeData);
if (propertyMap[propertyName]) {
propList[propertyName] = propertyMap[propertyName].unserialize(propNodeData);
}
else {
propList[propertyName] = propNodeData.firstChild
? propNodeData.firstChild.nodeValue
: propNodeData.nodeValue;
}
}
}
return propList;
};