diff --git a/CHANGELOG.md b/CHANGELOG.md index 96af439e5..c5d04b211 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -100,6 +100,9 @@ * `newOp()`: Deprecated in favor of the new `Op` class. * The `AuthorManager.getAuthor4Token()` function is deprecated; use the new `AuthorManager.getAuthorId()` function instead. +* The exported database records covered by the `exportEtherpadAdditionalContent` + server-side hook now include keys like `${customPrefix}:${padId}:*`, not just + `${customPrefix}:${padId}`. # 1.8.18 diff --git a/doc/api/hooks_server-side.md b/doc/api/hooks_server-side.md index c12f42b20..b51ed5877 100644 --- a/doc/api/hooks_server-side.md +++ b/doc/api/hooks_server-side.md @@ -952,10 +952,10 @@ Called from `src/node/utils/ExportEtherpad.js` and Called when exporting to an `.etherpad` file or when importing from an `.etherpad` file. The hook function should return prefixes for pad-specific -records that should be included in the export/import. On export, each -`${prefix}:${padId}` record (but not `${prefix}:${padId}:*` records) are -included in the generated `.etherpad` file. On import, all `${prefix}:${padId}` -and `${prefix}:${padId}:*` records are loaded into the database. +records that should be included in the export/import. On export, all +`${prefix}:${padId}` and `${prefix}:${padId}:*` records are included in the +generated `.etherpad` file. On import, all `${prefix}:${padId}` and +`${prefix}:${padId}:*` records are loaded into the database. Context properties: None. diff --git a/src/node/utils/ExportEtherpad.js b/src/node/utils/ExportEtherpad.js index 6f5eb474c..a036b9318 100644 --- a/src/node/utils/ExportEtherpad.js +++ b/src/node/utils/ExportEtherpad.js @@ -15,6 +15,7 @@ * limitations under the License. */ +const assert = require('assert').strict; const authorManager = require('../db/AuthorManager'); const hooks = require('../../static/js/pluginfw/hooks'); const padManager = require('../db/PadManager'); @@ -34,7 +35,14 @@ exports.getPadRaw = async (padId, readOnlyId) => { for (let i = 0; i <= pad.chatHead; ++i) data[`${pfx}:chat:${i}`] = await pad.getChatMessage(i); const prefixes = await hooks.aCallAll('exportEtherpadAdditionalContent'); await Promise.all(prefixes.map(async (prefix) => { - data[`${prefix}:${readOnlyId || padId}`] = await pad.db.get(`${prefix}:${padId}`); + const srcPfx = `${prefix}:${padId}`; + const dstPfx = `${prefix}:${readOnlyId || padId}`; + data[dstPfx] = await pad.db.get(srcPfx); + assert(!srcPfx.includes('*')); + for (const k of await pad.db.findKeys(`${srcPfx}:*`, null)) { + assert(k.startsWith(`${srcPfx}:`)); + data[`${dstPfx}:${k.slice(srcPfx.length + 1)}`] = await pad.db.get(k); + } })); return data; }; diff --git a/src/tests/backend/specs/ExportEtherpad.js b/src/tests/backend/specs/ExportEtherpad.js index 8a4c056cc..e66bb4633 100644 --- a/src/tests/backend/specs/ExportEtherpad.js +++ b/src/tests/backend/specs/ExportEtherpad.js @@ -30,24 +30,38 @@ describe(__filename, function () { it('exports custom records', async function () { const pad = await padManager.getPad(padId); await pad.db.set(`custom:${padId}`, 'a'); + await pad.db.set(`custom:${padId}:`, 'b'); + await pad.db.set(`custom:${padId}:foo`, 'c'); const data = await exportEtherpad.getPadRaw(pad.id, null); assert.equal(data[`custom:${padId}`], 'a'); + assert.equal(data[`custom:${padId}:`], 'b'); + assert.equal(data[`custom:${padId}:foo`], 'c'); }); it('export from read-only pad uses read-only ID', async function () { const pad = await padManager.getPad(padId); const readOnlyId = await readOnlyManager.getReadOnlyId(padId); await pad.db.set(`custom:${padId}`, 'a'); + await pad.db.set(`custom:${padId}:`, 'b'); + await pad.db.set(`custom:${padId}:foo`, 'c'); const data = await exportEtherpad.getPadRaw(padId, readOnlyId); assert.equal(data[`custom:${readOnlyId}`], 'a'); + assert.equal(data[`custom:${readOnlyId}:`], 'b'); + assert.equal(data[`custom:${readOnlyId}:foo`], 'c'); assert(!(`custom:${padId}` in data)); + assert(!(`custom:${padId}:` in data)); + assert(!(`custom:${padId}:foo` in data)); }); it('does not export records from pad with similar ID', async function () { const pad = await padManager.getPad(padId); await pad.db.set(`custom:${padId}x`, 'a'); + await pad.db.set(`custom:${padId}x:`, 'b'); + await pad.db.set(`custom:${padId}x:foo`, 'c'); const data = await exportEtherpad.getPadRaw(pad.id, null); assert(!(`custom:${padId}x` in data)); + assert(!(`custom:${padId}x:` in data)); + assert(!(`custom:${padId}x:foo` in data)); }); }); });