Skip to content
Browse files

fixed Depth header reading, various integration bugs with CardDAV and…

… the redis backend, auth plugin error flow handling through asyncEvents
  • Loading branch information...
1 parent 0eb98b5 commit c0eeb29bc7d96fac34a3874b7dfecf8719a80785 @mikedeboer committed Feb 14, 2013
View
4 lib/CardDAV/addressBook.js
@@ -111,7 +111,9 @@ var jsCardDAV_AddressBook = module.exports = jsDAV_Collection.extend(jsCardDAV_i
* @param resource vcardData
* @return string|null
*/
- createFile: function(name, vcardData, callback) {
+ createFile: function(name, vcardData, enc, callback) {
+ if (Buffer.isBuffer(vcardData))
+ vcardData = vcardData.toString("utf8");
this.carddavBackend.createCard(this.addressBookInfo.id, name, vcardData, callback);
},
View
27 lib/CardDAV/backends/redis.js
@@ -299,15 +299,18 @@ var jsCardDAV_Backend_Redis = module.exports = jsCardDAV_iBackend.extend({
self.redis.multi(commands).exec(function(err, res) {
if (err)
return callback(err);
- if (!res)
+ if (!res || !res.length)
return callback(null, []);
- var cards = Db.fromMultiBulk(res).map(function(data, idx) {
- return {
+ var cards = [];
+ Db.fromMultiBulk(res).map(function(data, idx) {
+ if (!data[0] && !data[1])
+ return;
+ cards.push({
uri: cardUris[idx],
carddata: data[0],
lastmodified: data[1]
- };
+ });
});
callback(null, cards);
});
@@ -330,16 +333,14 @@ var jsCardDAV_Backend_Redis = module.exports = jsCardDAV_iBackend.extend({
return callback(err);
res = Db.fromMultiBulk(res);
- if (!res || !res.length)
+ if (!res || !res.length || (!res[0] && !res[1]))
return callback();
- callback(null, res && res.length
- ? {
- uri: cardUri,
- carddata: res[0],
- lastmodified: res[1]
- }
- : false
- );
+
+ callback(null, {
+ uri: cardUri,
+ carddata: res[0],
+ lastmodified: res[1]
+ });
});
},
View
5 lib/CardDAV/card.js
@@ -77,7 +77,7 @@ var jsCardDAV_Card = module.exports = jsDAV_File.extend(jsCardDAV_iCard, jsDAVAC
if (err)
return callback(err);
- self.cardData = cardData;
+ self.cardData.carddata = cardData;
callback(null, self.cardData.carddata);
});
},
@@ -130,7 +130,8 @@ var jsCardDAV_Card = module.exports = jsDAV_File.extend(jsCardDAV_iCard, jsDAVAC
this.get(function(err, data) {
if (err)
return callback(err);
- callback(null, '"' + Util.md5(data.carddata) + '"');
+ console.log("RECEIVED CARD FROM DB: ",data);
+ callback(null, '"' + Util.md5(data) + '"');
});
},
View
2 lib/CardDAV/plugin.js
@@ -365,7 +365,7 @@ var jsCardDAV_Plugin = module.exports = jsDAV_Plugin.extend({
* @param jsDAV_iCollection parentNode
* @return void
*/
- beforeCreateFile: function(e, path, data, parentNode) {
+ beforeCreateFile: function(e, path, data, enc, parentNode) {
if (!parentNode.hasFeature(jsCardDAV_iAddressBook))
return e.next();
View
55 lib/DAV/handler.js
@@ -826,6 +826,34 @@ jsDAV_Handler.STATUS_MAP = {
this.httpPut = function() {
var self = this;
var uri = this.getRequestUri();
+
+ // Intercepting Content-Range
+ if (this.httpRequest.headers["content-range"]) {
+ /*
+ Content-Range is dangerous for PUT requests: PUT per definition
+ stores a full resource. draft-ietf-httpbis-p2-semantics-15 says
+ in section 7.6:
+ An origin server SHOULD reject any PUT request that contains a
+ Content-Range header field, since it might be misinterpreted as
+ partial content (or might be partial content that is being mistakenly
+ PUT as a full representation). Partial content updates are possible
+ by targeting a separately identified resource with state that
+ overlaps a portion of the larger resource, or by using a different
+ method that has been specifically defined for partial updates (for
+ example, the PATCH method defined in [RFC5789]).
+ This clarifies RFC2616 section 9.6:
+ The recipient of the entity MUST NOT ignore any Content-*
+ (e.g. Content-Range) headers that it does not understand or implement
+ and MUST return a 501 (Not Implemented) response in such cases.
+ OTOH is a PUT request with a Content-Range currently the only way to
+ continue an aborted upload request and is supported by curl, mod_dav,
+ Tomcat and others. Since some clients do use this feature which results
+ in unexpected behaviour (cf PEAR::HTTP_WebDAV_Client 1.0.1), we reject
+ all PUT requests with a Content-Range for now.
+ */
+
+ return this.handleError(new Exc.NotImplemented("PUT with Content-Range is not allowed."));
+ }
// First we'll do a check to see if the resource already exists
this.getNodeForPath(uri, function(err, node) {
@@ -1630,7 +1658,6 @@ jsDAV_Handler.STATUS_MAP = {
if (self.plugins[pluginName].getSupportedReportSet)
reportPlugins.push(self.plugins[pluginName]);
});
-
this.getNodeForPath(path, function(err, parentNode) {
if (!Util.empty(err))
return cbgetpropspath(err);
@@ -1642,7 +1669,7 @@ jsDAV_Handler.STATUS_MAP = {
//if (jsDAV.debugMode)
// console.log("getPropertiesForPath", depth, parentNode,parentNode.hasFeature(jsDAV_iCollection));
- if (depth == 1 && parentNode.hasFeature(jsDAV_iCollection)) {
+ if (depth === 1 && parentNode.hasFeature(jsDAV_iCollection)) {
parentNode.getChildren(function(err, cNodes) {
if (!Util.empty(err))
return cbgetpropspath(err);
@@ -1912,11 +1939,12 @@ jsDAV_Handler.STATUS_MAP = {
if (depth == "infinity")
return jsDAV_Handler.DEPTH_INFINITY;
+ depth = parseInt(depth, 10);
// If its an unknown value. we'll grab the default
- if (typeof depth != "number")
+ if (isNaN(depth))
return def;
- return parseInt(depth, 10);
+ return depth;
};
/**
@@ -2178,7 +2206,7 @@ jsDAV_Handler.STATUS_MAP = {
self.getNodeForPath(dir, function(err, parent) {
if (!Util.empty(err))
return cbcreatefile(err);
- self.dispatchEvent("beforeCreateFile", uri, dataOrStream, enc, function(stop) {
+ self.dispatchEvent("beforeCreateFile", uri, dataOrStream, enc, parent, function(stop) {
if (stop === true)
return cbcreatefile();
@@ -2200,6 +2228,8 @@ jsDAV_Handler.STATUS_MAP = {
function afterBind(err) {
if (!Util.empty(err))
return cbcreatefile(err);
+
+ self.server.tree.markDirty(dir + "/" + name);
self.dispatchEvent("afterBind", uri, Path.join(self.server.tree.basePath, uri));
cbcreatefile();
}
@@ -2291,14 +2321,11 @@ jsDAV_Handler.STATUS_MAP = {
self.updateProperties(uri, properties, function(err, errorResult) {
if (err || !errorResult["200"].length)
return rollback(err, errorResult);
- self.dispatchEvent("afterBind", uri, Path.join(parent.path, newName));
- cbcreatecoll();
+ onDone();
});
}
- else {
- self.dispatchEvent("afterBind", uri, Path.join(parent.path, newName));
- cbcreatecoll();
- }
+ else
+ onDone();
function rollback(exc, res) {
self.server.tree.getNodeForPath(uri, function(err, node) {
@@ -2313,6 +2340,12 @@ jsDAV_Handler.STATUS_MAP = {
});
});
}
+
+ function onDone() {
+ self.server.tree.markDirty(parentUri);
+ self.dispatchEvent("afterBind", uri, Path.join(parent.path, newName));
+ cbcreatecoll();
+ }
});
}
});
View
108 lib/DAV/objectTree.js
@@ -23,6 +23,9 @@ var Exc = require("./../shared/exceptions");
var jsDAV_ObjectTree = module.exports = jsDAV_Tree.extend({
initialize: function(rootNode) {
this.rootNode = rootNode;
+ // we keep a cache dual to the one found in jsDAV_Handler, because of
+ // possibly heavy-duty object-store (database) ops internally.
+ this.cache = {};
},
/**
@@ -34,29 +37,90 @@ var jsDAV_ObjectTree = module.exports = jsDAV_Tree.extend({
getNodeForPath: function(path, cbgetnodepath) {
path = Util.trim(path, "/");
- //if (!path || path=='.') return this.rootNode;
- var currentNode = this.rootNode;
- var c = path.split("/");
- // We're splitting up the path variable into folder/subfolder components
- // and traverse to the correct node..
- Async.list(c)
- .each(function(pathPart, cbnextnpath) {
- // If this part of the path is just a dot, it actually means we can skip it
- if (pathPart == "." || pathPart === "")
- if (!currentNode || !currentNode.hasFeature(jsDAV_iCollection))
- return cbnextnpath(new Exc.FileNotFound("Could not find node at path: " + path));
+ // Did we cache it earlier?
+ if (this.cache[path])
+ return cbgetnodepath(null, this.cache[path]);
+ // Is it the root node?
+ if (!path || !path.length)
+ return cbgetnodepath(null, this.rootNode);
+
+ // Attempting to fetch its parent
+ var parts = Util.splitPath(path);
+ var parentName = parts[0];
+ var baseName = parts[1];
+ var self = this;
- currentNode.getChild(pathPart, function(err, crt) {
- if (err)
- return cbnextnpath(err);
- currentNode = crt;
- cbnextnpath();
+ // If there was no parent, we must simply ask it from the root node.
+ if (parentName === "") {
+ this.rootNode.getChild(baseName, afterGetChild);
+ }
+ else {
+ // Otherwise, we recursively grab the parent and ask him/her.
+ this.getNodeForPath(parentName, function(err, parent) {
+ if (!parent || !parent.hasFeature(jsDAV_iCollection))
+ return cbgetnodepath(new Exc.NotFound("Could not find node at path: " + path));
+
+ parent.getChild(baseName, afterGetChild);
+ });
+ }
+
+ function afterGetChild(err, node) {
+ if (err)
+ return cbgetnodepath(err);
+ self.cache[path] = node;
+ cbgetnodepath(null, node);
+ }
+ },
+
+ /**
+ * Returns a list of childnodes for a given path.
+ *
+ * @param string path
+ * @return array
+ */
+ getChildren: function(path, cbgetchildren) {
+ var self = this;
+ this.getNodeForPath(path, function(err, node) {
+ if (err)
+ return cbgetchildren(err);
+
+ node.getChildren(function(err, children) {
+ if (err)
+ return cbgetchildren(err);
+
+ children.forEach(function(child) {
+ self.cache[Util.trim(path, "/") + "/" + child.getName()] = child;
});
- })
- .end(function(err) {
- if (err)
- return cbgetnodepath(err);
- cbgetnodepath(null, currentNode);
- });
+ cbgetchildren(null, children);
+ });
+ });
+ },
+
+ /**
+ * This method is called with every tree update
+ *
+ * Examples of tree updates are:
+ * * node deletions
+ * * node creations
+ * * copy
+ * * move
+ * * renaming nodes
+ *
+ * If Tree classes implement a form of caching, this will allow
+ * them to make sure caches will be expired.
+ *
+ * If a path is passed, it is assumed that the entire subtree is dirty
+ *
+ * @param string path
+ * @return void
+ */
+ markDirty: function(path) {
+ // We don't care enough about sub-paths
+ // flushing the entire cache
+ path = Util.trim(path, "/");
+ for (var nodePath in this.cache) {
+ if (nodePath.indexOf(path) === 0)
+ delete this.cache[nodePath];
+ }
}
});
View
2 lib/DAV/plugins/auth.js
@@ -76,7 +76,7 @@ var jsDAV_Auth_Plugin = module.exports = jsDAV_ServerPlugin.extend({
this.authBackend.authenticate(this.handler, this.realm, function(err, res) {
if (err || !res) {
//do something with response here...
- return e.stop();
+ return err ? e.next(err) : e.stop();
}
e.next();
});
View
17 lib/DAV/plugins/auth/abstractBasic.js
@@ -9,6 +9,8 @@
var jsDAV_Auth_iBackend = require("./iBackend");
+var Exc = require("./../../../shared/exceptions");
+
/**
* HTTP Basic authentication backend class
*
@@ -52,10 +54,11 @@ var jsDAV_Auth_Backend_AbstractBasic = module.exports = jsDAV_Auth_iBackend.exte
*
* @return void
*/
- requireAuth: function(res, realm, err, callback) {
- res.writeHead(401, {"WWW-Authenticate": "Basic realm=\"" + realm + "\""});
- res.end();
- callback(err);
+ requireAuth: function(realm, err, callback) {
+ if (!(err instanceof Exc.jsDAV_Exception))
+ err = new Exc.NotAuthenticated(err);
+ err.addHeader("WWW-Authenticate", "Basic realm=\"" + realm + "\"");
+ callback(err, false);
},
/**
@@ -73,17 +76,17 @@ var jsDAV_Auth_Backend_AbstractBasic = module.exports = jsDAV_Auth_iBackend.exte
var auth = req.headers["authorization"];
if (!auth || auth.toLowerCase().indexOf("basic") !== 0)
- return this.requireAuth(res, realm, "No basic authentication headers were found", cbauth);
+ return this.requireAuth(realm, "No basic authentication headers were found", cbauth);
var userpass = (new Buffer(auth.substr(6), "base64")).toString("utf8").split(":");
if (!userpass.length)
- return this.requireAuth(res, realm, "No basic authentication headers were found", cbauth);
+ return this.requireAuth(realm, "No basic authentication headers were found", cbauth);
// Authenticates the user
var self = this;
this.validateUserPass(userpass[0], userpass[1], function(valid) {
if (!valid)
- return self.requireAuth(res, realm, "Username or password does not match", cbauth);
+ return self.requireAuth(realm, "Username or password does not match", cbauth);
self.currentUser = userpass[0];
cbauth(null, true);
View
27 lib/DAV/plugins/auth/abstractDigest.js
@@ -9,8 +9,8 @@
var jsDAV_Auth_iBackend = require("./iBackend");
-var Exc = require("./../../../shared/exceptions");
-var Util = require("./../../../shared/util");
+var Exc = require("./../../../shared/exceptions");
+var Util = require("./../../../shared/util");
/**
* These constants are used in setQOP();
@@ -206,7 +206,10 @@ var jsDAV_Auth_Backend_AbstractDigest = module.exports = jsDAV_Auth_iBackend.ext
*
* @return void
*/
- requireAuth: function(res, realm, err, cbreqauth) {
+ requireAuth: function(realm, err, cbreqauth) {
+ if (!(err instanceof Exc.jsDAV_Exception))
+ err = new Exc.NotAuthenticated(err);
+
var currQop = "";
switch (this.qop) {
case QOP_AUTH:
@@ -220,13 +223,10 @@ var jsDAV_Auth_Backend_AbstractDigest = module.exports = jsDAV_Auth_iBackend.ext
break;
}
- res.writeHead(401, {
- "WWW-Authenticate": "Digest realm=\"" + realm + "\",qop=\"" + currQop
- + "\",nonce=\"" + this.nonce + "\",opaque=\""
- + this.opaque + "\""
- });
- res.end();
- cbreqauth && cbreqauth(null, false);
+ err.addHeader("WWW-Authenticate", "Digest realm=\"" + realm +
+ "\",qop=\"" + currQop + "\",nonce=\"" + this.nonce +
+ "\",opaque=\"" + this.opaque + "\"");
+ cbreqauth(err, false);
},
/**
@@ -240,19 +240,18 @@ var jsDAV_Auth_Backend_AbstractDigest = module.exports = jsDAV_Auth_iBackend.ext
*/
authenticate: function(handler, realm, cbauth) {
var req = handler.httpRequest;
- var res = handler.httpResponse;
this.init(realm, req);
var username = this.digestParts["username"];
// No username was given
if (!username)
- return this.requireAuth(res, realm, "No digest authentication headers were found", cbauth);
+ return this.requireAuth(realm, "No digest authentication headers were found", cbauth);
var self = this;
this.getDigestHash(realm, username, function(err, hash) {
// If this was false, the user account didn't exist
if (err || !hash)
- return self.requireAuth(res, realm, err || "The supplied username was not on file", cbauth);
+ return self.requireAuth(realm, err || "The supplied username was not on file", cbauth);
if (typeof hash != "string") {
handler.handleError(new Exc.jsDAV_Exception(
@@ -263,7 +262,7 @@ var jsDAV_Auth_Backend_AbstractDigest = module.exports = jsDAV_Auth_iBackend.ext
// If this was false, the password or part of the hash was incorrect.
self.validateA1(handler, hash, function(isValid) {
if (!isValid)
- return self.requireAuth(res, realm, "Incorrect username", cbauth);
+ return self.requireAuth(realm, "Incorrect username", cbauth);
self.currentUser = username;
cbauth(null, true);
View
2 lib/DAV/plugins/browser.js
@@ -269,7 +269,7 @@ var jsDAV_Browser_Plugin = module.exports = jsDAV_ServerPlugin.extend({
// This is the current directory, we can skip it
if (Util.rtrim(file["href"], "/") == path)
return next();
-
+
name = encodeURI(Util.splitPath(file["href"])[1] || "");
type = null;
View
74 lib/DAV/tree.js
@@ -106,7 +106,12 @@ var jsDAV_Tree = module.exports = Base.extend({
self.getNodeForPath(destinationDir, function(err, destinationParent) {
if (err)
return cbcopytree(err);
- self.copyNode(sourceNode, destinationParent, destinationName, cbcopytree);
+ self.copyNode(sourceNode, destinationParent, destinationName, function(err) {
+ if (err)
+ return cbcopytree(err);
+ self.markDirty(destinationDir);
+ cbcopytree();
+ });
});
});
},
@@ -134,18 +139,81 @@ var jsDAV_Tree = module.exports = Base.extend({
this.getNodeForPath(sourcePath, function(err, renameable) {
if (err)
return cbmovetree(err);
- renameable.setName(destinationName, cbmovetree);
+ renameable.setName(destinationName, onDone);
});
}
else {
var self = this;
this.copy(sourcePath, destinationPath, function(err) {
if (err)
return cbmovetree(err);
- self.getNodeForPath(sourcePath)["delete"](cbmovetree);
+ self.getNodeForPath(sourcePath)["delete"](onDone);
});
}
+
+ function onDone(err) {
+ if (err)
+ return cbmovetree(err);
+ self.markDirty(sourceDir);
+ self.markDirty(destinationDir);
+ cbmovetree();
+ }
+ },
+
+ /**
+ * Deletes a node from the tree
+ *
+ * @param string path
+ * @return void
+ */
+ "delete": function(path, cbtreedelete) {
+ var self = this;
+ this.getNodeForPath(path, function(err, node) {
+ if (err)
+ return cbtreedelete(err);
+
+ node.delete(function(err) {
+ if (err)
+ return cbtreedelete(err);
+ self.markDirty(Util.splitPath(path)[0]);
+ cbtreedelete();
+ });
+ });
},
+
+ /**
+ * Returns a list of childnodes for a given path.
+ *
+ * @param string path
+ * @return array
+ */
+ getChildren: function(path, cbtreechildren) {
+ this.getNodeForPath(path, function(err, node) {
+ if (err)
+ return cbtreechildren(err);
+ node.getChildren(cbtreechildren);
+ });
+ },
+
+ /**
+ * This method is called with every tree update
+ *
+ * Examples of tree updates are:
+ * * node deletions
+ * * node creations
+ * * copy
+ * * move
+ * * renaming nodes
+ *
+ * If Tree classes implement a form of caching, this will allow
+ * them to make sure caches will be expired.
+ *
+ * If a path is passed, it is assumed that the entire subtree is dirty
+ *
+ * @param string path
+ * @return void
+ */
+ markDirty: function(path) {},
/**
* copyNode
View
5 lib/DAVACL/backends/redis.js
@@ -120,13 +120,12 @@ var jsDAVACL_Backend_Redis = module.exports = jsDAVACL_iBackend.extend({
self.redis.multi(commands).exec(function(err, res) {
if (err)
return callback(err);
-
var strip = (self.tableName + "/").length
var principals = Db.fromMultiBulk(res).map(function(row, idx) {
var obj = {
uri: keys[idx].substr(strip)
};
- for (var i = 1, l = fields.length; i < l; ++i) {
+ for (var i = 0, l = fields.length; i < l; ++i) {
if (!row[i])
continue;
// put it back like 'obj["{DAV:}displayname"] = val'
@@ -163,7 +162,7 @@ var jsDAVACL_Backend_Redis = module.exports = jsDAVACL_iBackend.extend({
var principal = {
uri: path
};
- for (var i = 1, l = fields.length; i < l; ++i) {
+ for (var i = 0, l = fields.length; i < l; ++i) {
if (!res[i])
continue;
// put it back like 'principal["{DAV:}displayname"] = val'
View
4 lib/DAVACL/principal.js
@@ -48,8 +48,8 @@ var jsDAVACL_Principal = module.exports = jsDAV_Node.extend(jsDAVACL_iPrincipal,
* @param array principalProperties
*/
initialize: function(principalBackend, principalProperties) {
- if (!principalProperties.uri){console.trace();
- throw new Exc.jsDAV_Exception("The principal properties must at least contain the \"uri\" key");}
+ if (!principalProperties.uri)
+ throw new Exc.jsDAV_Exception("The principal properties must at least contain the \"uri\" key");
this.principalBackend = principalBackend;
this.principalProperties = principalProperties;
View
9 lib/shared/exceptions.js
@@ -325,6 +325,15 @@ exports.NotAuthenticated = function(msg, extra) {
this.code = 401;
this.type = "NotAuthenticated";
this.message = msg || this.type;
+ this.headers = {};
+
+ this.addHeader = function(name, value) {
+ this.headers[name] = value;
+ };
+
+ this.getHTTPHeaders = function(handler, cbheaders) {
+ cbheaders(null, this.headers);
+ };
};
exports.NotAuthenticated.prototype = new exports.jsDAV_Exception();

0 comments on commit c0eeb29

Please sign in to comment.
Something went wrong with that request. Please try again.