Skip to content

Commit ad84c8a

Browse files
committed
Merge branch 'develop'
2 parents 55f76c5 + 37769cc commit ad84c8a

File tree

17 files changed

+239
-246
lines changed

17 files changed

+239
-246
lines changed

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,16 @@
1+
# 1.8.11
2+
3+
### Notable fixes
4+
5+
* Fix server crash issue within PadMessageHandler due to SocketIO handling
6+
* Fix editor issue with drop downs not being visible
7+
* Ensure correct version is passed when loading front end resources
8+
* Ensure underscore and jquery are available in original location for plugin comptability
9+
10+
### Notable enhancements
11+
12+
* Improved page load speeds
13+
114
# 1.8.10
215

316
### Security Patches

src/node/handler/PadMessageHandler.js

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1409,16 +1409,14 @@ const composePadChangesets = async (padId, startNum, endNum) => {
14091409
};
14101410

14111411
const _getRoomSockets = (padID) => {
1412-
const roomSockets = [];
1413-
const room = socketio.sockets.adapter.rooms[padID];
1414-
1415-
if (room) {
1416-
for (const id of Object.keys(room.sockets)) {
1417-
roomSockets.push(socketio.sockets.sockets[id]);
1418-
}
1419-
}
1420-
1421-
return roomSockets;
1412+
const ns = socketio.sockets; // Default namespace.
1413+
const adapter = ns.adapter;
1414+
// We could call adapter.clients(), but that method is unnecessarily asynchronous. Replicate what
1415+
// it does here, but synchronously to avoid a race condition. This code will have to change when
1416+
// we update to socket.io v3.
1417+
const room = adapter.rooms[padID];
1418+
if (!room) return [];
1419+
return Object.keys(room.sockets).map((id) => ns.connected[id]).filter((s) => s);
14221420
};
14231421

14241422
/**
@@ -1438,14 +1436,13 @@ exports.padUsers = async (padID) => {
14381436
await Promise.all(_getRoomSockets(padID).map(async (roomSocket) => {
14391437
const s = sessioninfos[roomSocket.id];
14401438
if (s) {
1441-
return authorManager.getAuthor(s.author).then((author) => {
1442-
// Fixes: https://github.com/ether/etherpad-lite/issues/4120
1443-
// On restart author might not be populated?
1444-
if (author) {
1445-
author.id = s.author;
1446-
padUsers.push(author);
1447-
}
1448-
});
1439+
const author = await authorManager.getAuthor(s.author);
1440+
// Fixes: https://github.com/ether/etherpad-lite/issues/4120
1441+
// On restart author might not be populated?
1442+
if (author) {
1443+
author.id = s.author;
1444+
padUsers.push(author);
1445+
}
14491446
}
14501447
}));
14511448

src/node/hooks/express/static.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ const getTar = async () => {
3131
exports.expressCreateServer = async (hookName, args) => {
3232
// Cache both minified and static.
3333
const assetCache = new CachingMiddleware();
34-
args.app.all(/\/javascripts\/(.*)/, assetCache.handle);
34+
args.app.all(/\/javascripts\/(.*)/, assetCache.handle.bind(assetCache));
3535

3636
// Minify will serve static files compressed (minify enabled). It also has
3737
// file-specific hacks for ace/require-kernel/etc.

src/node/utils/caching_middleware.js

Lines changed: 109 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,14 @@
1616
* limitations under the License.
1717
*/
1818

19-
const async = require('async');
2019
const Buffer = require('buffer').Buffer;
2120
const fs = require('fs');
21+
const fsp = fs.promises;
2222
const path = require('path');
2323
const zlib = require('zlib');
2424
const settings = require('./Settings');
2525
const existsSync = require('./path_exists');
26+
const util = require('util');
2627

2728
/*
2829
* The crypto module can be absent on reduced node installations.
@@ -77,146 +78,126 @@ if (_crypto) {
7778
should replace this.
7879
*/
7980

