Permalink
Browse files

renamed error classes to have lesser verbose invocations and 'finishe…

…d' porting CardDAV. Still need to port the VObject parser
  • Loading branch information...
mikedeboer committed Jan 28, 2013
1 parent 3b8b6d5 commit a45a15bf55a2e026246bc8e8e1ab9e68efe83f43
@@ -0,0 +1,122 @@
+/*
+ * @package jsDAV
+ * @subpackage CardDAV
+ * @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
+ */
+"use strict";
+
+var jsDAV_Plugin = require("./../DAV/plugin");
+var jsCardDAV_Plugin = require("./plugin");
+var jsCardDAV_iAddressBook = require("./interfaces/iAddressBook");
+var jsVObject_Reader = require("./../VObject/reader");
+
+var Exc = require("./../shared/exceptions");
+var Util = require("./../shared/util");
+
+var Url = require("url");
+
+/**
+ * VCF Exporter
+ *
+ * This plugin adds the ability to export entire address books as .vcf files.
+ * This is useful for clients that don't support CardDAV yet. They often do
+ * support vcf files.
+ */
+var jsCardDAV_VCFExportPlugin = module.exports = jsDAV_Plugin.extend({
+ /**
+ * Reference to Handler class
+ *
+ * @var jsDAV_Handler
+ */
+ handler: null,
+
+ /**
+ * Initializes the plugin and registers event handlers
+ *
+ * @param jsDAV_Handler handler
+ * @return void
+ */
+ initialize: function(handler) {
+ this.handler = handler;
+ this.handler.addEventListener("beforeMethod", this.beforeMethod.bind(this));
+ },
+
+ /**
+ * 'beforeMethod' event handles. This event handles intercepts GET requests ending
+ * with ?export
+ *
+ * @param string method
+ * @param string uri
+ * @return bool
+ */
+ beforeMethod: function(e, method, uri) {
+ if (method != "GET")
+ return e.next();
+
+ var parsedUrl = Url.parse(this.handler.httpRequest.url, true);
+ if (!("export" in parsedUrl.query))
+ return e.next();
+
+ // splitting uri
+ uri = uri.split("?")[0]
+
+ var self = this;
+ this.handler.getNodeForPath(uri, function(err, node) {
+ if (err)
+ return e.next(err);
+ if (!node.hasFeature(jsCardDAV_iAddressBook))
+ return e.next();
+
+ // Checking ACL, if available.
+ var aclPlugin = self.handler.plugins.acl;
+ if (aclPlugin) {
+ aclPlugin.checkPrivileges(uri, "{DAV:}read", function(err, hasPriv) {
+ if (err)
+ return e.next(err);
+ afterAcl();
+ });
+ }
+ else
+ afterAcl();
+
+ function afterAcl() {
+
+ self.handler.getPropertiesForPath(uri, ["{" + jsCardDAV_Plugin.NS_CARDDAV + "}address-data"], 1, function(err, nodes) {
+ if (err)
+ return e.next(err);
+
+ // e.stop() to break the event chain
+ e.stop();
+ self.handler.httpResponse.writeHead(200, {"content-type": "text/directory"});
+ self.handler.httpResponse.end(self.generateVCF(nodes));
+ });
+ }
+ });
+ },
+
+ /**
+ * Merges all vcard objects, and builds one big vcf export
+ *
+ * @param array nodes
+ * @return string
+ */
+ generateVCF: function(nodes) {
+ var output = [];
+
+ for (var node, nodeData, i = 0, l = nodes.length; i < l; ++i) {
+ node = nodes[i];
+
+ if (node["200"]["{" + jsCardDAV_Plugin.NS_CARDDAV + "}address-data"])
+ continue;
+
+ nodeData = node["200"]["{" + jsCardDAV_Plugin.NS_CARDDAV + "}address-data"];
+
+ // Parsing this node so VObject can clean up the output.
+ output.push(jsVObject_Reader.read(nodeData).serialize());
+ }
+
+ return output.join("");
+ }
+});
@@ -0,0 +1,210 @@
+/*
+ * @package jsDAV
+ * @subpackage CardDAV
+ * @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
+ */
+"use strict";
+
+var Base = require("./../shared/base");
+var Exc = require("./../shared/exceptions");
+var Xml = require("./../shared/xml");
+
+var Xpath = require("xpath");
+
+/**
+ * Parses the addressbook-query report request body.
+ *
+ * Whoever designed this format, and the CalDAV equivalent even more so,
+ * has no feel for design.
+ */
+var jsCardDAV_AddressBookQueryParser = module.exports = Base.extend({
+ TEST_ANYOF: "anyof",
+ TEST_ALLOF: "allof",
+
+ MATCH_TYPES: ["contains", "equals", "starts-with", "ends-with"],
+
+ /**
+ * List of requested properties the client wanted
+ *
+ * @var array
+ */
+ requestedProperties: [],
+
+ /**
+ * The number of results the client wants
+ *
+ * null means it wasn't specified, which in most cases means 'all results'.
+ *
+ * @var int|null
+ */
+ limit: null,
+
+ /**
+ * List of property filters.
+ *
+ * @var array
+ */
+ filters: [],
+
+ /**
+ * Either TEST_ANYOF or TEST_ALLOF
+ *
+ * @var string
+ */
+ test: null,
+
+ /**
+ * DOM Document
+ *
+ * @var DOMDocument
+ */
+ dom: null,
+
+ /**
+ * DOM XPath object
+ *
+ * @var DOMXPath
+ */
+ xpath: null,
+
+ /**
+ * Creates the parser
+ *
+ * @param DOMDocument dom
+ */
+ initialize: function(dom) {
+ this.dom = dom;
+ },
+
+ /**
+ * Parses the request.
+ *
+ * @return void
+ * @throws Exc.BadRequest
+ */
+ parse: function() {
+ var filterNode = null;
+ var limit = parseInt(Xpath.select("number(/card:addressbook-query/card:limit/card:nresults)"), this.dom);
+ if (isNaN(limit))
+ limit = null;
+
+ var test;
+ var filter = Xpath.select("/card:addressbook-query/card:filter", this.dom);
+
+ // According to the CardDAV spec there needs to be exactly 1 filter
+ // element. However, KDE 4.8.2 contains a bug that will encode 0 filter
+ // elements, so this is a workaround for that.
+ //
+ // See: https://bugs.kde.org/show_bug.cgi?id=300047
+ if (filter.length === 0) {
+ test = null;
+ filter = null;
+ }
+ else if (filter.length === 1) {
+ filter = filter.item(0);
+ test = Xpath.select("string(@test)", filter);
+ }
+ else {
+ throw new Exc.BadRequest("Only one filter element is allowed");
+ }
+
+ if (!test)
+ test = this.TEST_ANYOF;
+ if (test !== this.TEST_ANYOF && test !== this.TEST_ALLOF)
+ throw new Exc.BadRequest("The test attribute must either hold 'anyof' or 'allof'");
+
+ var propFilters = [];
+
+ var propFilterNodes = Xpath.select("card:prop-filter", filter);
+ for (var i = 0, l = propFilterNodes.length; i < l; ++i)
+ propFilters.push(this.parsePropFilterNode(propFilterNodes[i]));
+
+ this.filters = propFilters;
+ this.limit = limit;
+ this.requestedProperties = Object.keys(Xml.parseProperties(this.dom.firstChild));
+ this.test = test;
+ },
+
+ /**
+ * Parses the prop-filter xml element
+ *
+ * @param DOMElement propFilterNode
+ * @return array
+ * @throws Exc.BadRequest
+ */
+ parsePropFilterNode: function(propFilterNode) {
+ var propFilter = {
+ name: propFilterNode.getAttribute("name"),
+ test: propFilterNode.getAttribute("test")
+ }
+ if (!propFilter.test)
+ propFilter.test = this.TEST_ANYOF;
+
+ propFilter["is-not-defined"] = Xpath.select("card:is-not-defined", propFilterNode).length > 0;
+ var paramFilterNodes = Xpath.select("card:param-filter", propFilterNode);
+
+ propFilter["param-filters"] = [];
+
+ for (var i = 0, l = paramFilterNodes.length; i < l; ++i)
+ propFilter["param-filters"].push(this.parseParamFilterNode(paramFilterNodes[i]));
+
+ propFilter["text-matches"] = [];
+ var textMatchNodes = Xpath.select("card:text-match", propFilterNode);
+ for (i = 0, l = textMatchNodes.length; i < l; ++i)
+ propFilter["text-matches"].push(this.parseTextMatchNode(textMatchNodes[i]));
+
+ return propFilter;
+ },
+
+ /**
+ * Parses the param-filter element
+ *
+ * @param DOMElement paramFilterNode
+ * @return array
+ * @throws Exc.BadRequest
+ */
+ parseParamFilterNode: function(paramFilterNode) {
+ var paramFilter = {
+ name: paramFilterNode.getAttribute("name"),
+ "is-not-defined": Xpath.select("card:is-not-defined", paramFilterNode).length > 0,
+ "text-match": null
+ };
+
+ var textMatch = Xpath.select("card:text-match", paramFilterNode);
+ if (textMatch.length > 0)
+ paramFilter["text-match"] = this.parseTextMatchNode(textMatch[0]);
+
+ return paramFilter;
+ },
+
+ /**
+ * Text match
+ *
+ * @param DOMElement textMatchNode
+ * @return array
+ * @throws Exc.BadRequest
+ */
+ parseTextMatchNode: function(textMatchNode) {
+ var matchType = textMatchNode.getAttribute("match-type");
+ if (!matchType)
+ matchType = "contains";
+
+ if (this.MATH_TYPES.indexOf(matchType) === -1)
+ throw new Exc.BadRequest("Unknown match-type: " + matchType);
+
+ var negateCondition = textMatchNode.getAttribute("negate-condition");
+ negateCondition = negateCondition == "yes";
+ var collation = textMatchNode.getAttribute("collation");
+ if (!collation)
+ collation = "i;unicode-casemap";
+
+ return {
+ "negate-condition": negateCondition,
+ collation: collation,
+ "match-type": matchType,
+ value: textMatchNode.nodeValue
+ }
+ }
+});
Oops, something went wrong.

0 comments on commit a45a15b

Please sign in to comment.