diff --git a/server-ce/hotfix/3.5.10/Dockerfile b/server-ce/hotfix/3.5.10/Dockerfile new file mode 100644 index 0000000000..ae09f8975c --- /dev/null +++ b/server-ce/hotfix/3.5.10/Dockerfile @@ -0,0 +1,9 @@ +FROM sharelatex/sharelatex:3.5.9 + +# Patch: clear invite and invite tokens through the websocket +COPY pr_13427.patch . +RUN patch -p0 < pr_13427.patch + +# Patch: https://github.com/Automattic/mongoose/commit/f1efabf350522257364aa5c2cb36e441cf08f1a2 +COPY mongoose_proto.patch . +RUN patch -p0 < mongoose_proto.patch diff --git a/server-ce/hotfix/3.5.10/mongoose_proto.patch b/server-ce/hotfix/3.5.10/mongoose_proto.patch new file mode 100644 index 0000000000..37559dbe56 --- /dev/null +++ b/server-ce/hotfix/3.5.10/mongoose_proto.patch @@ -0,0 +1,12 @@ +--- node_modules/mongoose/lib/document.js ++++ node_modules/mongoose/lib/document.js +@@ -689,6 +689,10 @@ function init(self, obj, doc, opts, prefix) { + + function _init(index) { + i = keys[index]; ++ // avoid prototype pollution ++ if (i === '__proto__' || i === 'constructor') { ++ return; ++ } + path = prefix + i; + schema = self.$__schema.path(path); diff --git a/server-ce/hotfix/3.5.10/pr_13427.patch b/server-ce/hotfix/3.5.10/pr_13427.patch new file mode 100644 index 0000000000..716b7ce4a4 --- /dev/null +++ b/server-ce/hotfix/3.5.10/pr_13427.patch @@ -0,0 +1,92 @@ +--- services/web/app/src/Features/Editor/EditorHttpController.js ++++ services/web/app/src/Features/Editor/EditorHttpController.js +@@ -73,6 +73,7 @@ async function joinProject(req, res, next) { + if (isRestrictedUser) { + project.owner = { _id: project.owner._id } + project.members = [] ++ project.invites = [] + } + // Only show the 'renamed or deleted' message once + if (project.deletedByExternalDataSource) { +--- services/web/app/src/Features/Project/ProjectEditorHandler.js ++++ services/web/app/src/Features/Project/ProjectEditorHandler.js +@@ -48,19 +48,13 @@ + deletedDocsFromDocstore + ), + members: [], +- invites, ++ invites: this.buildInvitesView(invites), + imageName: + project.imageName != null + ? Path.basename(project.imageName) + : undefined, + } + +- if (result.invites == null) { +- result.invites = [] +- } +- result.invites.forEach(invite => { +- delete invite.token +- }) + ;({ owner, ownerFeatures, members } = + this.buildOwnerAndMembersViews(members)) + result.owner = owner +@@ -99,7 +93,7 @@ + let owner = null + let ownerFeatures = null + const filteredMembers = [] +- for (const member of Array.from(members || [])) { ++ for (const member of members || []) { + if (member.privilegeLevel === 'owner') { + ownerFeatures = member.user.features + owner = this.buildUserModelView(member.user, 'owner') +@@ -128,24 +122,15 @@ + }, + + buildFolderModelView(folder) { +- let file + const fileRefs = _.filter(folder.fileRefs || [], file => file != null) + return { + _id: folder._id, + name: folder.name, +- folders: Array.from(folder.folders || []).map(childFolder => ++ folders: (folder.folders || []).map(childFolder => + this.buildFolderModelView(childFolder) + ), +- fileRefs: (() => { +- const result = [] +- for (file of Array.from(fileRefs)) { +- result.push(this.buildFileModelView(file)) +- } +- return result +- })(), +- docs: Array.from(folder.docs || []).map(doc => +- this.buildDocModelView(doc) +- ), ++ fileRefs: fileRefs.map(file => this.buildFileModelView(file)), ++ docs: (folder.docs || []).map(doc => this.buildDocModelView(doc)), + } + }, + +@@ -164,4 +149,21 @@ + name: doc.name, + } + }, ++ ++ buildInvitesView(invites) { ++ if (invites == null) { ++ return [] ++ } ++ return invites.map(invite => ++ _.pick(invite, [ ++ '_id', ++ 'createdAt', ++ 'email', ++ 'expires', ++ 'privileges', ++ 'projectId', ++ 'sendingUserId', ++ ]) ++ ) ++ }, + } diff --git a/server-ce/hotfix/4.0.5/Dockerfile b/server-ce/hotfix/4.0.5/Dockerfile new file mode 100644 index 0000000000..f62bd566a6 --- /dev/null +++ b/server-ce/hotfix/4.0.5/Dockerfile @@ -0,0 +1,13 @@ +FROM sharelatex/sharelatex:4.0.4 + +# Patch: clear invite and invite tokens through the websocket +COPY pr_13427.patch . +RUN patch -p0 < pr_13427.patch + +# Patch: https://github.com/Automattic/mongoose/commit/f1efabf350522257364aa5c2cb36e441cf08f1a2 +COPY mongoose_proto.patch . +RUN patch -p0 < mongoose_proto.patch + +# Patch: Allow digits in PDF filenames +COPY pr_13122.patch . +RUN patch -p0 < pr_13122.patch diff --git a/server-ce/hotfix/4.0.5/mongoose_proto.patch b/server-ce/hotfix/4.0.5/mongoose_proto.patch new file mode 100644 index 0000000000..d519ad9f9f --- /dev/null +++ b/server-ce/hotfix/4.0.5/mongoose_proto.patch @@ -0,0 +1,12 @@ +--- services/web/node_modules/mongoose/lib/document.js ++++ services/web/node_modules/mongoose/lib/document.js +@@ -739,6 +739,10 @@ function init(self, obj, doc, opts, prefix) { + + function _init(index) { + i = keys[index]; ++ // avoid prototype pollution ++ if (i === '__proto__' || i === 'constructor') { ++ return; ++ } + path = prefix + i; + schemaType = docSchema.path(path); diff --git a/server-ce/hotfix/4.0.5/pr_13122.patch b/server-ce/hotfix/4.0.5/pr_13122.patch new file mode 100644 index 0000000000..3a1a5ffca3 --- /dev/null +++ b/server-ce/hotfix/4.0.5/pr_13122.patch @@ -0,0 +1,11 @@ +--- services/web/app/src/Features/Compile/CompileController.js ++++ services/web/app/src/Features/Compile/CompileController.js +@@ -371,7 +371,7 @@ module.exports = CompileController = { + }, + + _getSafeProjectName(project) { +- return project.name.replace(/\P{L}/gu, '_') ++ return project.name.replace(/[^\p{L}\p{Nd}]/gu, '_') + }, + + deleteAuxFiles(req, res, next) { diff --git a/server-ce/hotfix/4.0.5/pr_13427.patch b/server-ce/hotfix/4.0.5/pr_13427.patch new file mode 100644 index 0000000000..716b7ce4a4 --- /dev/null +++ b/server-ce/hotfix/4.0.5/pr_13427.patch @@ -0,0 +1,92 @@ +--- services/web/app/src/Features/Editor/EditorHttpController.js ++++ services/web/app/src/Features/Editor/EditorHttpController.js +@@ -73,6 +73,7 @@ async function joinProject(req, res, next) { + if (isRestrictedUser) { + project.owner = { _id: project.owner._id } + project.members = [] ++ project.invites = [] + } + // Only show the 'renamed or deleted' message once + if (project.deletedByExternalDataSource) { +--- services/web/app/src/Features/Project/ProjectEditorHandler.js ++++ services/web/app/src/Features/Project/ProjectEditorHandler.js +@@ -48,19 +48,13 @@ + deletedDocsFromDocstore + ), + members: [], +- invites, ++ invites: this.buildInvitesView(invites), + imageName: + project.imageName != null + ? Path.basename(project.imageName) + : undefined, + } + +- if (result.invites == null) { +- result.invites = [] +- } +- result.invites.forEach(invite => { +- delete invite.token +- }) + ;({ owner, ownerFeatures, members } = + this.buildOwnerAndMembersViews(members)) + result.owner = owner +@@ -99,7 +93,7 @@ + let owner = null + let ownerFeatures = null + const filteredMembers = [] +- for (const member of Array.from(members || [])) { ++ for (const member of members || []) { + if (member.privilegeLevel === 'owner') { + ownerFeatures = member.user.features + owner = this.buildUserModelView(member.user, 'owner') +@@ -128,24 +122,15 @@ + }, + + buildFolderModelView(folder) { +- let file + const fileRefs = _.filter(folder.fileRefs || [], file => file != null) + return { + _id: folder._id, + name: folder.name, +- folders: Array.from(folder.folders || []).map(childFolder => ++ folders: (folder.folders || []).map(childFolder => + this.buildFolderModelView(childFolder) + ), +- fileRefs: (() => { +- const result = [] +- for (file of Array.from(fileRefs)) { +- result.push(this.buildFileModelView(file)) +- } +- return result +- })(), +- docs: Array.from(folder.docs || []).map(doc => +- this.buildDocModelView(doc) +- ), ++ fileRefs: fileRefs.map(file => this.buildFileModelView(file)), ++ docs: (folder.docs || []).map(doc => this.buildDocModelView(doc)), + } + }, + +@@ -164,4 +149,21 @@ + name: doc.name, + } + }, ++ ++ buildInvitesView(invites) { ++ if (invites == null) { ++ return [] ++ } ++ return invites.map(invite => ++ _.pick(invite, [ ++ '_id', ++ 'createdAt', ++ 'email', ++ 'expires', ++ 'privileges', ++ 'projectId', ++ 'sendingUserId', ++ ]) ++ ) ++ }, + }