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.

934 lines (853 loc) 31.3 kB
/* * @package jsDAV * @subpackage shared * @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 */ var jsDAV = require("./../jsdav"); var Exc = require("./exceptions"); var Crypto = require("crypto"); var Async = require("asyncjs"); var Util = require("util"); var Fs = require("fs"); // keep the following around until NodeJS < 0.8 blow over if (!Fs.exists) { var Path = require("path"); Fs.exists = Path.exists; Fs.existsSync = Path.existsSync; } /** * Make sure that an array instance contains only unique values (NO duplicates). * * @type {Array} */ exports.makeUnique = function(arr){ var i, length, newArr = []; for (i = 0, length = arr.length; i < length; i++) { if (newArr.indexOf(arr[i]) == -1) newArr.push(arr[i]); } arr.length = 0; for (i = 0, length = newArr.length; i < length; i++) arr.push(newArr[i]); return arr; }; /** * Search for a value 'obj' inside an array instance and remove it when found. * * @param {Array} arr * @param {mixed} obj * @type {Array} */ exports.arrayRemove = function(arr, obj) { for (var i = arr.length - 1; i >= 0; i--) { if (arr[i] != obj) continue; arr.splice(i, 1); } return arr; }; /** * Strips whitespace from the beginning and end of a string * version: 1107.2516 * from: http://phpjs.org/functions/trim * original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) * example 1: trim(' Kevin van Zonneveld '); * returns 1: 'Kevin van Zonneveld' * example 2: trim('Hello World', 'Hdle'); * returns 2: 'o Wor' * example 3: trim(16, 1); * returns 3: 6 */ exports.trim = function(str, charlist) { // Strips whitespace from the beginning and end of a string var whitespace, l = 0, i = 0; str += ""; if (!charlist) { // default list whitespace = " \n\r\t\f\x0b\xa0\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u2028\u2029\u3000"; } else { // preg_quote custom list charlist += ""; whitespace = charlist.replace(/([\[\]\(\)\.\?\/\*\{\}\+\$\^\:])/g, "$1"); } l = str.length; for (i = 0; i < l; i++) { if (whitespace.indexOf(str.charAt(i)) === -1) { str = str.substring(i); break; } } l = str.length; for (i = l - 1; i >= 0; i--) { if (whitespace.indexOf(str.charAt(i)) === -1) { str = str.substring(0, i + 1); break; } } return whitespace.indexOf(str.charAt(0)) === -1 ? str : ""; }; /** * Removes trailing whitespace * version: 1107.2516 * from: http://phpjs.org/functions/rtrim * original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) * example 1: rtrim(' Kevin van Zonneveld '); * returns 1: ' Kevin van Zonneveld' */ exports.rtrim = function(str, charlist) { charlist = !charlist ? " \\s\u00A0" : (charlist+"").replace(/([\[\]\(\)\.\?\/\*\{\}\+\$\^\:])/g, "\\$1"); var re = new RegExp("[" + charlist + "]+$", "g"); return (str+"").replace(re, ""); }; /** * Checks if a needle occurs in a haystack ;) * * @param {String} haystack * @param {String} needle * @param {String} matchType * @return bool */ exports.textMatch = function(haystack, needle, matchType) { matchType = matchType || "contains"; switch (matchType) { case "contains" : return haystack.indexOf(needle) > -1; case "equals" : return haystack === needle; case "starts-with" : return haystack.indexOf(needle) === 0; case "ends-with" : return haystack.lastIndexOf(needle) === (haystack.length - needle.length); default : throw new Exc.BadRequest("Match-type: " + matchType + " is not supported"); } }; /** * Splits a string into chunks like JS' String#split(), but with some additional * options, like each item in the resulting array is stripped from any whitespace. * * @param {String} s * @param {String} seperator * @param {Number} limit * @param {Boolean} bLowerCase */ exports.splitSafe = function(s, seperator, limit, bLowerCase) { return (bLowerCase && s.toLowerCase() || s) .replace(/(?:^\s+|\n|\s+$)/g, "") .split(new RegExp("[\\s ]*" + seperator + "[\\s ]*", "g"), limit || 999); }; /** * Extends an object with one or more other objects by copying all their * properties. * @param {Object} dest the destination object. * @param {Object} src the object that is copies from. * @return {Object} the destination object. */ exports.extend = function(dest, src){ var prop, i, x = !dest.notNull; if (arguments.length == 2) { for (prop in src) { if (x || src[prop]) dest[prop] = src[prop]; } return dest; } for (i = 1; i < arguments.length; i++) { src = arguments[i]; for (prop in src) { if (x || src[prop]) dest[prop] = src[prop]; } } return dest; }; exports.arrayToMap = function(arr) { var map = {}; for (var i = 0, l = arr.length; i < l; ++i) map[arr[i]] = 1; return map; }; /** * Main used to check if 'err' is undefined or null * * @param {mixed} obj * @return {Boolean} */ exports.empty = function(obj) { if (arguments.length === 1) return obj === undefined || obj === null || obj === "" || obj === false; // support multiple arguments that shortens: // Util.empty('foo') && Util.empty('bar') to Util.empty('foo', 'bar') for (var empty = true, i = 0, l = arguments.length; i < l && empty; ++i) { obj = arguments[i]; empty = (obj === undefined || obj === null || obj === "" || obj === false); } return empty; }; /** * Determines whether a {String} is true in the html attribute sense. * @param {mixed} value the variable to check * Possible values: * true The function returns true. * 'true' The function returns true. * 'on' The function returns true. * 1 The function returns true. * '1' The function returns true. * @return {Boolean} whether the {String} is considered to imply truth. */ exports.isTrue = function(c){ return (c === true || c === "true" || c === "on" || typeof c == "number" && c > 0 || c === "1"); }; /** * Determines whether a {String} is false in the html attribute sense. * @param {mixed} value the variable to check * Possible values: * false The function returns true. * 'false' The function returns true. * 'off' The function returns true. * 0 The function returns true. * '0' The function returns true. * @return {Boolean} whether the {String} is considered to imply untruth. */ exports.isFalse = function(c){ return (c === false || c === "false" || c === "off" || c === 0 || c === "0"); }; exports.isScalar = function(mixed) { return (/boolean|number|string/).test(typeof mixed); }; /** * Returns the 'dirname' and 'basename' for a path. * * The reason there is a custom function for this purpose, is because * basename() is locale aware (behaviour changes if C locale or a UTF-8 locale is used) * and we need a method that just operates on UTF-8 characters. * * In addition Path.split is platform aware, and will treat backslash (\) as a * directory separator on windows. * * This method returns the 2 components as an array. * * If there is no dirname, it will return an empty string. Any / appearing at the * end of the {String} is stripped off. * * @param {string} path * @return array */ exports.splitPath = function(path) { //var sPath = Url.parse(path).pathname; //return [Path.dirname(sPath) || null, Path.basename(sPath) || null]; var matches = path.match(/^(?:(?:(.*)(?:\/+))?([^\/]+))(?:\/?)$/i); return matches && matches.length ? [matches[1] || "", matches[2] || ""] : [null, null]; }; exports.escapeRegExp = function(str) { return str.replace(/([.*+?\^${}()|\[\]\/\\])/g, "\\$1"); }; // taken from http://xregexp.com/ exports.grepEscapeRegExp = function(str) { return str.replace(/[[\]{}()*+?.,\\^$|#\s"']/g, "\\$&"); } exports.escapeShell = function(str) { return str.replace(/([\\"'`$\s\(\)<>])/g, "\\$1"); }; // Internationalization strings exports.i18n = { /** * Defines what day starts the week * * Monday (1) is the international standard. * Redefine this to 0 if you want weeks to begin on Sunday. */ beginWeekday : 1, dayNames : [ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ], dayNumbers : { "Sun" : 0, "Mon" : 1, "Tue" : 2, "Wed" : 3, "Thu" : 4, "Fri" : 5, "Sat" : 6, "Sunday" : 0, "Monday" : 1, "Tuesday" : 2, "Wednesday" : 3, "Thursday" : 4, "Friday" : 5, "Saturday" : 6 }, monthNames : [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ], monthNumbers : { "Jan" : 0, "Feb" : 1, "Mar" : 2, "Apr" : 3, "May" : 4, "Jun" : 5, "Jul" : 6, "Aug" : 7, "Sep" : 8, "Oct" : 9, "Nov" : 10, "Dec" : 11 } }; exports.DATE_DEFAULT = "ddd mmm dd yyyy HH:MM:ss"; exports.DATE_SHORT = "m/d/yy"; exports.DATE_MEDIUM = "mmm d, yyyy"; exports.DATE_LONG = "mmmm d, yyyy"; exports.DATE_FULL = "dddd, mmmm d, yyyy"; exports.DATE_SHORTTIME = "h:MM TT"; exports.DATE_MEDIUMTIME = "h:MM:ss TT"; exports.DATE_LONGTIME = "h:MM:ss TT Z"; exports.DATE_ISODATE = "yyyy-mm-dd"; exports.DATE_ISOTIME = "HH:MM:ss"; exports.DATE_ISODATETIME = "yyyy-mm-dd'T'HH:MM:ss"; exports.DATE_ISOUTCDATETIME = "UTC:yyyy-mm-dd'T'HH:MM:ss'Z'"; exports.DATE_RFC1123 = "ddd, dd mmm yyyy HH:MM:ss o"; exports.DATE_RFC822 = "ddd, dd, mmm yy HH:MM:ss Z";////RFC 822: 'Tue, 20 Jun 82 08:09:07 GMT' exports.dateFormat = (function () { var token = /d{1,4}|m{1,4}|yy(?:yy)?|([HhMsTt])\1?|[LloSZ]|"[^"]*"|'[^']*'/g, timezone = /\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[\-+]\d{4})?)\b/g, timezoneClip = /[^\-+\dA-Z]/g, pad = function (val, len) { val = String(val); len = len || 2; while (val.length < len) val = "0" + val; return val; }; // Regexes and supporting functions are cached through closure return function (date, mask, utc) { // You can't provide utc if you skip other args (use the "UTC:" mask prefix) if (arguments.length == 1 && (typeof date == "string" || date instanceof String) && !/\d/.test(date)) { mask = date; date = undefined; } // Passing date through Date applies apf.date.getDateTime, if necessary date = date ? new Date(date) : new Date(); if (isNaN(date)) return "NaN";//throw new SyntaxError("invalid date"); mask = String(mask || exports.DATE_DEFAULT); // Allow setting the utc argument via the mask if (mask.slice(0, 4) == "UTC:") { mask = mask.slice(4); utc = true; } var _ = utc ? "getUTC" : "get", d = date[_ + "Date"](), D = date[_ + "Day"](), m = date[_ + "Month"](), y = date[_ + "FullYear"](), H = date[_ + "Hours"](), M = date[_ + "Minutes"](), s = date[_ + "Seconds"](), L = date[_ + "Milliseconds"](), o = utc ? 0 : date.getTimezoneOffset(), flags = { d : d, dd : pad(d), ddd : exports.i18n.dayNames[D], dddd: exports.i18n.dayNames[D + 7], m : m + 1, mm : pad(m + 1), mmm : exports.i18n.monthNames[m], mmmm: exports.i18n.monthNames[m + 12], yy : String(y).slice(2), yyyy: y, h : H % 12 || 12, hh : pad(H % 12 || 12), H : H, HH : pad(H), M : M, MM : pad(M), s : s, ss : pad(s), l : pad(L, 3), L : pad(L > 99 ? Math.round(L / 10) : L), t : H < 12 ? "a" : "p", tt : H < 12 ? "am" : "pm", T : H < 12 ? "A" : "P", TT : H < 12 ? "AM" : "PM", Z : utc ? "UTC" : (String(date).match(timezone) || [""]).pop().replace(timezoneClip, ""), o : (o > 0 ? "-" : "+") + pad(Math.floor(Math.abs(o) / 60) * 100 + Math.abs(o) % 60, 4), S : ["th", "st", "nd", "rd"] [d % 10 > 3 ? 0 : (d % 100 - d % 10 != 10) * d % 10] }; return mask.replace(token, function ($0) { return $0 in flags ? flags[$0] : $0.slice(1, $0.length - 1); }); }; })(); /** * Return a hash of the given {String} and optional encoding, defaulting to hex. * * @param {String} str * @param {String} algo. Defaults to 'md5' * @param {String} encoding. Defaults to 'hex' * @return {String} * @api public */ exports.createHash = function(str, algo, encoding) { return Crypto.createHash(algo || "md5").update(str).digest(encoding || "hex"); }; /** * Return a hash of the given {ReadableStream} and optional encoding, defaulting * to hex. * * @param {ReadableStream} stream * @param {String} algo. Defaults to 'md5' * @param {String} encoding. Defaults to 'hex' * @param {Function} callback * @return {String} * @api public */ exports.createHashStream = function(stream, algo, encoding, callback) { if (arguments.length == 2) { callback = algo; algo = "md5" encoding = "hex"; } else if (arguments.length == 3) { callback = encoding; encoding = "hex"; } var hash = Crypto.createHash(algo || "md5"); var callbackCalled = false; stream.on("data", function(buf) { hash.update(buf); }); stream.on("error", function(err) { if (callbackCalled) return; callbackCalled = true; cbfsgetetag(err); }); stream.on("end", function() { if (callbackCalled) return; callbackCalled = true; callback(null, hash.digest(encoding || "hex")); }); }; /** * Default mime type. */ var defaultMime = exports.defaultMime = "application/octet-stream"; exports.mime = { /** * Return mime type for the given path, * otherwise default to exports.defaultMime * ("application/octet-stream"). * * @param {String} path * @return {String} * @api public */ type: function getMime(path) { var index = String(path).lastIndexOf("."); if (index < 0) { return defaultMime; } var type = exports.mime.types[path.substring(index).toLowerCase()] || defaultMime; return (/(text|javascript)/).test(type) ? type + "; charset=utf-8" : type; }, /** * Mime types. */ types: { ".3gp" : "video/3gpp", ".a" : "application/octet-stream", ".ai" : "application/postscript", ".aif" : "audio/x-aiff", ".aiff" : "audio/x-aiff", ".aml" : "application/aml", ".asc" : "application/pgp-signature", ".asf" : "video/x-ms-asf", ".asm" : "text/x-asm", ".asx" : "video/x-ms-asf", ".atom" : "application/atom+xml", ".au" : "audio/basic", ".avi" : "video/x-msvideo", ".bat" : "application/x-msdownload", ".bin" : "application/octet-stream", ".bmp" : "image/bmp", ".bz2" : "application/x-bzip2", ".c" : "text/x-c", ".cab" : "application/vnd.ms-cab-compressed", ".cc" : "text/x-c", ".chm" : "application/vnd.ms-htmlhelp", ".class" : "application/octet-stream", ".coffee": "text/x-script.coffeescript", ".com" : "application/x-msdownload", ".conf" : "text/plain", ".cpp" : "text/x-c", ".crt" : "application/x-x509-ca-cert", ".cs" : "text/x-csharp", ".css" : "text/css", ".csv" : "text/csv", ".cxx" : "text/x-c", ".deb" : "application/x-debian-package", ".der" : "application/x-x509-ca-cert", ".diff" : "text/x-diff", ".djv" : "image/vnd.djvu", ".djvu" : "image/vnd.djvu", ".dll" : "application/x-msdownload", ".dmg" : "application/octet-stream", ".doc" : "application/msword", ".dot" : "application/msword", ".dtd" : "application/xml-dtd", ".dvi" : "application/x-dvi", ".ear" : "application/java-archive", ".eml" : "message/rfc822", ".eps" : "application/postscript", ".exe" : "application/x-msdownload", ".f" : "text/x-fortran", ".f77" : "text/x-fortran", ".f90" : "text/x-fortran", ".flv" : "video/x-flv", ".for" : "text/x-fortran", ".gem" : "application/octet-stream", ".gemspec" : "text/x-script.ruby", ".gif" : "image/gif", ".gz" : "application/x-gzip", ".h" : "text/x-c", ".hh" : "text/x-c", ".htm" : "text/html", ".html" : "text/html", ".ico" : "image/vnd.microsoft.icon", ".ics" : "text/calendar", ".ifb" : "text/calendar", ".iso" : "application/octet-stream", ".jar" : "application/java-archive", ".java" : "text/x-java-source", ".jnlp" : "application/x-java-jnlp-file", ".jpeg" : "image/jpeg", ".jpg" : "image/jpeg", ".js" : "application/javascript", ".json" : "application/json", ".log" : "text/plain", ".m3u" : "audio/x-mpegurl", ".m4v" : "video/mp4", ".man" : "text/troff", ".manifest": "text/cache-manifest", ".mathml" : "application/mathml+xml", ".mbox" : "application/mbox", ".mdoc" : "text/troff", ".me" : "text/troff", ".mid" : "audio/midi", ".midi" : "audio/midi", ".mime" : "message/rfc822", ".mml" : "application/mathml+xml", ".mng" : "video/x-mng", ".mov" : "video/quicktime", ".mp3" : "audio/mpeg", ".mp4" : "video/mp4", ".mp4v" : "video/mp4", ".mpeg" : "video/mpeg", ".mpg" : "video/mpeg", ".ms" : "text/troff", ".msi" : "application/x-msdownload", ".odp" : "application/vnd.oasis.opendocument.presentation", ".ods" : "application/vnd.oasis.opendocument.spreadsheet", ".odt" : "application/vnd.oasis.opendocument.text", ".ogg" : "application/ogg", ".p" : "text/x-pascal", ".pas" : "text/x-pascal", ".pbm" : "image/x-portable-bitmap", ".pdf" : "application/pdf", ".pem" : "application/x-x509-ca-cert", ".pgm" : "image/x-portable-graymap", ".pgp" : "application/pgp-encrypted", ".php" : "application/x-httpd-php", ".pkg" : "application/octet-stream", ".pl" : "text/x-script.perl", ".pm" : "text/x-script.perl-module", ".png" : "image/png", ".pnm" : "image/x-portable-anymap", ".ppm" : "image/x-portable-pixmap", ".pps" : "application/vnd.ms-powerpoint", ".ppt" : "application/vnd.ms-powerpoint", ".ps" : "application/postscript", ".psd" : "image/vnd.adobe.photoshop", ".py" : "text/x-script.python", ".qt" : "video/quicktime", ".ra" : "audio/x-pn-realaudio", ".rake" : "text/x-script.ruby", ".ram" : "audio/x-pn-realaudio", ".rar" : "application/x-rar-compressed", ".rb" : "text/x-script.ruby", ".rdf" : "application/rdf+xml", ".roff" : "text/troff", ".rpm" : "application/x-redhat-package-manager", ".rss" : "application/rss+xml", ".rtf" : "application/rtf", ".ru" : "text/x-script.ruby", ".s" : "text/x-asm", ".sgm" : "text/sgml", ".sgml" : "text/sgml", ".sh" : "application/x-sh", ".sig" : "application/pgp-signature", ".snd" : "audio/basic", ".so" : "application/octet-stream", ".svg" : "image/svg+xml", ".svgz" : "image/svg+xml", ".swf" : "application/x-shockwave-flash", ".t" : "text/troff", ".tar" : "application/x-tar", ".tbz" : "application/x-bzip-compressed-tar", ".tci" : "application/x-topcloud", ".tcl" : "application/x-tcl", ".tex" : "application/x-tex", ".texi" : "application/x-texinfo", ".texinfo" : "application/x-texinfo", ".text" : "text/plain", ".textile" : "text/x-web-textile", ".tif" : "image/tiff", ".tiff" : "image/tiff", ".torrent" : "application/x-bittorrent", ".tr" : "text/troff", ".ttf" : "application/x-font-ttf", ".txt" : "text/plain", ".vcf" : "text/x-vcard", ".vcs" : "text/x-vcalendar", ".vrml" : "model/vrml", ".war" : "application/java-archive", ".wav" : "audio/x-wav", ".wma" : "audio/x-ms-wma", ".wmv" : "video/x-ms-wmv", ".wmx" : "video/x-ms-wmx", ".wrl" : "model/vrml", ".wsdl" : "application/wsdl+xml", ".xbm" : "image/x-xbitmap", ".xhtml" : "application/xhtml+xml", ".xls" : "application/vnd.ms-excel", ".xml" : "application/xml", ".xpm" : "image/x-xpixmap", ".xsl" : "application/xml", ".xslt" : "application/xslt+xml", ".yaml" : "text/yaml", ".yml" : "text/yaml", ".zip" : "application/zip" } }; /** * Generate a random uuid. Usage: Math.uuid(length, radix) * * EXAMPLES: * // No arguments - returns RFC4122, version 4 ID * >>> Math.uuid() * "92329D39-6F5C-4520-ABFC-AAB64544E172" * * // One argument - returns ID of the specified length * >>> Math.uuid(15) // 15 character ID (default base=62) * "VcydxgltxrVZSTV" * * // Two arguments - returns ID of the specified length, and radix. (Radix must be <= 62) * >>> Math.uuid(8, 2) // 8 character ID (base=2) * "01001010" * >>> Math.uuid(8, 10) // 8 character ID (base=10) * "47473046" * >>> Math.uuid(8, 16) // 8 character ID (base=16) * "098F4D35" * * @param {Number} [len] The desired number of characters. Defaults to rfc4122, version 4 form * @param {Number} [radix] The number of allowable values for each character. * @type {String} */ exports.uuid = function(len, radix) { var i, chars = exports.uuid.CHARS, uuid = [], rnd = Math.random; radix = radix || chars.length; if (len) { // Compact form for (i = 0; i < len; i++) uuid[i] = chars[0 | rnd() * radix]; } else { // rfc4122, version 4 form var r; // rfc4122 requires these characters uuid[8] = uuid[13] = uuid[18] = uuid[23] = "-"; uuid[14] = "4"; // Fill in random data. At i==19 set the high bits of clock sequence as // per rfc4122, sec. 4.1.5 for (i = 0; i < 36; i++) { if (!uuid[i]) { r = 0 | rnd() * 16; uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r & 0xf]; } } } return uuid.join(""); }; //Public array of chars to use exports.uuid.CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".split(""); exports.uniqid = function(prefix, more_entropy) { // + original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) // + revised by: Kankrelune (http://www.webfaktory.info/) // % note 1: Uses an internal counter (in php_js global) to avoid collision // * example 1: uniqid(); // * returns 1: 'a30285b160c14' // * example 2: uniqid('foo'); // * returns 2: 'fooa30285b1cd361' // * example 3: uniqid('bar', true); // * returns 3: 'bara20285b23dfd1.31879087' if (typeof prefix == 'undefined') { prefix = ""; } var retId; var formatSeed = function (seed, reqWidth) { seed = parseInt(seed, 10).toString(16); // to hex str if (reqWidth < seed.length) { // so long we split return seed.slice(seed.length - reqWidth); } if (reqWidth > seed.length) { // so short we pad return Array(1 + (reqWidth - seed.length)).join("0") + seed; } return seed; }; if (!exports.uniqidSeed) { // init seed with big random int exports.uniqidSeed = Math.floor(Math.random() * 0x75bcd15); } exports.uniqidSeed++; retId = prefix // start with prefix, add current milliseconds hex string + formatSeed(parseInt(new Date().getTime() / 1000, 10), 8) + formatSeed(exports.uniqidSeed, 5); // add seed hex string if (more_entropy) { // for more entropy we add a float lower to 10 retId += (Math.random() * 10).toFixed(8).toString(); } return retId; }; exports.concatBuffers = function(bufs) { var buffer, length = 0, index = 0; if (!Array.isArray(bufs)) bufs = Array.prototype.slice.call(arguments); for (var i = 0, l = bufs.length; i < l; ++i) { buffer = bufs[i]; if (!buffer) continue; if (!Buffer.isBuffer(buffer)) buffer = bufs[i] = new Buffer(buffer); length += buffer.length; } buffer = new Buffer(length); bufs.forEach(function(buf, i) { buf = bufs[i]; buf.copy(buffer, index, 0, buf.length); index += buf.length; delete bufs[i]; }); return buffer; }; /** * StreamBuffer - Buffers submitted data in advance to facilitate asynchonous operations * http://tech.richardrodger.com/2011/03/28/node-js---dealing-with-submitted-http-request-data-when-you-have-to-make-a-database-call-first/ */ exports.streamBuffer = function(req) { // streambuffer already attached to req object if (req.streambuffer) return req; var buffers = []; var ended = false; var ondata = null; var onend = null; req.streambuffer = { ondata: function(fn) { for (var i = 0; i < buffers.length; i++) fn(buffers[i]); ondata = fn; buffers = null; }, onend: function(fn) { onend = fn; if (ended) onend(); } }; req.on("data", function(chunk) { if (!chunk) return; if (ondata) ondata(chunk); else buffers.push(chunk); }); req.on("end", function() { ended = true; if (onend) onend(); }); return req; }; exports.copy = function(src, dst, overwrite, callback) { function copy() { Fs.stat(src, function(err) { if (err) return callback(err); var readStream = Fs.createReadStream(src); var writeStream = Fs.createWriteStream(dst); writeStream.on("error", callback); writeStream.on("close", callback); readStream.pipe(writeStream, callback); }); } if (overwrite) { copy(); } else { Fs.stat(dst, function(err) { if (!err) return callback(new Error("File " + dst + " exists.")); copy(); }); } }; exports.move = function(src, dst, overwrite, callback) { function copyIfFailed(err) { if (!err) return callback(null); exports.copy(src, dst, overwrite, function(err) { if (!err) { // TODO // should we revert the copy if the unlink fails? Fs.unlink(src, callback); } else { callback(err); } }); } if (overwrite) { Fs.rename(src, dst, copyIfFailed); } else { Fs.stat(dst, function(err) { if (!err) return callback(new Error("File " + dst + " exists.")); Fs.rename(src, dst, copyIfFailed); }); } }; var levels = { "info": ["\033[90m", "\033[39m"], // grey "error": ["\033[31m", "\033[39m"], // red "fatal": ["\033[35m", "\033[39m"], // magenta "exit": ["\033[36m", "\033[39m"] // cyan }; var _slice = Array.prototype.slice; exports.log = function() { var args = _slice.call(arguments); var lastArg = args[args.length - 1]; var level = levels[lastArg] ? args.pop() : "info"; if (!args.length) return; var msg = args.map(function(arg) { return typeof arg != "string" ? Util.inspect(arg) : arg; }).join(" "); var pfx = levels[level][0] + "[" + level + "]" + levels[level][1]; msg.split("\n").forEach(function(line) { console.log(pfx + " " + line); }); };