module.exports = function(RED) { var https = require('https'); var http = require('http'); var url = require('url'); var OPERATIONS = { // Read/List 'list': { method: 'PROPFIND', path: '/remote.php/dav/files/{user}/{folder}' }, 'get': { method: 'GET', path: '/remote.php/dav/files/{user}/{path}' }, // Write 'upload': { method: 'PUT', path: '/remote.php/dav/files/{user}/{path}' }, 'mkdir': { method: 'MKCOL', path: '/remote.php/dav/files/{user}/{folder}' }, // Modify 'move': { method: 'MOVE', path: '/remote.php/dav/files/{user}/{path}', destHeader: true }, 'copy': { method: 'COPY', path: '/remote.php/dav/files/{user}/{path}', destHeader: true }, // Delete 'delete': { method: 'DELETE', path: '/remote.php/dav/files/{user}/{path}' }, // OCS sharing 'listShares': { method: 'GET', path: '/ocs/v2.php/apps/files_sharing/api/v1/shares' }, 'createShare': { method: 'POST', path: '/ocs/v2.php/apps/files_sharing/api/v1/shares', body: ['path','shareType','permissions','shareWith'] }, 'deleteShare': { method: 'DELETE', path: '/ocs/v2.php/apps/files_sharing/api/v1/shares/{id}' }, // File info 'fileInfo': { method: 'GET', path: '/ocs/v2.php/apps/files/api/v1/stats' }, 'favorites': { method: 'GET', path: '/ocs/v2.php/apps/files/api/v1/files' } }; function FileOperationsNode(config) { RED.nodes.createNode(this, config); var node = this; this.configNode = RED.nodes.getNode(config.nextcloud); this.operation = config.operation || 'list'; this.ncUser = config.ncUser || ''; this.ncPath = config.ncPath || ''; this.ncFolder = config.ncFolder || ''; this.destination = config.destination || ''; this.bodySharePath = config.bodySharePath; this.bodyShareType = config.bodyShareType; this.bodyPermissions = config.bodyPermissions; this.bodyShareWith = config.bodyShareWith; if (!this.configNode) { node.error("No Nextcloud configuration node selected"); return; } node.on('input', function(msg) { var op = OPERATIONS[msg.operation || node.operation]; if (!op) { node.error("Unknown operation: " + (msg.operation || node.operation), msg); return; } var baseUrl = (node.configNode.baseUrl || '').replace(/\/+$/, ''); var username = node.configNode.credentials.username; var password = node.configNode.credentials.password; var ncUser = msg.ncUser || node.ncUser || username; var ncPath = msg.ncPath || node.ncPath || ''; var ncFolder = msg.ncFolder || node.ncFolder || ''; var dest = msg.destination || node.destination || ''; var path = op.path .replace('{user}', encodeURIComponent(ncUser)) .replace('{path}', ncPath) .replace('{folder}', ncFolder) .replace('{id}', msg.id || ''); var fullUrl = baseUrl + path; var parsedUrl = url.parse(fullUrl); var headers = { 'Authorization': 'Basic ' + Buffer.from(username + ':' + password).toString('base64') }; // WebDAV requests need depth header for PROPFIND if (op.method === 'PROPFIND') { headers['Depth'] = msg.depth || '1'; headers['Content-Type'] = 'application/xml'; } // MOVE/COPY use Destination header if (op.destHeader && dest) { headers['Destination'] = baseUrl + '/remote.php/dav/files/' + encodeURIComponent(ncUser) + '/' + dest; } // OCS sharing headers if (op.path.indexOf('/ocs/') === 0) { headers['OCS-APIRequest'] = 'true'; headers['Accept'] = 'application/json'; } if (msg.headers && typeof msg.headers === 'object') { Object.keys(msg.headers).forEach(function(k) { headers[k] = msg.headers[k]; }); } var bodyStr = null; if (op.body) { var bodyObj = {}; op.body.forEach(function(b) { var val = msg[b] !== undefined ? msg[b] : getBodyField(node, b); if (val !== undefined && val !== '') { bodyObj[b] = val; } }); if (Object.keys(bodyObj).length > 0) { headers['Content-Type'] = 'application/json'; bodyStr = JSON.stringify(bodyObj); } } if (msg.body) { headers['Content-Type'] = msg.contentType || 'application/json'; bodyStr = typeof msg.body === 'string' ? msg.body : JSON.stringify(msg.body); } if (msg.payload && op.method === 'PUT') { bodyStr = typeof msg.payload === 'string' ? msg.payload : JSON.stringify(msg.payload); } var opts = { hostname: parsedUrl.hostname, port: parsedUrl.port || (parsedUrl.protocol === 'https:' ? 443 : 80), path: parsedUrl.path, method: op.method, headers: headers, rejectUnauthorized: false }; var transport = parsedUrl.protocol === 'https:' ? https : http; node.log(op.method + ' ' + fullUrl); var req = transport.request(opts, function(res) { var body = ''; res.on('data', function(chunk) { body += chunk; }); res.on('end', function() { msg.statusCode = res.statusCode; msg.operation = msg.operation || node.operation; // Try JSON, fallback to XML/text try { msg.payload = JSON.parse(body); } catch(e) { // PROPFIND returns XML msg.payload = body; } node.send(msg); }); }); req.on('error', function(err) { msg.error = err.message; node.error(op.method + ' ' + fullUrl + ' — ' + err.message, msg); }); if (bodyStr) { req.write(bodyStr); } req.end(); }); } function getBodyField(node, name) { var map = { 'path': node.bodySharePath, 'shareType': node.bodyShareType, 'permissions': node.bodyPermissions, 'shareWith': node.bodyShareWith }; return map[name]; } RED.nodes.registerType("file-operations", FileOperationsNode); };