diff options
author | James Lal <james@lightsofapollo.com> | 2012-06-18 20:51:13 -0700 |
---|---|---|
committer | James Lal <james@lightsofapollo.com> | 2012-06-18 20:51:13 -0700 |
commit | a6c747412c0960331e4055eee97d8328ff88d584 (patch) | |
tree | c4a82365e592a2d1cfa46cd0f5f595dde6626ff8 | |
download | jsCalDAV-a6c747412c0960331e4055eee97d8328ff88d584.tar.gz |
Initial hack
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | Makefile | 19 | ||||
-rw-r--r-- | lib/caldav/caldav.js | 101 | ||||
-rw-r--r-- | lib/caldav/ics.js | 140 | ||||
-rw-r--r-- | lib/caldav/responder.js | 209 | ||||
-rw-r--r-- | lib/caldav/sax.js | 158 | ||||
-rw-r--r-- | lib/caldav/sax/propstat.js | 30 | ||||
-rw-r--r-- | lib/caldav/xhr.js | 111 | ||||
-rw-r--r-- | package.json | 28 | ||||
-rw-r--r-- | samples/calendar-data | 11 | ||||
-rw-r--r-- | samples/get-props | 0 | ||||
-rw-r--r-- | samples/resource-type | 10 | ||||
-rw-r--r-- | samples/xml/calendar-multiget.xml | 63 | ||||
-rw-r--r-- | samples/xml/prop-get.xml | 27 | ||||
-rw-r--r-- | samples/xml/propstat-success.xml | 10 | ||||
-rw-r--r-- | test.js | 104 | ||||
-rw-r--r-- | test/caldav/ics_test.js | 15 | ||||
-rw-r--r-- | test/caldav/sax/propstat_test.js | 27 | ||||
-rw-r--r-- | test/caldav/sax_test.js | 93 | ||||
-rw-r--r-- | test/helper.js | 16 |
20 files changed, 1174 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fd4f2b0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules +.DS_Store diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..de8556a --- /dev/null +++ b/Makefile @@ -0,0 +1,19 @@ +REPORTER := spec + +.PHONY: test +test: + ./node_modules/mocha/bin/mocha \ + --ui tdd \ + --reporter $(REPORTER) \ + --growl test/helper.js \ + test/caldav/*_test.js + +.PHONY: watch +FILES= +watch: + ./node_modules/mocha/bin/mocha \ + --ui tdd \ + --reporter $(REPORTER) \ + --watch \ + --growl \ + test/helper.js $(FILES) diff --git a/lib/caldav/caldav.js b/lib/caldav/caldav.js new file mode 100644 index 0000000..cf81358 --- /dev/null +++ b/lib/caldav/caldav.js @@ -0,0 +1,101 @@ +(function(global, module) { + + /** + * Define a list of paths + * this will only be used in the browser. + */ + var paths = {}; + + + /** + * Exports object is a shim + * we use in the browser to + * create an object that will behave much + * like module.exports + */ + function Exports(path) { + this.path = path; + } + + Exports.prototype = { + + /** + * Unified require between browser/node. + * Path is relative to this file so you + * will want to use it like this from any depth. + * + * + * var Leaf = ns.require('sub/leaf'); + * + * + * @param {String} path path lookup relative to this file. + */ + require: function exportRequire(path) { + if (typeof(window) === 'undefined') { + return require(require('path').join(__dirname, path)); + } else { + return paths[path]; + } + }, + + /** + * Maps exports to a file path. + */ + set exports(val) { + return paths[this.path] = val; + }, + + get exports() { + return paths[this.path]; + } + }; + + /** + * Module object constructor. + * + * + * var module = Module('sub/leaf'); + * module.exports = function Leaf(){} + * + * + * @constructor + * @param {String} path file path. + */ + function Module(path) { + return new Exports(path); + } + + Module.require = Exports.prototype.require; + Module.exports = Module; + Module._paths = paths; + + + /** + * Reference self as exports + * which also happens to be the constructor + * so you can assign items to the namespace: + * + * //assign to Module.X + * //assume module.exports is Module + * module.exports.X = Foo; //Module.X === Foo; + * Module.exports('foo'); //creates module.exports object. + * + */ + module.exports = Module; + + /** + * In the browser assign + * to a global namespace + * obviously 'Module' would + * be whatever your global namespace is. + */ + if (this.window) + window.CalDav = Module; + +}( + this, + (typeof(module) === 'undefined') ? + {} : + module +)); + diff --git a/lib/caldav/ics.js b/lib/caldav/ics.js new file mode 100644 index 0000000..47d9151 --- /dev/null +++ b/lib/caldav/ics.js @@ -0,0 +1,140 @@ +/** +@namespace +*/ +(function(module, ns) { + + // Iterate over all entries if x is an array, otherwise just call fn on x. + function ForAll(x, fn) { + if (!(x instanceof Array)) { + fn(x); + return; + } + for (var n = 0; n < x.length; ++n) + fn(x[n]); + } + + /* Pattern for an individual entry: name:value */ + var ENTRY = /^([A-Za-z0-9-]+)((?:;[A-Za-z0-9-]+=(?:"[^"]+"|[^";:,]+)(?:,(?:"[^"]+"|[^";:,]+))*)*):(.*)$/; + /* Pattern for an individual parameter: name=value[,value] */ + var PARAM = /;([A-Za-z0-9-]+)=((?:"[^"]+"|[^";:,]+)(?:,(?:"[^"]+"|[^";:,]+))*)/g; + /* Pattern for an individual parameter value: value | "value" */ + var PARAM_VALUE = /,?("[^"]+"|[^";:,]+)/g; + + // Parse a calendar in iCal format. + function ParseICal (text, success, error) { + // Parse the text into an object graph + var lines = text.replace('\r', '').split('\n'); + var tos = Object.create(null); + var stack = [tos]; + + // Parse parameters for an entry. Foramt: <param>=<pvalue>[;...] + function parseParams(params) { + var map = Object.create(null); + var param = PARAM.exec(params); + while (param) { + var values = []; + var value = PARAM_VALUE.exec(param[2]); + while (value) { + values.push(value[1].replace(/^"(.*)"$/, '$1')); + value = PARAM_VALUE.exec(param[2]); + } + map[param[1].toLowerCase()] = (values.length > 1 ? values : values[0]); + param = PARAM.exec(params); + } + return map; + } + + // Add a property to the current object. If a property with the same name + // already exists, turn it into an array. + function add(prop, value, params) { + if (params) + value = { parameters: parseParams(params), value: value }; + if (prop in tos) { + var previous = tos[prop]; + if (previous instanceof Array) { + previous.push(value); + return; + } + value = [previous, value]; + } + tos[prop] = value; + } + + for (var n = 0; n < lines.length; ++n) { + var line = lines[n]; + // check whether the line continues (next line stats with space or tab) + var nextLine; + while ((nextLine = lines[n+1]) && (nextLine[0] == ' ' || nextLine[0] == '\t')) { + line += nextLine.substr(1); + ++n; + continue; + } + // parse the entry, format is 'PROPERTY:VALUE' + var matches = ENTRY.exec(line); + if (!matches) + return error('invalid format'); + var prop = matches[1].toLowerCase(); + var params = matches[2]; + var value = matches[3]; + switch (prop) { + case 'begin': + var obj = Object.create(null); + add(value.toLowerCase(), obj); + stack.push(tos = obj); + break; + case 'end': + stack.pop(); + tos = stack[stack.length - 1]; + if (stack.length == 1) { + var cal = stack[0]; + if (typeof cal.vcalendar != 'object' || cal.vcalendar instanceof Array) + return error('single vcalendar object expected'); + return success(cal.vcalendar); + } + break; + default: + add(prop, value, params); + break; + } + } + return error('unexpected end of file'); + } + + function Value(v) { + return (typeof v !== 'object') ? v : v.value; + } + + function Parameter(v, name) { + if (typeof v !== 'object') + return undefined; + return v.parameters[name]; + } + + // Parse a time specification. + function ParseDateTime(v) { + var dt = Value(v); + if (Parameter(v, 'VALUE') == 'DATE') { + // 20081202 + return new Date(dt.substr(0, 4), dt.substr(4, 2), dt.substr(6, 2)); + } + v = Value(v); + // 20120426T130000Z + var year = dt.substr(0, 4); + var month = dt.substr(4, 2) - 1; + var day = dt.substr(6, 2); + var hour = dt.substr(9, 2); + var min = dt.substr(11, 2); + var sec = dt.substr(13, 2); + if (dt[15] == 'Z') + return new Date(Date.UTC(year, month, day, hour, min, sec)); + return new Date(year, month, day, hour, min, sec); + } + + module.exports = ParseICal; + +}.apply( + this, + (this.CalDav) ? + [CalDav('ics'), CalDav] : + [module, require('./caldav')] +)); diff --git a/lib/caldav/responder.js b/lib/caldav/responder.js new file mode 100644 index 0000000..b4abd21 --- /dev/null +++ b/lib/caldav/responder.js @@ -0,0 +1,209 @@ +/** +@namespace +*/ +(function(module, ns) { + + /** + * Constructor + * + * @param {Object} list of events to add onto responder. + */ + function Responder(events) { + this._$events = Object.create(null); + + if (typeof(events) !== 'undefined') { + this.addEventListener(events); + } + }; + + /** + * Stringifies request to websocket + * + * + * @param {String} command command name. + * @param {Object} data object to be sent over the wire. + * @return {String} json object. + */ + Responder.stringify = function stringify(command, data) { + return JSON.stringify([command, data]); + }; + + /** + * Parses request from WebSocket. + * + * @param {String} json json string to translate. + * @return {Object} ex: { event: 'test', data: {} }. + */ + Responder.parse = function parse(json) { + var data; + try { + data = (json.forEach) ? json : JSON.parse(json); + } catch (e) { + throw new Error("Could not parse json: '" + json + '"'); + } + + return {event: data[0], data: data[1]}; + }; + + Responder.prototype = { + parse: Responder.parse, + stringify: Responder.stringify, + + /** + * Events on this instance + * + * @type Object + */ + _$events: null, + + /** + * Recieves json string event and dispatches an event. + * + * @param {String|Object} json data object to respond to. + * @param {String} json.event event to emit. + * @param {Object} json.data data to emit with event. + * @param {Object} [params] option number of params to pass to emit. + * @return {Object} result of WebSocketCommon.parse. + */ + respond: function respond(json) { + var event = Responder.parse(json), + args = Array.prototype.slice.call(arguments).slice(1); + + args.unshift(event.data); + args.unshift(event.event); + + this.emit.apply(this, args); + + return event; + }, + + //TODO: Extract event emitter logic + + /** + * Adds an event listener to this object. + * + * + * @param {String} type event name. + * @param {Function} callback event callback. + */ + addEventListener: function addEventListener(type, callback) { + var event; + + if (typeof(callback) === 'undefined' && typeof(type) === 'object') { + for (event in type) { + if (type.hasOwnProperty(event)) { + this.addEventListener(event, type[event]); + } + } + + return this; + } + + if (!(type in this._$events)) { + this._$events[type] = []; + } + + this._$events[type].push(callback); + + return this; + }, + + /** + * Adds an event listener which will + * only fire once and then remove itself. + * + * + * @param {String} type event name. + * @param {Function} callback fired when event is emitted. + */ + once: function once(type, callback) { + var self = this; + function onceCb() { + self.removeEventListener(type, onceCb); + callback.apply(this, arguments); + } + + this.addEventListener(type, onceCb); + + return this; + }, + + /** + * Emits an event. + * + * Accepts any number of additional arguments to pass unto + * event listener. + * + * @param {String} eventName name of the event to emit. + * @param {Object} [arguments] additional arguments to pass. + */ + emit: function emit() { + var args = Array.prototype.slice.call(arguments), + event = args.shift(), + eventList, + self = this; + + if (event in this._$events) { + eventList = this._$events[event]; + + eventList.forEach(function(callback) { + callback.apply(self, args); + }); + } + + return this; + }, + + /** + * Removes all event listeners for a given event type + * + * + * @param {String} event event type to remove. + */ + removeAllEventListeners: function removeAllEventListeners(name) { + if (name in this._$events) { + //reuse array + this._$events[name].length = 0; + } + + return this; + }, + + /** + * Removes a single event listener from a given event type + * and callback function. + * + * + * @param {String} eventName event name. + * @param {Function} callback same instance of event handler. + */ + removeEventListener: function removeEventListener(name, callback) { + var i, length, events; + + if (!(name in this._$events)) { + return false; + } + + events = this._$events[name]; + + for (i = 0, length = events.length; i < length; i++) { + if (events[i] && events[i] === callback) { + events.splice(i, 1); + return true; + } + } + + return false; + } + + }; + + Responder.prototype.on = Responder.prototype.addEventListener; + module.exports = Responder; + +}.apply( + this, + (this.CalDav) ? + [CalDav('responder'), CalDav] : + [module, require('./caldav')] +)); diff --git a/lib/caldav/sax.js b/lib/caldav/sax.js new file mode 100644 index 0000000..28fa716 --- /dev/null +++ b/lib/caldav/sax.js @@ -0,0 +1,158 @@ +(function(module, ns) { + + var sax = require('sax'), + Responder = ns.require('responder'); + + function Parser() { + var dispatch = [ + 'onerror', + 'onopentag', + 'onclosetag', + 'ontext' + ]; + + this.parse = sax.parser(true, { + xmlns: true, + trim: true, + normalize: true, + lowercase: true + }); + + dispatch.forEach(function(event) { + this.parse[event] = this._dispatchEvent.bind(this, event); + }, this); + + this.parse.onend = this.onend.bind(this); + + this.depth = 0; + this.handles = {}; + + this.stack = []; + this.handlerStack = []; + this.tagStack = []; + + this.current = this.root = {}; + this.setParser(this); + + Responder.call(this); + } + + Parser.prototype = { + + __proto__: Responder.prototype, + + setParser: function(parse) { + this.currentParser = parse; + }, + + restoreParser: function() { + if ('oncomplete' in this.currentParser) { + this.currentParser.oncomplete.call(this); + } + + var parser = this.handlerStack.pop(); + this.setParser(parser || this); + }, + + _dispatchEvent: function(type, data) { + + if (type === 'onopentag') { + data.tagSpec = data.uri + '/' + data.local; + } + + if (type in this.currentParser) { + this.currentParser[type].call(this, data); + } else { + this[type](data); + } + }, + + addHandler: function(obj) { + this.handles[obj.tag] = obj; + }, + + checkHandler: function(handle) { + var handler, + handlers = this.currentParser.handles; + + if (handle in handlers) { + handler = handlers[handle]; + if (handler !== this.currentParser) { + return handler; + } + } + + return false; + }, + + handleError: function() { + }, + + onopentag: function(data) { + var current = this.current, + name = data.local, + handler = this.checkHandler(data.tagSpec); + + if (handler) { + this.handlerStack.push(this.currentParser); + this.setParser(handler); + return this._dispatchEvent('onopentag', data); + } + + this.tagStack.push(data.tagSpec); + this.stack.push(this.current); + + if (name in current) { + var next = {}; + + if (!(current[name] instanceof Array)) { + current[name] = [current[name]]; + } + + current[name].push(next); + this.current = next; + } else { + this.current = current[name] = {}; + } + }, + + onend: function() { + this.emit('complete', this.root, this); + }, + + checkStackForHandler: function(restore) { + var stack = this.tagStack, + last = stack[stack.length - 1], + result; + + result = last === this.currentParser.tag; + + if (restore && result) { + this.restoreParser(); + } + + return result; + }, + + onclosetag: function() { + this.current = this.stack.pop(); + this.tagStack.pop(); + }, + + ontext: function(data) { + this.current.value = data; + }, + + write: function(data) { + return this.parse.write(data); + } + }; + + module.exports = Parser; + +}.apply( + this, + (this.CalDav) ? + [CalDav('xml_parser'), CalDav] : + [module, require('./caldav')] +)); diff --git a/lib/caldav/sax/propstat.js b/lib/caldav/sax/propstat.js new file mode 100644 index 0000000..0339f92 --- /dev/null +++ b/lib/caldav/sax/propstat.js @@ -0,0 +1,30 @@ +(function(module, ns) { + var Responder = ns.require('responder'); + + function Propstat(sax, complete) { + + function onopen() { + + } + + function onclose() { + + } + + function ontext() { + + } + + sax.on('tagopen', onopen); + sax.on('tagclose', ontext); + sax.on('text', ontext); + } + + module.exports = Propstat; + +}.apply( + this, + (this.CalDav) ? + [CalDav('sax/propstat'), CalDav] : + [module, require('../caldav')] +)); diff --git a/lib/caldav/xhr.js b/lib/caldav/xhr.js new file mode 100644 index 0000000..bfc3eeb --- /dev/null +++ b/lib/caldav/xhr.js @@ -0,0 +1,111 @@ +/** +@namespace +*/ +(function(module, ns) { + var Native; + + if (typeof(window) === 'undefined') { + Native = require('xmlhttprequest').XMLHttpRequest; + } else { + Native = window.XMLHttpRequest; + } + + /** + * Creates a XHR wrapper. + * Depending on the platform this is loaded + * from the correct wrapper type will be used. + * + * Options are derived from properties on the prototype. + * See each property for its default value. + * + * @class + * @name CalDav.Xhr + * @param {Object} options options for xhr. + * @param {String} [options.method="GET"] any HTTP verb like 'GET' or 'POST'. + * @param {Boolean} [options.async] false will indicate + * a synchronous request. + * @param {Object} [options.headers] full of http headers. + * @param {Object} [options.data] post data. + */ + function Xhr(options) { + var key; + if (typeof(options) === 'undefined') { + options = {}; + } + + for (key in options) { + if (options.hasOwnProperty(key)) { + this[key] = options[key]; + } + } + } + + Xhr.prototype = { + /** @scope CalDav.Xhr.prototype */ + + xhrClass: Native, + method: 'GET', + async: true, + waiting: false, + user: null, + password: null, + + headers: {}, + data: {}, + + _seralize: function _seralize() { + return this.data; + }, + + /** + * Aborts request if its in progress. + */ + abort: function abort() { + if (this.xhr) { + this.xhr.abort(); + } + }, + + /** + * Sends request to server. + * + * @param {Function} callback success/failure handler. + */ + send: function send(callback) { + var header, xhr; + + if (typeof(callback) === 'undefined') { + callback = this.callback; + } + + xhr = this.xhr = new this.xhrClass(); + xhr.open(this.method, this.url, this.async, this.user, this.password); + + for (header in this.headers) { + if (this.headers.hasOwnProperty(header)) { + xhr.setRequestHeader(header, this.headers[header]); + } + } + + xhr.onreadystatechange = function onReadyStateChange() { + var data; + if (xhr.readyState === 4) { + data = xhr.responseText; + this.waiting = false; + callback(data, xhr); + } + }.bind(this); + + this.waiting = true; + xhr.send(this._seralize()); + } + }; + + module.exports = Xhr; + +}.apply( + this, + (this.CalDav) ? + [CalDav('xhr'), CalDav] : + [module, require('./caldav')] +)); diff --git a/package.json b/package.json new file mode 100644 index 0000000..4c84623 --- /dev/null +++ b/package.json @@ -0,0 +1,28 @@ +{ + "name": "caldav", + "version": "0.0.1", + "author": "", + "description": "", + "main": "lib/index.js", + + "repository": { + "type": "git", + "url": "https://github.com/.git" + }, + + "keywords": [ + "" + ], + "dependencies" : { + "xmlhttprequest": "1.4.2" + }, + "devDependencies": { + "mocha": "~1.1", + "chai": "~1.0", + "sax": "0.4.0" + }, + "license": "MIT", + "engine": { + "node": ">=0.4" + } +} diff --git a/samples/calendar-data b/samples/calendar-data new file mode 100644 index 0000000..1b9ed7b --- /dev/null +++ b/samples/calendar-data @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8" ?> +<C:calendar-query xmlns:D="DAV:" + xmlns:C="urn:ietf:params:xml:ns:caldav"> + <D:prop> + <D:getetag/> + <C:calendar-data/> + </D:prop> + <C:filter> + <C:comp-filter name="VCALENDAR"/> + </C:filter> +</C:calendar-query> diff --git a/samples/get-props b/samples/get-props new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/samples/get-props diff --git a/samples/resource-type b/samples/resource-type new file mode 100644 index 0000000..a6aa471 --- /dev/null +++ b/samples/resource-type @@ -0,0 +1,10 @@ +<?xml version="1.0" ?> +<D:propfind xmlns:C="urn:ietf:params:xml:ns:caldav" xmlns:CS="http://calendarserver.org/ns/" xmlns:D="DAV:"> + <D:prop> + <D:resourcetype/> + <D:owner/> + <D:supported-report-set/> + <C:supported-calendar-component-set/> + <CS:getctag/> + </D:prop> +</D:propfind> diff --git a/samples/xml/calendar-multiget.xml b/samples/xml/calendar-multiget.xml new file mode 100644 index 0000000..9ef1aaf --- /dev/null +++ b/samples/xml/calendar-multiget.xml @@ -0,0 +1,63 @@ +<D:multistatus xmlns:D="DAV:"> + <D:response> + <D:href>/dav/calmozilla1/Calendar/calmozilla1/6e4d147f-4d75-436f-a233-976fd06fd3f7.ics</D:href> + <D:propstat> + <D:status>HTTP/1.1 200 OK</D:status> + <D:prop> + <C:calendar-data xmlns:C="urn:ietf:params:xml:ns:caldav">BEGIN:VCALENDAR + VERSION:2.0 PRODID:Zimbra-Calendar-Provider + BEGIN:VTIMEZONE TZID:Etc/GMT + BEGIN:STANDARD DTSTART:19710101T000000 + TZOFFSETTO:-0000 TZOFFSETFROM:-0000 + TZNAME:GMT END:STANDARD + END:VTIMEZONE BEGIN:VEVENT + UID:6e4d147f-4d75-436f-a233-976fd06fd3f7 SUMMARY:Foo + ORGANIZER:mailto:calmozilla1@yahoo.com DTSTART;TZID="Etc/GMT":20120629T143000 + DTEND;TZID="Etc/GMT":20120629T150000 STATUS:CONFIRMED + CLASS:PUBLIC X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY + TRANSP:OPAQUE X-MICROSOFT-DISALLOW-COUNTER:TRUE + DTSTAMP:20120616T221243Z SEQUENCE:0 + END:VEVENT END:VCALENDAR</C:calendar-data> + <D:getetag>"2-2"</D:getetag> + </D:prop> + </D:propstat> + </D:response> + <D:response> + <D:href>/dav/calmozilla1/Calendar/calmozilla1/25b74da2-47d3-4ea8-be27-774fb13041b8.ics</D:href> + <D:propstat> + <D:status>HTTP/1.1 200 OK</D:status> + <D:prop> + <C:calendar-data xmlns:C="urn:ietf:params:xml:ns:caldav">BEGIN:VCALENDAR + VERSION:2.0 PRODID:Zimbra-Calendar-Provider + BEGIN:VEVENT UID:25b74da2-47d3-4ea8-be27-774fb13041b8 + SUMMARY:Bar ORGANIZER;CN=Sahaja Lal:mailto:calmozilla1@yahoo.com + DTSTART;VALUE=DATE:20120630 DTEND;VALUE=DATE:20120701 + STATUS:CONFIRMED CLASS:PUBLIC + X-MICROSOFT-CDO-ALLDAYEVENT:TRUE X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY + TRANSP:OPAQUE X-MICROSOFT-DISALLOW-COUNTER:TRUE + DTSTAMP:20120616T221255Z SEQUENCE:0 + END:VEVENT END:VCALENDAR</C:calendar-data> + <D:getetag>"3-3"</D:getetag> + </D:prop> + </D:propstat> + </D:response> + <D:response> + <D:href>/dav/calmozilla1/Calendar/calmozilla1/a8c499cb-3cab-44d5-ab1e-18abc0f5286f.ics</D:href> + <D:propstat> + <D:status>HTTP/1.1 200 OK</D:status> + <D:prop> + <C:calendar-data xmlns:C="urn:ietf:params:xml:ns:caldav">BEGIN:VCALENDAR + VERSION:2.0 PRODID:Zimbra-Calendar-Provider + BEGIN:VEVENT UID:a8c499cb-3cab-44d5-ab1e-18abc0f5286f + SUMMARY:Baz ORGANIZER;CN=Sahaja Lal:mailto:calmozilla1@yahoo.com + DTSTART;VALUE=DATE:20120628 DTEND;VALUE=DATE:20120629 + STATUS:CONFIRMED CLASS:PUBLIC + X-MICROSOFT-CDO-ALLDAYEVENT:TRUE X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY + TRANSP:OPAQUE X-MICROSOFT-DISALLOW-COUNTER:TRUE + DTSTAMP:20120616T221305Z SEQUENCE:0 + END:VEVENT END:VCALENDAR</C:calendar-data> + <D:getetag>"4-4"</D:getetag> + </D:prop> + </D:propstat> + </D:response> +</D:multistatus> diff --git a/samples/xml/prop-get.xml b/samples/xml/prop-get.xml new file mode 100644 index 0000000..72ee984 --- /dev/null +++ b/samples/xml/prop-get.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<D:multistatus xmlns:D="DAV:"> + + <D:response> + <D:href>/calendar/dav/calmozilla1%40gmail.com/user/</D:href> + <D:propstat> + <D:status>HTTP/1.1 200 OK</D:status> + <D:prop> + <D:principal-URL> + <D:href>/calendar/dav/calmozilla1@gmail.com/user/</D:href> + </D:principal-URL> + <D:resourcetype> + <D:principal /> + <D:collection /> + </D:resourcetype> + </D:prop> + </D:propstat> + + <D:propstat> + <D:status>HTTP/1.1 404 Not Found</D:status> + <D:prop> + <A:current-user-principal xmlns:A="DAV:" /> + </D:prop> + </D:propstat> + </D:response> + +</D:multistatus> diff --git a/samples/xml/propstat-success.xml b/samples/xml/propstat-success.xml new file mode 100644 index 0000000..93f27ec --- /dev/null +++ b/samples/xml/propstat-success.xml @@ -0,0 +1,10 @@ +<D:status>HTTP/1.1 200 OK</D:status> +<D:prop> + <D:principal-URL> + <D:href>/calendar/dav/calmozilla1@gmail.com/user/</D:href> + </D:principal-URL> + <D:resourcetype> + <D:principal /> + <D:collection /> + </D:resourcetype> +</D:prop> @@ -0,0 +1,104 @@ +var XHR = require('./lib/caldav/xhr.js'); +var url, user, pass, domain; + +function getBody(file) { + return require('fs').readFileSync( + __dirname + '/samples/' + file, 'utf8' + ); +} + +domain = 'http://localdav.com'; +uri = '/calendars/admin'; +user = 'admin'; +pass = 'admin'; + +function request(options) { + var defaults = { + user: user, + password: pass, + url: domain + uri + (options.uri || ''), + headers: { + Depth: 0 + } + }; + + if (typeof(options) === 'undefined') { + options = {}; + } + + for (var key in options) { + if (options.hasOwnProperty(key)) { + defaults[key] = options[key]; + } + } + + return new XHR(defaults); +} + + + +function getProps() { + + var body = '<?xml version="1.0" encoding="utf-8" ?>' + + '<D:propfind xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">' + + '<D:prop>' + + '<C:calendar-home-set/>' + + '<C:calendar-user-address-set/>' + + '<C:schedule-inbox-URL/>' + + '<C:schedule-outbox-URL/>' + + '</D:prop>' + + '</D:propfind>'; + + + var xhr = request({ + method: 'PROPFIND', + data: body, + headers: { + 'Depth': 0 + } + }); + + xhr.send(function(text, xhr) { + console.log(xhr.responseText); + }); +} + + +function getCalenders() { + var body = getBody('calendar-data'); + + var xhr = request({ + url: domain + '/calendar/dav/james%40lightsofapollo.com/', + method: 'REPORT', + headers: { + Depth: 1 + }, + data: body + }); + + xhr.send(function(text) { + console.log(text); + }); +} + + +function checkResource() { + var body = getBody('resource-type'), + xhr; + + xhr = request({ + uri: '/default', + method: 'PROPFIND', + data: body + }); + + xhr.send(function(text) { + console.log(text); + }); + +} + +checkResource(); + +//getCalenders(); +//getProps(); diff --git a/test/caldav/ics_test.js b/test/caldav/ics_test.js new file mode 100644 index 0000000..3f821b4 --- /dev/null +++ b/test/caldav/ics_test.js @@ -0,0 +1,15 @@ +var fs = require('fs'), + ics = requireLib('ics'), + data = fs.readFileSync(__dirname + '/../../data/test.data', 'utf8'); + +suite('caldav/ics', function() { + + test('intiailizer', function() { + ics(data, function(data) { + console.log(data.vevent[0]); + }, function() { + console.log('y'); + }) + }); + +}); diff --git a/test/caldav/sax/propstat_test.js b/test/caldav/sax/propstat_test.js new file mode 100644 index 0000000..74360a7 --- /dev/null +++ b/test/caldav/sax/propstat_test.js @@ -0,0 +1,27 @@ +suite('caldav/sax/propstat', function() { + var stat = requireLib('sax/propstat'), + sax = requireLib('sax'), + subject; + + var expected = { + status: 200, + 'principal-URL': '/calendar/dav/calmozilla1@gmail.com/user/', + 'resource-type': [ + 'principal', + 'collection' + ] + }; + + test('propstat success', function(done) { + var parser = sax(); + + console.log(parser.on); + stat(parser, function(err, result) { + console.log(result); + done(); + }); + + parser.write(loadSample('xml/propstat-success.xml')).close(); + }); + +}); diff --git a/test/caldav/sax_test.js b/test/caldav/sax_test.js new file mode 100644 index 0000000..83d69c1 --- /dev/null +++ b/test/caldav/sax_test.js @@ -0,0 +1,93 @@ +var xml = requireLib('sax'); + +suite('sax test', function() { + + var data, + subject; + + test('existance', function() { + var parser = new xml(); + + var StatusHandler = { + tag: 'DAV:/status', + + onopentag: function(data) { + }, + + ontext: function(data) { + this.current.status = data.match(/([0-9]{3,3})/)[1]; + }, + + onclosetag: function(data) { + this.restoreParser(); + } + }; + + var ResourceTypeHandler = { + tag: 'DAV:/resourcetype', + + onopentag: function(data) { + this.tagStack.push(data.tagSpec); + + if (data.local === 'resourcetype') { + this.current.resourceTypes = []; + } else { + this.current.resourceTypes.push(data.local); + } + }, + + onclosetag: function(data) { + this.checkStackForHandler(true); + this.tagStack.pop(); + } + }; + + var TextOnlyHandler = { + tag: 'DAV:/href', + + onopentag: function(data) { + }, + + ontext: function(data) { + this.current.href = data; + }, + + onclosetag: function(data) { + this.restoreParser(); + } + }; + + var ResponseHandler = { + tag: 'DAV:/response', + handles: { + 'DAV:/status': StatusHandler, + 'DAV:/resourcetype': ResourceTypeHandler, + 'DAV:/href': TextOnlyHandler, + 'DAV:/getetag': TextOnlyHandler + }, + + onclosetag: function(data) { + this.checkStackForHandler(true); + this.onclosetag(data); + }, + + oncomplete: function() { + this.emit('response', this.current, this); + } + + }; + + parser.addHandler(ResponseHandler); + + parser.on('response', function(data, context) { + console.log(JSON.stringify(data), '\n\n'); + }); + + parser.once('complete', function(data, parser) { + console.log(JSON.stringify(data)); + }); + + parser.write(data).close(); + }); + +}); diff --git a/test/helper.js b/test/helper.js new file mode 100644 index 0000000..9f40311 --- /dev/null +++ b/test/helper.js @@ -0,0 +1,16 @@ +var chai = require('chai'), + fs = require('fs'); + +chai.Assertion.includeStack = true; +assert = chai.assert; + +loadSample = function(file, cb) { + var root = __dirname + '/../samples/'; + fs.readFile(root + file, 'utf8', function(err, contents) { + cb(err, contents); + }); +}; + +requireLib = function(lib) { + return require(__dirname + '/../lib/caldav/' + lib); +}; |