UNPKG

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.

290 lines (266 loc) 10.1 kB
/* * @package jsDAV * @subpackage DAV * @copyright Copyright(c) 2011 Ajax.org B.V. <info AT ajax DOT org> * @author Mike de Boer <info AT mikedeboer DOT nl> * @license http://github.com/mikedeboer/jsDAV/blob/master/LICENSE MIT License */ "use strict"; var jsDAV_ServerPlugin = require("./../plugin"); var jsDAV_Property_GetLastModified = require("./../property/getLastModified"); var jsDAV_Property_ResourceType = require("./../property/resourceType"); var Fs = require("fs"); var Async = require("asyncjs"); var Exc = require("./../../shared/exceptions"); var Util = require("./../../shared/util"); var Xml = require("./../../shared/xml"); /** * Temporary File Filter Plugin * * The purpose of this filter is to intercept some of the garbage files * operation systems and applications tend to generate when mounting * a WebDAV share as a disk. * * It will intercept these files and place them in a separate directory. * these files are not deleted automatically, so it is adviceable to * delete these after they are not accessed for 24 hours. * * Currently it supports: * * OS/X style resource forks and .DS_Store * * desktop.ini and Thumbs.db (windows) * * .*.swp (vim temporary files) * * .dat.* (smultron temporary files) * * Additional patterns can be added, by adding on to the * temporaryFilePatterns property. */ var jsDAV_TemporaryFileFilter_Plugin = module.exports = jsDAV_ServerPlugin.extend({ /** * Plugin name * * @var String */ name: "temporaryfilefilter", /** * This is the list of patterns we intercept. * If new patterns are added, they must be valid patterns for preg_match. * * @var array */ temporaryFilePatterns: { "OS/X resource forks": /^\._(.*)/, "OS/X custom folder settings": /^\.DS_Store/, "Windows custom folder settings": /^desktop\.ini/, "Windows thumbnail cache": /^Thumbs\.db/, "ViM temporary files": /^\.(.*)\.swp/, "Smultron seems to create these": /^\.dat(.*)/, "Windows 7 lockfiles": /^~lock\.(.*)#/ }, /** * This is the directory where this plugin * will store it's files. * * @var string */ dataDir: null, initialize: function(handler) { this.handler = handler; /* * Make sure you specify a directory for your files. If you don't, we will * use the system's temp directory instead, and you might not want that. */ this.dataDir = this.handler.server.tmpDir + "/jsdav"; // ensure that the path is there Async.makePath(this.dataDir, function() {}); this.handler.addEventListener("beforeMethod", this.beforeMethod.bind(this)); this.handler.addEventListener("beforeCreateFile", this.beforeCreateFile.bind(this)); }, /** * This method is called before any HTTP method handler * * This method intercepts any GET, DELETE, PUT and PROPFIND calls to * filenames that are known to match the 'temporary file' regex. * * @param {String} method * @return bool */ beforeMethod: function(e, method) { var tempLocation = this.isTempFile(this.handler.getRequestUri()); var func = "http" + method.charAt(0).toUpperCase() + method.substr(1).toLowerCase(); if (!tempLocation || !this[func]) return e.next(); this[func](e, tempLocation); }, /** * This method is invoked if some subsystem creates a new file. * * This is used to deal with HTTP LOCK requests which create a new * file. * * @param {String} uri * @param resource data * @return bool */ beforeCreateFile: function(e, uri, data) { var tempPath = this.isTempFile(uri); if (!tempPath) return e.next(); var enc = "utf8"; if (!data || data.length === 0) { //new node version will support writing empty files? data = new Buffer(0); enc = "binary"; } Fs.writeFile(tempPath, data, enc, function(err) { if (err) return e.next(err); //@todo set response header: {"X-jsDav-Temp": "true"} e.stop(); }); }, /** * This method will check if the url matches the temporary file pattern * if it does, it will return an path based on this.dataDir for the * temporary file storage. * * @param {String} path * @return boolean|string */ isTempFile: function(path) { var tempFile; // We're only interested in the basename. var tempPath = Util.splitPath(path)[1]; for (var i in this.temporaryFilePatterns) { tempFile = this.temporaryFilePatterns[i]; if (tempFile.test(tempPath)) return this.dataDir + "/jsdav_" + Util.createHash(path) + ".tempfile"; } return false; }, /** * This method handles the GET method for temporary files. * If the file doesn't exist, it will return false which will kick in * the regular system for the GET method. * * @param {String} tempLocation * @return bool */ httpGet: function(e, tempLocation) { var self = this; Fs.exists(tempLocation, function(exists) { if (!exists) return e.next(); Fs.stat(tempLocation, function(err, stat) { if (err) return e.next(err); Fs.readFile(tempLocation, function(err, data) { if (err) return e.next(err); var res = self.handler.httpResponse; res.writeHead(200, { "Content-Type": "application/octet-stream", "Content-Length": stat.size, "X-jsDAV-Temp": "true" }); res.end(data); e.stop(); }); }); }); }, /** * This method handles the PUT method. * * @param {String} tempLocation * @return bool */ httpPut: function(e, tempLocation) { var self = this; Fs.exists(tempLocation, function(exists) { if (exists && self.handler.httpRequest.headers["if-none-match"]) { return e.next(new Exc.PreconditionFailed( "The resource already exists, and an If-None-Match header was supplied") ); } self.handler.getRequestBody("binary", null, false, function(err, body, cleanup) { if (err) return e.next(err); Fs.writeFile(tempLocation, body, "binary", function(err) { if (cleanup) cleanup(); if (err) return e.next(err); var res = self.handler.httpResponse; res.writeHead(!exists ? 201 : 200, {"X-jsDAV-Temp": "true"}); res.end(); e.stop(); }); }); }); }, /** * This method handles the DELETE method. * * If the file didn't exist, it will return false, which will make the * standard HTTP DELETE handler kick in. * * @param {String} tempLocation * @return bool */ httpDelete: function(e, tempLocation) { var self = this; Fs.exists(tempLocation, function(exists) { if (!exists) return e.next(); Fs.unlink(tempLocation, function(err) { if (err) return e.next(err); var res = self.handler.httpResponse; res.writeHead(204, {"X-jsDAV-Temp": "true"}); res.end(); e.stop(); }); }); }, /** * This method handles the PROPFIND method. * * It's a very lazy method, it won't bother checking the request body * for which properties were requested, and just sends back a default * set of properties. * * @param {String} tempLocation * @return void */ httpPropfind: function(e, tempLocation) { var self = this; Fs.stat(tempLocation, function(err, stat) { if (err || !stat) return e.next(); self.handler.getRequestBody("utf8", null, false, function(err, data) { if (err) return e.next(err); self.handler.parsePropfindRequest(data, function(err, requestedProps) { if (!Util.empty(err)) return e.next(err); var properties = {}; properties[tempLocation] = { "href" : self.handler.getRequestUri(), "200" : { "{DAV:}getlastmodified" : jsDAV_Property_GetLastModified.new(stat.mtime), "{DAV:}getcontentlength" : stat.size, "{DAV:}resourcetype" : jsDAV_Property_ResourceType.new(null) } }; properties[tempLocation]["200"]["{" + Xml.NS_AJAXORG + "}tempFile"] = true; var res = self.handler.httpResponse; res.writeHead(207, { "Content-Type": "application/xml; charset=utf-8", "X-jsDAV-Temp": "true" }); res.end(self.handler.generateMultiStatus(properties)); e.stop(); }); }); }); } });