From 1f7fcd54f14028eb9e596beb75bb5410ce83ca1c Mon Sep 17 00:00:00 2001 From: John McLear Date: Tue, 6 May 2014 21:13:22 +0100 Subject: [PATCH 1/8] working logic for allowing a server side hook to modify the export file name but NOT the extension --- src/node/handler/ExportHandler.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/node/handler/ExportHandler.js b/src/node/handler/ExportHandler.js index 8ff5bc488..b3b6f7568 100644 --- a/src/node/handler/ExportHandler.js +++ b/src/node/handler/ExportHandler.js @@ -27,6 +27,7 @@ var async = require("async"); var fs = require("fs"); var settings = require('../utils/Settings'); var os = require('os'); +var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks"); //load abiword only if its enabled if(settings.abiword != null) @@ -45,8 +46,13 @@ if(os.type().indexOf("Windows") > -1) */ exports.doExport = function(req, res, padId, type) { + // allow fileName to be overwritten by a hook, the type type is kept static for security reasons + var fileName = hooks.callAll("exportFileName", padId); + // if fileName is not set then set it to the padId, note that fileName is returned as an array. + if(!fileName[0]) fileName = padId; + //tell the browser that this is a downloadable file - res.attachment(padId + "." + type); + res.attachment(fileName + "." + type); //if this is a plain text export, we can do this directly // We have to over engineer this because tabs are stored as attributes and not plain text @@ -81,7 +87,7 @@ exports.doExport = function(req, res, padId, type) //ensure html can be collected by the garbage collector txt = null; - destFile = tempDirectory + "/eplite_export_" + randNum + "." + type; + destFile = tempDirectory + "/etherpad_export_" + randNum + "." + type; abiword.convertFile(srcFile, destFile, type, callback); }, //send the file @@ -168,7 +174,7 @@ exports.doExport = function(req, res, padId, type) else //write the html export to a file { randNum = Math.floor(Math.random()*0xFFFFFFFF); - srcFile = tempDirectory + "/eplite_export_" + randNum + ".html"; + srcFile = tempDirectory + "/etherpad_export_" + randNum + ".html"; fs.writeFile(srcFile, html, callback); } }, @@ -178,7 +184,7 @@ exports.doExport = function(req, res, padId, type) //ensure html can be collected by the garbage collector html = null; - destFile = tempDirectory + "/eplite_export_" + randNum + "." + type; + destFile = tempDirectory + "/etherpad_export_" + randNum + "." + type; abiword.convertFile(srcFile, destFile, type, callback); }, //send the file From a8d9a3868d9e2d2319f62bf19e9906947fae726a Mon Sep 17 00:00:00 2001 From: John McLear Date: Tue, 6 May 2014 21:22:03 +0100 Subject: [PATCH 2/8] docs for new hook --- doc/api/hooks_server-side.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/doc/api/hooks_server-side.md b/doc/api/hooks_server-side.md index 0bde2aad4..0c251c3ef 100644 --- a/doc/api/hooks_server-side.md +++ b/doc/api/hooks_server-side.md @@ -247,3 +247,19 @@ Things in context: This hook will allow a plug-in developer to re-write each line when exporting to HTML. +## exportFileName +Called from src/node/handler/ExportHandler.js + +Things in context: + +1. padId + +This hook will allow a plug-in developer to modify the file name of an exported pad. This is useful if you want to export a pad under another name and/or hide the padId under export. Note that the doctype or file extension cannot be modified for security reasons. + +Example: + +``` +exports.exportFileName = function(hook, padId){ + return "newFileName"+padId; +} +``` From 69ba3cc104e919ef51bc619e3b8a1630b3f11616 Mon Sep 17 00:00:00 2001 From: John McLear Date: Wed, 7 May 2014 15:17:14 +0100 Subject: [PATCH 3/8] remove eplite add etherpad --- src/node/handler/ImportHandler.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/node/handler/ImportHandler.js b/src/node/handler/ImportHandler.js index f58076bb4..154637892 100644 --- a/src/node/handler/ImportHandler.js +++ b/src/node/handler/ImportHandler.js @@ -98,7 +98,7 @@ exports.doImport = function(req, res, padId) } }, function(callback){ - destFile = path.join(tmpDirectory, "eplite_import_" + randNum + ".htm"); + destFile = path.join(tmpDirectory, "etherpad_import_" + randNum + ".htm"); // Logic for allowing external Import Plugins hooks.aCallAll("import", {srcFile: srcFile, destFile: destFile}, function(err, result){ From ec6dc4fa0ee785c7d80420ef706af7b87dab3218 Mon Sep 17 00:00:00 2001 From: John McLear Date: Wed, 7 May 2014 16:27:41 +0100 Subject: [PATCH 4/8] make async, please check --- src/node/handler/ExportHandler.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/node/handler/ExportHandler.js b/src/node/handler/ExportHandler.js index b3b6f7568..54b41c46c 100644 --- a/src/node/handler/ExportHandler.js +++ b/src/node/handler/ExportHandler.js @@ -46,10 +46,15 @@ if(os.type().indexOf("Windows") > -1) */ exports.doExport = function(req, res, padId, type) { + var fileName = padId; + // allow fileName to be overwritten by a hook, the type type is kept static for security reasons - var fileName = hooks.callAll("exportFileName", padId); - // if fileName is not set then set it to the padId, note that fileName is returned as an array. - if(!fileName[0]) fileName = padId; + hooks.aCallAll("exportFileName", padId, + function(err, hookFileName){ + // if fileName is set then set it to the padId, note that fileName is returned as an array. + if(fileName[0]) fileName = hookFileName; + } + ); //tell the browser that this is a downloadable file res.attachment(fileName + "." + type); From d09e66e271cde2c80f9a0bcebdea5d5f286ba7a6 Mon Sep 17 00:00:00 2001 From: John McLear Date: Mon, 12 May 2014 15:08:32 +0100 Subject: [PATCH 5/8] use call first and update docs --- doc/api/hooks_server-side.md | 4 ++-- src/node/handler/ExportHandler.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/api/hooks_server-side.md b/doc/api/hooks_server-side.md index 0c251c3ef..51026140b 100644 --- a/doc/api/hooks_server-side.md +++ b/doc/api/hooks_server-side.md @@ -259,7 +259,7 @@ This hook will allow a plug-in developer to modify the file name of an exported Example: ``` -exports.exportFileName = function(hook, padId){ - return "newFileName"+padId; +exports.exportFileName = function(hook, padId, callback){ + callback("newFileName"+padId); } ``` diff --git a/src/node/handler/ExportHandler.js b/src/node/handler/ExportHandler.js index 54b41c46c..11d247d65 100644 --- a/src/node/handler/ExportHandler.js +++ b/src/node/handler/ExportHandler.js @@ -49,10 +49,10 @@ exports.doExport = function(req, res, padId, type) var fileName = padId; // allow fileName to be overwritten by a hook, the type type is kept static for security reasons - hooks.aCallAll("exportFileName", padId, + hooks.aCallFirst("exportFileName", padId, function(err, hookFileName){ // if fileName is set then set it to the padId, note that fileName is returned as an array. - if(fileName[0]) fileName = hookFileName; + if(hookFileName[0]) fileName = hookFileName[0]; } ); From a82823ede267490a2afb753cc70f69374be7349b Mon Sep 17 00:00:00 2001 From: John McLear Date: Mon, 12 May 2014 15:15:10 +0100 Subject: [PATCH 6/8] use full string not first char --- src/node/handler/ExportHandler.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/node/handler/ExportHandler.js b/src/node/handler/ExportHandler.js index 11d247d65..d0d8ddaa9 100644 --- a/src/node/handler/ExportHandler.js +++ b/src/node/handler/ExportHandler.js @@ -52,7 +52,7 @@ exports.doExport = function(req, res, padId, type) hooks.aCallFirst("exportFileName", padId, function(err, hookFileName){ // if fileName is set then set it to the padId, note that fileName is returned as an array. - if(hookFileName[0]) fileName = hookFileName[0]; + if(hookFileName[0]) fileName = hookFileName; } ); From c63cb812e85db4dc25a98b7ca704bdbe80d11162 Mon Sep 17 00:00:00 2001 From: John McLear Date: Thu, 15 May 2014 10:53:37 +0100 Subject: [PATCH 7/8] Update ExportHandler.js --- src/node/handler/ExportHandler.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/node/handler/ExportHandler.js b/src/node/handler/ExportHandler.js index d0d8ddaa9..3eb53408a 100644 --- a/src/node/handler/ExportHandler.js +++ b/src/node/handler/ExportHandler.js @@ -52,7 +52,7 @@ exports.doExport = function(req, res, padId, type) hooks.aCallFirst("exportFileName", padId, function(err, hookFileName){ // if fileName is set then set it to the padId, note that fileName is returned as an array. - if(hookFileName[0]) fileName = hookFileName; + if(hookFileName) fileName = hookFileName; } ); From 19be8763c942746740b5cc8969c3791211500ecd Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Thu, 15 May 2014 12:47:28 +0000 Subject: [PATCH 8/8] Fix race condition --- src/node/handler/ExportHandler.js | 345 +++++++++++++++--------------- 1 file changed, 173 insertions(+), 172 deletions(-) diff --git a/src/node/handler/ExportHandler.js b/src/node/handler/ExportHandler.js index 3eb53408a..a748d3f2e 100644 --- a/src/node/handler/ExportHandler.js +++ b/src/node/handler/ExportHandler.js @@ -53,178 +53,179 @@ exports.doExport = function(req, res, padId, type) function(err, hookFileName){ // if fileName is set then set it to the padId, note that fileName is returned as an array. if(hookFileName) fileName = hookFileName; + + + //tell the browser that this is a downloadable file + res.attachment(fileName + "." + type); + + //if this is a plain text export, we can do this directly + // We have to over engineer this because tabs are stored as attributes and not plain text + + if(type == "txt") + { + var txt; + var randNum; + var srcFile, destFile; + + async.series([ + //render the txt document + function(callback) + { + exporttxt.getPadTXTDocument(padId, req.params.rev, false, function(err, _txt) + { + if(ERR(err, callback)) return; + txt = _txt; + callback(); + }); + }, + //decide what to do with the txt export + function(callback) + { + //if this is a txt export, we can send this from here directly + res.send(txt); + callback("stop"); + }, + //send the convert job to abiword + function(callback) + { + //ensure html can be collected by the garbage collector + txt = null; + + destFile = tempDirectory + "/etherpad_export_" + randNum + "." + type; + abiword.convertFile(srcFile, destFile, type, callback); + }, + //send the file + function(callback) + { + res.sendfile(destFile, null, callback); + }, + //clean up temporary files + function(callback) + { + async.parallel([ + function(callback) + { + fs.unlink(srcFile, callback); + }, + function(callback) + { + //100ms delay to accomidate for slow windows fs + if(os.type().indexOf("Windows") > -1) + { + setTimeout(function() + { + fs.unlink(destFile, callback); + }, 100); + } + else + { + fs.unlink(destFile, callback); + } + } + ], callback); + } + ], function(err) + { + if(err && err != "stop") ERR(err); + }) + } + else if(type == 'dokuwiki') + { + var randNum; + var srcFile, destFile; + + async.series([ + //render the dokuwiki document + function(callback) + { + exportdokuwiki.getPadDokuWikiDocument(padId, req.params.rev, function(err, dokuwiki) + { + res.send(dokuwiki); + callback("stop"); + }); + }, + ], function(err) + { + if(err && err != "stop") throw err; + }); + } + else + { + var html; + var randNum; + var srcFile, destFile; + + async.series([ + //render the html document + function(callback) + { + exporthtml.getPadHTMLDocument(padId, req.params.rev, false, function(err, _html) + { + if(ERR(err, callback)) return; + html = _html; + callback(); + }); + }, + //decide what to do with the html export + function(callback) + { + //if this is a html export, we can send this from here directly + if(type == "html") + { + res.send(html); + callback("stop"); + } + else //write the html export to a file + { + randNum = Math.floor(Math.random()*0xFFFFFFFF); + srcFile = tempDirectory + "/etherpad_export_" + randNum + ".html"; + fs.writeFile(srcFile, html, callback); + } + }, + //send the convert job to abiword + function(callback) + { + //ensure html can be collected by the garbage collector + html = null; + + destFile = tempDirectory + "/etherpad_export_" + randNum + "." + type; + abiword.convertFile(srcFile, destFile, type, callback); + }, + //send the file + function(callback) + { + res.sendfile(destFile, null, callback); + }, + //clean up temporary files + function(callback) + { + async.parallel([ + function(callback) + { + fs.unlink(srcFile, callback); + }, + function(callback) + { + //100ms delay to accomidate for slow windows fs + if(os.type().indexOf("Windows") > -1) + { + setTimeout(function() + { + fs.unlink(destFile, callback); + }, 100); + } + else + { + fs.unlink(destFile, callback); + } + } + ], callback); + } + ], function(err) + { + if(err && err != "stop") ERR(err); + }) + } } ); - - //tell the browser that this is a downloadable file - res.attachment(fileName + "." + type); - - //if this is a plain text export, we can do this directly - // We have to over engineer this because tabs are stored as attributes and not plain text - - if(type == "txt") - { - var txt; - var randNum; - var srcFile, destFile; - - async.series([ - //render the txt document - function(callback) - { - exporttxt.getPadTXTDocument(padId, req.params.rev, false, function(err, _txt) - { - if(ERR(err, callback)) return; - txt = _txt; - callback(); - }); - }, - //decide what to do with the txt export - function(callback) - { - //if this is a txt export, we can send this from here directly - res.send(txt); - callback("stop"); - }, - //send the convert job to abiword - function(callback) - { - //ensure html can be collected by the garbage collector - txt = null; - - destFile = tempDirectory + "/etherpad_export_" + randNum + "." + type; - abiword.convertFile(srcFile, destFile, type, callback); - }, - //send the file - function(callback) - { - res.sendfile(destFile, null, callback); - }, - //clean up temporary files - function(callback) - { - async.parallel([ - function(callback) - { - fs.unlink(srcFile, callback); - }, - function(callback) - { - //100ms delay to accomidate for slow windows fs - if(os.type().indexOf("Windows") > -1) - { - setTimeout(function() - { - fs.unlink(destFile, callback); - }, 100); - } - else - { - fs.unlink(destFile, callback); - } - } - ], callback); - } - ], function(err) - { - if(err && err != "stop") ERR(err); - }) - } - else if(type == 'dokuwiki') - { - var randNum; - var srcFile, destFile; - - async.series([ - //render the dokuwiki document - function(callback) - { - exportdokuwiki.getPadDokuWikiDocument(padId, req.params.rev, function(err, dokuwiki) - { - res.send(dokuwiki); - callback("stop"); - }); - }, - ], function(err) - { - if(err && err != "stop") throw err; - }); - } - else - { - var html; - var randNum; - var srcFile, destFile; - - async.series([ - //render the html document - function(callback) - { - exporthtml.getPadHTMLDocument(padId, req.params.rev, false, function(err, _html) - { - if(ERR(err, callback)) return; - html = _html; - callback(); - }); - }, - //decide what to do with the html export - function(callback) - { - //if this is a html export, we can send this from here directly - if(type == "html") - { - res.send(html); - callback("stop"); - } - else //write the html export to a file - { - randNum = Math.floor(Math.random()*0xFFFFFFFF); - srcFile = tempDirectory + "/etherpad_export_" + randNum + ".html"; - fs.writeFile(srcFile, html, callback); - } - }, - //send the convert job to abiword - function(callback) - { - //ensure html can be collected by the garbage collector - html = null; - - destFile = tempDirectory + "/etherpad_export_" + randNum + "." + type; - abiword.convertFile(srcFile, destFile, type, callback); - }, - //send the file - function(callback) - { - res.sendfile(destFile, null, callback); - }, - //clean up temporary files - function(callback) - { - async.parallel([ - function(callback) - { - fs.unlink(srcFile, callback); - }, - function(callback) - { - //100ms delay to accomidate for slow windows fs - if(os.type().indexOf("Windows") > -1) - { - setTimeout(function() - { - fs.unlink(destFile, callback); - }, 100); - } - else - { - fs.unlink(destFile, callback); - } - } - ], callback); - } - ], function(err) - { - if(err && err != "stop") ERR(err); - }) - } };