mirror of
https://github.com/ether/etherpad-lite.git
synced 2025-01-31 19:02:59 +01:00
Merge branch 'develop' of github.com:ether/etherpad-lite into npm
This commit is contained in:
commit
b911d7adcf
20 changed files with 347 additions and 51 deletions
8
.github/workflows/backend-tests.yml
vendored
8
.github/workflows/backend-tests.yml
vendored
|
@ -18,7 +18,7 @@ jobs:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
node: [14, 16, 18]
|
node: [16, 18, 20]
|
||||||
steps:
|
steps:
|
||||||
-
|
-
|
||||||
name: Checkout repository
|
name: Checkout repository
|
||||||
|
@ -55,7 +55,7 @@ jobs:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
node: [14, 16, 18]
|
node: [16, 18, 20]
|
||||||
steps:
|
steps:
|
||||||
-
|
-
|
||||||
name: Checkout repository
|
name: Checkout repository
|
||||||
|
@ -124,7 +124,7 @@ jobs:
|
||||||
-
|
-
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 14
|
node-version: 20
|
||||||
cache: 'npm'
|
cache: 'npm'
|
||||||
cache-dependency-path: |
|
cache-dependency-path: |
|
||||||
src/package-lock.json
|
src/package-lock.json
|
||||||
|
@ -157,7 +157,7 @@ jobs:
|
||||||
-
|
-
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 14
|
node-version: 20
|
||||||
cache: 'npm'
|
cache: 'npm'
|
||||||
cache-dependency-path: |
|
cache-dependency-path: |
|
||||||
src/package-lock.json
|
src/package-lock.json
|
||||||
|
|
2
.github/workflows/frontend-admin-tests.yml
vendored
2
.github/workflows/frontend-admin-tests.yml
vendored
|
@ -14,7 +14,7 @@ jobs:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
node: [14, 16, 18]
|
node: [16, 18, 20]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
-
|
-
|
||||||
|
|
6
.github/workflows/frontend-tests.yml
vendored
6
.github/workflows/frontend-tests.yml
vendored
|
@ -34,7 +34,7 @@ jobs:
|
||||||
-
|
-
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 14
|
node-version: 20
|
||||||
cache: 'npm'
|
cache: 'npm'
|
||||||
cache-dependency-path: |
|
cache-dependency-path: |
|
||||||
src/package-lock.json
|
src/package-lock.json
|
||||||
|
@ -98,7 +98,7 @@ jobs:
|
||||||
-
|
-
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 14
|
node-version: 20
|
||||||
cache: 'npm'
|
cache: 'npm'
|
||||||
cache-dependency-path: |
|
cache-dependency-path: |
|
||||||
src/package-lock.json
|
src/package-lock.json
|
||||||
|
@ -126,7 +126,7 @@ jobs:
|
||||||
# Etherpad core dependencies must be installed after installing the
|
# Etherpad core dependencies must be installed after installing the
|
||||||
# plugin's dependencies, otherwise npm will try to hoist common
|
# plugin's dependencies, otherwise npm will try to hoist common
|
||||||
# dependencies by removing them from src/node_modules and installing them
|
# dependencies by removing them from src/node_modules and installing them
|
||||||
# in the top-level node_modules. As of v6.14.10, npm's hoist logic appears
|
# in the top-level node_modules. As of v6.20.10, npm's hoist logic appears
|
||||||
# to be buggy, because it sometimes removes dependencies from
|
# to be buggy, because it sometimes removes dependencies from
|
||||||
# src/node_modules but fails to add them to the top-level node_modules.
|
# src/node_modules but fails to add them to the top-level node_modules.
|
||||||
# Even if npm correctly hoists the dependencies, the hoisting seems to
|
# Even if npm correctly hoists the dependencies, the hoisting seems to
|
||||||
|
|
2
.github/workflows/lint-package-lock.yml
vendored
2
.github/workflows/lint-package-lock.yml
vendored
|
@ -22,7 +22,7 @@ jobs:
|
||||||
-
|
-
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 14
|
node-version: 20
|
||||||
cache: 'npm'
|
cache: 'npm'
|
||||||
cache-dependency-path: |
|
cache-dependency-path: |
|
||||||
src/package-lock.json
|
src/package-lock.json
|
||||||
|
|
6
.github/workflows/load-test.yml
vendored
6
.github/workflows/load-test.yml
vendored
|
@ -22,7 +22,7 @@ jobs:
|
||||||
-
|
-
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 14
|
node-version: 20
|
||||||
cache: 'npm'
|
cache: 'npm'
|
||||||
cache-dependency-path: |
|
cache-dependency-path: |
|
||||||
src/package-lock.json
|
src/package-lock.json
|
||||||
|
@ -52,7 +52,7 @@ jobs:
|
||||||
-
|
-
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 14
|
node-version: 20
|
||||||
cache: 'npm'
|
cache: 'npm'
|
||||||
cache-dependency-path: |
|
cache-dependency-path: |
|
||||||
src/package-lock.json
|
src/package-lock.json
|
||||||
|
@ -109,7 +109,7 @@ jobs:
|
||||||
-
|
-
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 14
|
node-version: 20
|
||||||
cache: 'npm'
|
cache: 'npm'
|
||||||
cache-dependency-path: |
|
cache-dependency-path: |
|
||||||
src/package-lock.json
|
src/package-lock.json
|
||||||
|
|
2
.github/workflows/rate-limit.yml
vendored
2
.github/workflows/rate-limit.yml
vendored
|
@ -22,7 +22,7 @@ jobs:
|
||||||
-
|
-
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 14
|
node-version: 20
|
||||||
cache: 'npm'
|
cache: 'npm'
|
||||||
cache-dependency-path: |
|
cache-dependency-path: |
|
||||||
src/package-lock.json
|
src/package-lock.json
|
||||||
|
|
|
@ -18,7 +18,7 @@ jobs:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
node: [14, 16, 18]
|
node: [16, 18, 20]
|
||||||
steps:
|
steps:
|
||||||
-
|
-
|
||||||
name: Check out latest release
|
name: Check out latest release
|
||||||
|
|
4
.github/workflows/windows.yml
vendored
4
.github/workflows/windows.yml
vendored
|
@ -28,7 +28,7 @@ jobs:
|
||||||
-
|
-
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 14
|
node-version: 20
|
||||||
cache: 'npm'
|
cache: 'npm'
|
||||||
cache-dependency-path: |
|
cache-dependency-path: |
|
||||||
src/package-lock.json
|
src/package-lock.json
|
||||||
|
@ -108,7 +108,7 @@ jobs:
|
||||||
-
|
-
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 14
|
node-version: 20
|
||||||
cache: 'npm'
|
cache: 'npm'
|
||||||
cache-dependency-path: |
|
cache-dependency-path: |
|
||||||
etherpad/src/package-lock.json
|
etherpad/src/package-lock.json
|
||||||
|
|
11
CHANGELOG.md
11
CHANGELOG.md
|
@ -1,3 +1,14 @@
|
||||||
|
# Next release
|
||||||
|
|
||||||
|
### Notable enhancements and fixes
|
||||||
|
|
||||||
|
* Security
|
||||||
|
* Limit requested revisions in timeslider and export to head revision. (affects v1.9.0)
|
||||||
|
|
||||||
|
* Bugfixes
|
||||||
|
* revisions in `CHANGESET_REQ` (timeslider) and export (txt, html, custom)
|
||||||
|
are now checked to be numbers.
|
||||||
|
|
||||||
# 1.9.0
|
# 1.9.0
|
||||||
|
|
||||||
### Notable enhancements and fixes
|
### Notable enhancements and fixes
|
||||||
|
|
|
@ -46,7 +46,7 @@ We're looking for maintainers and have some funding available. Please contact J
|
||||||
|
|
||||||
### Requirements
|
### Requirements
|
||||||
|
|
||||||
[Node.js](https://nodejs.org/) >= **14.0.0**.
|
[Node.js](https://nodejs.org/) >= **16.20.1**.
|
||||||
|
|
||||||
### GNU/Linux and other UNIX-like systems
|
### GNU/Linux and other UNIX-like systems
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,7 @@ const exportTxt = require('../utils/ExportTxt');
|
||||||
const importHtml = require('../utils/ImportHtml');
|
const importHtml = require('../utils/ImportHtml');
|
||||||
const cleanText = require('./Pad').cleanText;
|
const cleanText = require('./Pad').cleanText;
|
||||||
const PadDiff = require('../utils/padDiff');
|
const PadDiff = require('../utils/padDiff');
|
||||||
|
const { checkValidRev, isInt } = require('../utils/checkValidRev');
|
||||||
|
|
||||||
/* ********************
|
/* ********************
|
||||||
* GROUP FUNCTIONS ****
|
* GROUP FUNCTIONS ****
|
||||||
|
@ -777,6 +778,13 @@ exports.createDiffHTML = async (padID, startRev, endRev) => {
|
||||||
|
|
||||||
// get the pad
|
// get the pad
|
||||||
const pad = await getPadSafe(padID, true);
|
const pad = await getPadSafe(padID, true);
|
||||||
|
const headRev = pad.getHeadRevisionNumber();
|
||||||
|
if (startRev > headRev)
|
||||||
|
startRev = headRev;
|
||||||
|
|
||||||
|
if (endRev > headRev)
|
||||||
|
endRev = headRev;
|
||||||
|
|
||||||
let padDiff;
|
let padDiff;
|
||||||
try {
|
try {
|
||||||
padDiff = new PadDiff(pad, startRev, endRev);
|
padDiff = new PadDiff(pad, startRev, endRev);
|
||||||
|
@ -822,9 +830,6 @@ exports.getStats = async () => {
|
||||||
** INTERNAL HELPER FUNCTIONS *
|
** INTERNAL HELPER FUNCTIONS *
|
||||||
**************************** */
|
**************************** */
|
||||||
|
|
||||||
// checks if a number is an int
|
|
||||||
const isInt = (value) => (parseFloat(value) === parseInt(value, 10)) && !isNaN(value);
|
|
||||||
|
|
||||||
// gets a pad safe
|
// gets a pad safe
|
||||||
const getPadSafe = async (padID, shouldExist, text, authorId = '') => {
|
const getPadSafe = async (padID, shouldExist, text, authorId = '') => {
|
||||||
// check if padID is a string
|
// check if padID is a string
|
||||||
|
@ -854,31 +859,6 @@ const getPadSafe = async (padID, shouldExist, text, authorId = '') => {
|
||||||
return padManager.getPad(padID, text, authorId);
|
return padManager.getPad(padID, text, authorId);
|
||||||
};
|
};
|
||||||
|
|
||||||
// checks if a rev is a legal number
|
|
||||||
// pre-condition is that `rev` is not undefined
|
|
||||||
const checkValidRev = (rev) => {
|
|
||||||
if (typeof rev !== 'number') {
|
|
||||||
rev = parseInt(rev, 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if rev is a number
|
|
||||||
if (isNaN(rev)) {
|
|
||||||
throw new CustomError('rev is not a number', 'apierror');
|
|
||||||
}
|
|
||||||
|
|
||||||
// ensure this is not a negative number
|
|
||||||
if (rev < 0) {
|
|
||||||
throw new CustomError('rev is not a negative number', 'apierror');
|
|
||||||
}
|
|
||||||
|
|
||||||
// ensure this is not a float value
|
|
||||||
if (!isInt(rev)) {
|
|
||||||
throw new CustomError('rev is a float value', 'apierror');
|
|
||||||
}
|
|
||||||
|
|
||||||
return rev;
|
|
||||||
};
|
|
||||||
|
|
||||||
// checks if a padID is part of a group
|
// checks if a padID is part of a group
|
||||||
const checkGroupPad = (padID, field) => {
|
const checkGroupPad = (padID, field) => {
|
||||||
// ensure this is a group pad
|
// ensure this is a group pad
|
||||||
|
|
|
@ -172,6 +172,9 @@ class Pad {
|
||||||
|
|
||||||
async getInternalRevisionAText(targetRev) {
|
async getInternalRevisionAText(targetRev) {
|
||||||
const keyRev = this.getKeyRevisionNumber(targetRev);
|
const keyRev = this.getKeyRevisionNumber(targetRev);
|
||||||
|
const headRev = this.getHeadRevisionNumber();
|
||||||
|
if (targetRev > headRev)
|
||||||
|
targetRev = headRev;
|
||||||
const [keyAText, changesets] = await Promise.all([
|
const [keyAText, changesets] = await Promise.all([
|
||||||
this._getKeyRevisionAText(keyRev),
|
this._getKeyRevisionAText(keyRev),
|
||||||
Promise.all(
|
Promise.all(
|
||||||
|
|
|
@ -29,6 +29,7 @@ const os = require('os');
|
||||||
const hooks = require('../../static/js/pluginfw/hooks');
|
const hooks = require('../../static/js/pluginfw/hooks');
|
||||||
const TidyHtml = require('../utils/TidyHtml');
|
const TidyHtml = require('../utils/TidyHtml');
|
||||||
const util = require('util');
|
const util = require('util');
|
||||||
|
const { checkValidRev } = require('../utils/checkValidRev');
|
||||||
|
|
||||||
const fsp_writeFile = util.promisify(fs.writeFile);
|
const fsp_writeFile = util.promisify(fs.writeFile);
|
||||||
const fsp_unlink = util.promisify(fs.unlink);
|
const fsp_unlink = util.promisify(fs.unlink);
|
||||||
|
@ -53,6 +54,12 @@ exports.doExport = async (req, res, padId, readOnlyId, type) => {
|
||||||
// tell the browser that this is a downloadable file
|
// tell the browser that this is a downloadable file
|
||||||
res.attachment(`${fileName}.${type}`);
|
res.attachment(`${fileName}.${type}`);
|
||||||
|
|
||||||
|
if (req.params.rev !== undefined) {
|
||||||
|
// ensure revision is a number
|
||||||
|
// modify req, as we use it in a later call to exportConvert
|
||||||
|
req.params.rev = checkValidRev(req.params.rev);
|
||||||
|
}
|
||||||
|
|
||||||
// if this is a plain text export, we can do this directly
|
// 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
|
// We have to over engineer this because tabs are stored as attributes and not plain text
|
||||||
if (type === 'etherpad') {
|
if (type === 'etherpad') {
|
||||||
|
|
|
@ -39,6 +39,7 @@ const stats = require('../stats');
|
||||||
const assert = require('assert').strict;
|
const assert = require('assert').strict;
|
||||||
const {RateLimiterMemory} = require('rate-limiter-flexible');
|
const {RateLimiterMemory} = require('rate-limiter-flexible');
|
||||||
const webaccess = require('../hooks/express/webaccess');
|
const webaccess = require('../hooks/express/webaccess');
|
||||||
|
const { checkValidRev } = require('../utils/checkValidRev');
|
||||||
|
|
||||||
let rateLimiter;
|
let rateLimiter;
|
||||||
let socketio = null;
|
let socketio = null;
|
||||||
|
@ -1076,10 +1077,14 @@ const handleChangesetRequest = async (socket, {data: {granularity, start, reques
|
||||||
if (granularity == null) throw new Error('missing granularity');
|
if (granularity == null) throw new Error('missing granularity');
|
||||||
if (!Number.isInteger(granularity)) throw new Error('granularity is not an integer');
|
if (!Number.isInteger(granularity)) throw new Error('granularity is not an integer');
|
||||||
if (start == null) throw new Error('missing start');
|
if (start == null) throw new Error('missing start');
|
||||||
|
start = checkValidRev(start);
|
||||||
if (requestID == null) throw new Error('mising requestID');
|
if (requestID == null) throw new Error('mising requestID');
|
||||||
const end = start + (100 * granularity);
|
const end = start + (100 * granularity);
|
||||||
const {padId, author: authorId} = sessioninfos[socket.id];
|
const {padId, author: authorId} = sessioninfos[socket.id];
|
||||||
const pad = await padManager.getPad(padId, null, authorId);
|
const pad = await padManager.getPad(padId, null, authorId);
|
||||||
|
const headRev = pad.getHeadRevisionNumber();
|
||||||
|
if (start > headRev)
|
||||||
|
start = headRev;
|
||||||
const data = await getChangesetInfo(pad, start, end, granularity);
|
const data = await getChangesetInfo(pad, start, end, granularity);
|
||||||
data.requestID = requestID;
|
data.requestID = requestID;
|
||||||
socket.json.send({type: 'CHANGESET_REQ', data});
|
socket.json.send({type: 'CHANGESET_REQ', data});
|
||||||
|
|
|
@ -21,10 +21,14 @@
|
||||||
|
|
||||||
const AttributeMap = require('../../static/js/AttributeMap');
|
const AttributeMap = require('../../static/js/AttributeMap');
|
||||||
const Changeset = require('../../static/js/Changeset');
|
const Changeset = require('../../static/js/Changeset');
|
||||||
|
const { checkValidRev } = require('./checkValidRev');
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This method seems unused in core and no plugins depend on it
|
||||||
|
*/
|
||||||
exports.getPadPlainText = (pad, revNum) => {
|
exports.getPadPlainText = (pad, revNum) => {
|
||||||
const _analyzeLine = exports._analyzeLine;
|
const _analyzeLine = exports._analyzeLine;
|
||||||
const atext = ((revNum !== undefined) ? pad.getInternalRevisionAText(revNum) : pad.atext);
|
const atext = ((revNum !== undefined) ? pad.getInternalRevisionAText(checkValidRev(revNum)) : pad.atext);
|
||||||
const textLines = atext.text.slice(0, -1).split('\n');
|
const textLines = atext.text.slice(0, -1).split('\n');
|
||||||
const attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text);
|
const attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text);
|
||||||
const apool = pad.pool;
|
const apool = pad.pool;
|
||||||
|
|
34
src/node/utils/checkValidRev.js
Normal file
34
src/node/utils/checkValidRev.js
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const CustomError = require('../utils/customError');
|
||||||
|
|
||||||
|
// checks if a rev is a legal number
|
||||||
|
// pre-condition is that `rev` is not undefined
|
||||||
|
const checkValidRev = (rev) => {
|
||||||
|
if (typeof rev !== 'number') {
|
||||||
|
rev = parseInt(rev, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if rev is a number
|
||||||
|
if (isNaN(rev)) {
|
||||||
|
throw new CustomError('rev is not a number', 'apierror');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensure this is not a negative number
|
||||||
|
if (rev < 0) {
|
||||||
|
throw new CustomError('rev is not a negative number', 'apierror');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensure this is not a float value
|
||||||
|
if (!isInt(rev)) {
|
||||||
|
throw new CustomError('rev is a float value', 'apierror');
|
||||||
|
}
|
||||||
|
|
||||||
|
return rev;
|
||||||
|
};
|
||||||
|
|
||||||
|
// checks if a number is an int
|
||||||
|
const isInt = (value) => (parseFloat(value) === parseInt(value, 10)) && !isNaN(value);
|
||||||
|
|
||||||
|
exports.isInt = isInt;
|
||||||
|
exports.checkValidRev = checkValidRev;
|
6
src/package-lock.json
generated
6
src/package-lock.json
generated
|
@ -7331,9 +7331,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"semver": {
|
"semver": {
|
||||||
"version": "7.5.2",
|
"version": "7.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz",
|
||||||
"integrity": "sha512-SoftuTROv/cRjCze/scjGyiDtcUyxw1rgYQSZY7XTmtR5hX+dm76iDbTH8TkLPHCQmlbQVSSbNZCPM2hb0knnQ==",
|
"integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"lru-cache": "^6.0.0"
|
"lru-cache": "^6.0.0"
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,7 +61,7 @@
|
||||||
"request": "2.88.2",
|
"request": "2.88.2",
|
||||||
"resolve": "1.22.2",
|
"resolve": "1.22.2",
|
||||||
"security": "1.0.0",
|
"security": "1.0.0",
|
||||||
"semver": "^7.5.2",
|
"semver": "^7.5.3",
|
||||||
"socket.io": "^2.4.1",
|
"socket.io": "^2.4.1",
|
||||||
"superagent": "^8.0.9",
|
"superagent": "^8.0.9",
|
||||||
"terser": "^5.18.1",
|
"terser": "^5.18.1",
|
||||||
|
@ -93,7 +93,7 @@
|
||||||
"typescript": "^4.9.5"
|
"typescript": "^4.9.5"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14.15.0",
|
"node": ">=16.20.1",
|
||||||
"npm": ">=6.14.0"
|
"npm": ">=6.14.0"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|
|
@ -447,6 +447,175 @@ describe(__filename, function () {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('revisions are supported in txt and html export', function () {
|
||||||
|
const makeGoodExport = () => ({
|
||||||
|
'pad:testing': {
|
||||||
|
atext: {
|
||||||
|
text: 'oofoo\n',
|
||||||
|
attribs: '|1+6',
|
||||||
|
},
|
||||||
|
pool: {
|
||||||
|
numToAttrib: {
|
||||||
|
0: ['author', 'a.foo'],
|
||||||
|
},
|
||||||
|
nextNum: 1,
|
||||||
|
},
|
||||||
|
head: 2,
|
||||||
|
savedRevisions: [],
|
||||||
|
},
|
||||||
|
'globalAuthor:a.foo': {
|
||||||
|
colorId: '#000000',
|
||||||
|
name: 'author foo',
|
||||||
|
timestamp: 1598747784631,
|
||||||
|
padIDs: 'testing',
|
||||||
|
},
|
||||||
|
'pad:testing:revs:0': {
|
||||||
|
changeset: 'Z:1>3+3$foo',
|
||||||
|
meta: {
|
||||||
|
author: 'a.foo',
|
||||||
|
timestamp: 1597632398288,
|
||||||
|
pool: {
|
||||||
|
nextNum: 1,
|
||||||
|
numToAttrib: {
|
||||||
|
0: ['author', 'a.foo'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
atext: {
|
||||||
|
text: 'foo\n',
|
||||||
|
attribs: '|1+4',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'pad:testing:revs:1': {
|
||||||
|
changeset: 'Z:4>1+1$o',
|
||||||
|
meta: {
|
||||||
|
author: 'a.foo',
|
||||||
|
timestamp: 1597632398288,
|
||||||
|
pool: {
|
||||||
|
nextNum: 1,
|
||||||
|
numToAttrib: {
|
||||||
|
0: ['author', 'a.foo'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
atext: {
|
||||||
|
text: 'fooo\n',
|
||||||
|
attribs: '*0|1+5',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'pad:testing:revs:2': {
|
||||||
|
changeset: 'Z:5>1+1$o',
|
||||||
|
meta: {
|
||||||
|
author: 'a.foo',
|
||||||
|
timestamp: 1597632398288,
|
||||||
|
pool: {
|
||||||
|
numToAttrib: {},
|
||||||
|
nextNum: 0,
|
||||||
|
},
|
||||||
|
atext: {
|
||||||
|
text: 'foooo\n',
|
||||||
|
attribs: '*0|1+6',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const importEtherpad = (records) => agent.post(`/p/${testPadId}/import`)
|
||||||
|
.attach('file', Buffer.from(JSON.stringify(records), 'utf8'), {
|
||||||
|
filename: '/test.etherpad',
|
||||||
|
contentType: 'application/etherpad',
|
||||||
|
});
|
||||||
|
|
||||||
|
before(async function () {
|
||||||
|
// makeGoodExport() is assumed to produce good .etherpad records. Verify that assumption so
|
||||||
|
// that a buggy makeGoodExport() doesn't cause checks to accidentally pass.
|
||||||
|
const records = makeGoodExport();
|
||||||
|
await deleteTestPad();
|
||||||
|
await importEtherpad(records)
|
||||||
|
.expect(200)
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect((res) => assert.deepEqual(res.body, {
|
||||||
|
code: 0,
|
||||||
|
message: 'ok',
|
||||||
|
data: {directDatabaseAccess: true},
|
||||||
|
}));
|
||||||
|
await agent.get(`/p/${testPadId}/export/txt`)
|
||||||
|
.expect(200)
|
||||||
|
.buffer(true).parse(superagent.parse.text)
|
||||||
|
.expect((res) => assert.equal(res.text, 'oofoo\n'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('txt request rev 1', async function () {
|
||||||
|
await agent.get(`/p/${testPadId}/1/export/txt`)
|
||||||
|
.expect(200)
|
||||||
|
.buffer(true).parse(superagent.parse.text)
|
||||||
|
.expect((res) => assert.equal(res.text, 'ofoo\n'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('txt request rev 2', async function () {
|
||||||
|
await agent.get(`/p/${testPadId}/2/export/txt`)
|
||||||
|
.expect(200)
|
||||||
|
.buffer(true).parse(superagent.parse.text)
|
||||||
|
.expect((res) => assert.equal(res.text, 'oofoo\n'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('txt request rev 1test returns rev 1', async function () {
|
||||||
|
await agent.get(`/p/${testPadId}/1test/export/txt`)
|
||||||
|
.expect(200)
|
||||||
|
.buffer(true).parse(superagent.parse.text)
|
||||||
|
.expect((res) => assert.equal(res.text, 'ofoo\n'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('txt request rev test1 is 403', async function () {
|
||||||
|
await agent.get(`/p/${testPadId}/test1/export/txt`)
|
||||||
|
.expect(500)
|
||||||
|
.buffer(true).parse(superagent.parse.text)
|
||||||
|
.expect((res) => assert.match(res.text, /rev is not a number/));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('txt request rev 5 returns head rev', async function () {
|
||||||
|
await agent.get(`/p/${testPadId}/5/export/txt`)
|
||||||
|
.expect(200)
|
||||||
|
.buffer(true).parse(superagent.parse.text)
|
||||||
|
.expect((res) => assert.equal(res.text, 'oofoo\n'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('html request rev 1', async function () {
|
||||||
|
await agent.get(`/p/${testPadId}/1/export/html`)
|
||||||
|
.expect(200)
|
||||||
|
.buffer(true).parse(superagent.parse.text)
|
||||||
|
.expect((res) => assert.match(res.text, /ofoo<br>/));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('html request rev 2', async function () {
|
||||||
|
await agent.get(`/p/${testPadId}/2/export/html`)
|
||||||
|
.expect(200)
|
||||||
|
.buffer(true).parse(superagent.parse.text)
|
||||||
|
.expect((res) => assert.match(res.text, /oofoo<br>/));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('html request rev 1test returns rev 1', async function () {
|
||||||
|
await agent.get(`/p/${testPadId}/1test/export/html`)
|
||||||
|
.expect(200)
|
||||||
|
.buffer(true).parse(superagent.parse.text)
|
||||||
|
.expect((res) => assert.match(res.text, /ofoo<br>/));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('html request rev test1 results in 500 response', async function () {
|
||||||
|
await agent.get(`/p/${testPadId}/test1/export/html`)
|
||||||
|
.expect(500)
|
||||||
|
.buffer(true).parse(superagent.parse.text)
|
||||||
|
.expect((res) => assert.match(res.text, /rev is not a number/));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('html request rev 5 returns head rev', async function () {
|
||||||
|
await agent.get(`/p/${testPadId}/5/export/html`)
|
||||||
|
.expect(200)
|
||||||
|
.buffer(true).parse(superagent.parse.text)
|
||||||
|
.expect((res) => assert.match(res.text, /oofoo<br>/));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('Import authorization checks', function () {
|
describe('Import authorization checks', function () {
|
||||||
let authorize;
|
let authorize;
|
||||||
|
|
||||||
|
|
|
@ -77,6 +77,89 @@ describe(__filename, function () {
|
||||||
await otherPad.remove();
|
await otherPad.remove();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('CHANGESET_REQ: verify revNum is a number (regression)', async function () {
|
||||||
|
const otherPadId = `${padId}other`;
|
||||||
|
assert(!await padManager.doesPadExist(otherPadId));
|
||||||
|
const otherPad = await padManager.getPad(otherPadId, 'other text\n');
|
||||||
|
let errorCatched = 0;
|
||||||
|
try {
|
||||||
|
await otherPad.setText('other text\n');
|
||||||
|
await common.sendMessage(roSocket, {
|
||||||
|
component: 'pad',
|
||||||
|
padId: otherPadId, // The server should ignore this.
|
||||||
|
type: 'CHANGESET_REQ',
|
||||||
|
data: {
|
||||||
|
granularity: 1,
|
||||||
|
start: 'test123',
|
||||||
|
requestID: 'requestId',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
assert.equal('This code should never run', 1);
|
||||||
|
}
|
||||||
|
catch(e) {
|
||||||
|
assert.match(e.message, /rev is not a number/);
|
||||||
|
errorCatched = 1;
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
await otherPad.remove();
|
||||||
|
assert.equal(errorCatched, 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('CHANGESET_REQ: revNum is converted to number if possible (regression)', async function () {
|
||||||
|
const otherPadId = `${padId}other`;
|
||||||
|
assert(!await padManager.doesPadExist(otherPadId));
|
||||||
|
const otherPad = await padManager.getPad(otherPadId, 'other text\n');
|
||||||
|
try {
|
||||||
|
await otherPad.setText('other text\n');
|
||||||
|
const resP = common.waitForSocketEvent(roSocket, 'message');
|
||||||
|
await common.sendMessage(roSocket, {
|
||||||
|
component: 'pad',
|
||||||
|
padId: otherPadId, // The server should ignore this.
|
||||||
|
type: 'CHANGESET_REQ',
|
||||||
|
data: {
|
||||||
|
granularity: 1,
|
||||||
|
start: '1test123',
|
||||||
|
requestID: 'requestId',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const res = await resP;
|
||||||
|
assert.equal(res.type, 'CHANGESET_REQ');
|
||||||
|
assert.equal(res.data.requestID, 'requestId');
|
||||||
|
assert.equal(res.data.start, 1);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
await otherPad.remove();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('CHANGESET_REQ: revNum 2 is converted to head rev 1 (regression)', async function () {
|
||||||
|
const otherPadId = `${padId}other`;
|
||||||
|
assert(!await padManager.doesPadExist(otherPadId));
|
||||||
|
const otherPad = await padManager.getPad(otherPadId, 'other text\n');
|
||||||
|
try {
|
||||||
|
await otherPad.setText('other text\n');
|
||||||
|
const resP = common.waitForSocketEvent(roSocket, 'message');
|
||||||
|
await common.sendMessage(roSocket, {
|
||||||
|
component: 'pad',
|
||||||
|
padId: otherPadId, // The server should ignore this.
|
||||||
|
type: 'CHANGESET_REQ',
|
||||||
|
data: {
|
||||||
|
granularity: 1,
|
||||||
|
start: '2',
|
||||||
|
requestID: 'requestId',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const res = await resP;
|
||||||
|
assert.equal(res.type, 'CHANGESET_REQ');
|
||||||
|
assert.equal(res.data.requestID, 'requestId');
|
||||||
|
assert.equal(res.data.start, 1);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
await otherPad.remove();
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('USER_CHANGES', function () {
|
describe('USER_CHANGES', function () {
|
||||||
|
|
Loading…
Reference in a new issue