80-
function CachingMiddleware() {
81-
}
82-
CachingMiddleware.prototype = new function () {
83-
const handle = (req, res, next) => {
81+
module.exports = class CachingMiddleware {
82+
handle(req, res, next) {
83+
this._handle(req, res, next).catch((err) => next(err || new Error(err)));
84+
}
85+
86+
async _handle(req, res, next) {
8487
if (!(req.method === 'GET' || req.method === 'HEAD') || !CACHE_DIR) {
8588
return next(undefined, req, res);
8689
}
8790

88-
const old_req = {};
89-
const old_res = {};
91+
const oldReq = {};
92+
const oldRes = {};
9093

9194
const supportsGzip =
9295
(req.get('Accept-Encoding') || '').indexOf('gzip') !== -1;
9396

94-
const path = require('url').parse(req.url).path;
95-
const cacheKey = generateCacheKey(path);
97+
const url = new URL(req.url, 'http://localhost');
98+
const cacheKey = generateCacheKey(url.pathname + url.search);
9699

97-
fs.stat(`${CACHE_DIR}minified_${cacheKey}`, (error, stats) => {
98-
const modifiedSince = (req.headers['if-modified-since'] &&
99-
new Date(req.headers['if-modified-since']));
100-
const lastModifiedCache = !error && stats.mtime;
101-
if (lastModifiedCache && responseCache[cacheKey]) {
102-
req.headers['if-modified-since'] = lastModifiedCache.toUTCString();
103-
} else {
104-
delete req.headers['if-modified-since'];
100+
const stats = await fsp.stat(`${CACHE_DIR}minified_${cacheKey}`).catch(() => {});
101+
const modifiedSince =
102+
req.headers['if-modified-since'] && new Date(req.headers['if-modified-since']);
103+
if (stats != null && stats.mtime && responseCache[cacheKey]) {
104+
req.headers['if-modified-since'] = stats.mtime.toUTCString();
105+
} else {
106+
delete req.headers['if-modified-since'];
107+
}
108+
109+
// Always issue get to downstream.
110+
oldReq.method = req.method;
111+
req.method = 'GET';
112+
113+
// This handles read/write synchronization as well as its predecessor,
114+
// which is to say, not at all.
115+
// TODO: Implement locking on write or ditch caching of gzip and use
116+
// existing middlewares.
117+
const respond = () => {
118+
req.method = oldReq.method || req.method;
119+
res.write = oldRes.write || res.write;
120+
res.end = oldRes.end || res.end;
121+
122+
const headers = {};
123+
Object.assign(headers, (responseCache[cacheKey].headers || {}));
124+
const statusCode = responseCache[cacheKey].statusCode;
125+
126+
let pathStr = `${CACHE_DIR}minified_${cacheKey}`;
127+
if (supportsGzip && /application\/javascript/.test(headers['content-type'])) {
128+
pathStr += '.gz';
129+
headers['content-encoding'] = 'gzip';
105130
}
106131

107-
// Always issue get to downstream.
108-
old_req.method = req.method;
109-
req.method = 'GET';
132+
const lastModified = headers['last-modified'] && new Date(headers['last-modified']);
110133

111-
const expirationDate = new Date(((responseCache[cacheKey] || {}).headers || {}).expires);
112-
if (expirationDate > new Date()) {
113-
// Our cached version is still valid.
114-
return respond();
134+
if (statusCode === 200 && lastModified <= modifiedSince) {
135+
res.writeHead(304, headers);
136+
res.end();
137+
} else if (req.method === 'GET') {
138+
const readStream = fs.createReadStream(pathStr);
139+
res.writeHead(statusCode, headers);
140+
readStream.pipe(res);
141+
} else {
142+
res.writeHead(statusCode, headers);
143+
res.end();
115144
}
145+
};
116146

117-
const _headers = {};
118-
old_res.setHeader = res.setHeader;
119-
res.setHeader = (key, value) => {
120-
// Don't set cookies, see issue #707
121-
if (key.toLowerCase() === 'set-cookie') return;
122-
123-
_headers[key.toLowerCase()] = value;
124-
old_res.setHeader.call(res, key, value);
125-
};
126-
127-
old_res.writeHead = res.writeHead;
128-
res.writeHead = function (status, headers) {
129-
res.writeHead = old_res.writeHead;
130-
if (status === 200) {
131-
// Update cache
132-
let buffer = '';
133-
134-
Object.keys(headers || {}).forEach((key) => {
135-
res.setHeader(key, headers[key]);
136-
});
137-
headers = _headers;
138-
139-
old_res.write = res.write;
140-
old_res.end = res.end;
141-
res.write = function (data, encoding) {
142-
buffer += data.toString(encoding);
143-
};
144-
res.end = function (data, encoding) {
145-
async.parallel([
146-
function (callback) {
147-
const path = `${CACHE_DIR}minified_${cacheKey}`;
148-
fs.writeFile(path, buffer, (error, stats) => {
149-
callback();
150-
});
151-
},
152-
function (callback) {
153-
const path = `${CACHE_DIR}minified_${cacheKey}.gz`;
154-
zlib.gzip(buffer, (error, content) => {
155-
if (error) {
156-
callback();
157-
} else {
158-
fs.writeFile(path, content, (error, stats) => {
159-
callback();
160-
});
161-
}
162-
});
163-
},
164-
], () => {
165-
responseCache[cacheKey] = {statusCode: status, headers};
166-
respond();
167-
});
168-
};
169-
} else if (status === 304) {
170-
// Nothing new changed from the cached version.
171-
old_res.write = res.write;
172-
old_res.end = res.end;
173-
res.write = function (data, encoding) {};
174-
res.end = function (data, encoding) { respond(); };
175-
} else {
176-
res.writeHead(status, headers);
177-
}
178-
};
179-
180-
next(undefined, req, res);
181-
182-
// This handles read/write synchronization as well as its predecessor,
183-
// which is to say, not at all.
184-
// TODO: Implement locking on write or ditch caching of gzip and use
185-
// existing middlewares.
186-
function respond() {
187-
req.method = old_req.method || req.method;
188-
res.write = old_res.write || res.write;
189-
res.end = old_res.end || res.end;
190-
191-
const headers = {};
192-
Object.assign(headers, (responseCache[cacheKey].headers || {}));
193-
const statusCode = responseCache[cacheKey].statusCode;
194-
195-
let pathStr = `${CACHE_DIR}minified_${cacheKey}`;
196-
if (supportsGzip && /application\/javascript/.test(headers['content-type'])) {
197-
pathStr += '.gz';
198-
headers['content-encoding'] = 'gzip';
199-
}
200-
201-
const lastModified = (headers['last-modified'] &&
202-
new Date(headers['last-modified']));
203-
204-
if (statusCode === 200 && lastModified <= modifiedSince) {
205-
res.writeHead(304, headers);
206-
res.end();
207-
} else if (req.method === 'GET') {
208-
const readStream = fs.createReadStream(pathStr);
209-
res.writeHead(statusCode, headers);
210-
readStream.pipe(res);
211-
} else {
212-
res.writeHead(statusCode, headers);
213-
res.end();
214-
}
215-
}
216-
});
217-
};
147+
const expirationDate = new Date(((responseCache[cacheKey] || {}).headers || {}).expires);
148+
if (expirationDate > new Date()) {
149+
// Our cached version is still valid.
150+
return respond();
151+
}
218152

219-
this.handle = handle;
220-
}();
153+
const _headers = {};
154+
oldRes.setHeader = res.setHeader;
155+
res.setHeader = (key, value) => {
156+
// Don't set cookies, see issue #707
157+
if (key.toLowerCase() === 'set-cookie') return;
158+
159+
_headers[key.toLowerCase()] = value;
160+
oldRes.setHeader.call(res, key, value);
161+
};
162+
163+
oldRes.writeHead = res.writeHead;
164+
res.writeHead = (status, headers) => {
165+
res.writeHead = oldRes.writeHead;
166+
if (status === 200) {
167+
// Update cache
168+
let buffer = '';
169+
170+
Object.keys(headers || {}).forEach((key) => {
171+
res.setHeader(key, headers[key]);
172+
});
173+
headers = _headers;
174+
175+
oldRes.write = res.write;
176+
oldRes.end = res.end;
177+
res.write = (data, encoding) => {
178+
buffer += data.toString(encoding);
179+
};
180+
res.end = async (data, encoding) => {
181+
await Promise.all([
182+
fsp.writeFile(`${CACHE_DIR}minified_${cacheKey}`, buffer).catch(() => {}),
183+
util.promisify(zlib.gzip)(buffer)
184+
.then((content) => fsp.writeFile(`${CACHE_DIR}minified_${cacheKey}.gz`, content))
185+
.catch(() => {}),
186+
]);
187+
responseCache[cacheKey] = {statusCode: status, headers};
188+
respond();
189+
};
190+
} else if (status === 304) {
191+
// Nothing new changed from the cached version.
192+
oldRes.write = res.write;
193+
oldRes.end = res.end;
194+
res.write = (data, encoding) => {};
195+
res.end = (data, encoding) => { respond(); };
196+
} else {
197+
res.writeHead(status, headers);
198+
}
199+
};
221200

222-
module.exports = CachingMiddleware;
201+
next(undefined, req, res);
202+
}
203+
};

0 commit comments

Comments
 (0)