Files
overleaf-cep/services/clsi/test/unit/js/StaticServerForbidSymlinks.test.js
Andrew Rumble cd7da983d1 Merge pull request #30232 from overleaf/ar/convert-clsi-to-es-modules
[clsi] convert to ES modules

GitOrigin-RevId: fb7fa52cc8f678ee31be352e62a5dff95e88008b
2026-01-22 09:06:23 +00:00

271 lines
7.0 KiB
JavaScript

import { vi, describe, beforeEach, it } from 'vitest'
import path from 'node:path'
import sinon from 'sinon'
const modulePath = path.join(
import.meta.dirname,
'../../../app/js/StaticServerForbidSymlinks'
)
describe('StaticServerForbidSymlinks', function () {
beforeEach(async function (ctx) {
ctx.settings = {
path: {
compilesDir: '/compiles/here',
},
}
ctx.fs = {}
vi.doMock('@overleaf/settings', () => ({
default: ctx.settings,
}))
vi.doMock('fs', () => ({
default: ctx.fs,
}))
ctx.ForbidSymlinks = (await import(modulePath)).default
ctx.dummyStatic = (rootDir, options) => (req, res, next) =>
// console.log "dummyStatic serving file", rootDir, "called with", req.url
// serve it
next()
ctx.StaticServerForbidSymlinks = ctx.ForbidSymlinks(
ctx.dummyStatic,
ctx.settings.path.compilesDir
)
ctx.req = {
params: {
project_id: '12345',
},
}
ctx.res = {}
ctx.req.url = '/12345/output.pdf'
})
describe('sending a normal file through', function () {
beforeEach(function (ctx) {
ctx.fs.realpath = sinon
.stub()
.callsArgWith(
1,
null,
`${ctx.settings.path.compilesDir}/${ctx.req.params.project_id}/output.pdf`
)
})
it('should call next', async function (ctx) {
await new Promise((resolve, reject) => {
ctx.res.sendStatus = function (resCode) {
resCode.should.equal(200)
resolve()
}
ctx.StaticServerForbidSymlinks(ctx.req, ctx.res, err => {
if (err) reject(err)
resolve()
})
})
})
})
describe('with a missing file', function () {
beforeEach(function (ctx) {
ctx.fs.realpath = sinon
.stub()
.callsArgWith(
1,
{ code: 'ENOENT' },
`${ctx.settings.path.compilesDir}/${ctx.req.params.project_id}/unknown.pdf`
)
})
it('should send a 404', async function (ctx) {
await new Promise((resolve, reject) => {
ctx.res.sendStatus = function (resCode) {
resCode.should.equal(404)
resolve()
}
ctx.StaticServerForbidSymlinks(ctx.req, ctx.res)
})
})
})
describe('with a new line', function () {
beforeEach(function (ctx) {
ctx.req.url = '/12345/output.pdf\nother file'
ctx.fs.realpath = sinon.stub().yields()
})
it('should process the correct file', async function (ctx) {
await new Promise((resolve, reject) => {
ctx.res.sendStatus = () => {
ctx.fs.realpath.should.have.been.calledWith(
`${ctx.settings.path.compilesDir}/12345/output.pdf\nother file`
)
resolve()
}
ctx.StaticServerForbidSymlinks(ctx.req, ctx.res)
})
})
})
describe('with a symlink file', function () {
beforeEach(function (ctx) {
ctx.fs.realpath = sinon
.stub()
.callsArgWith(1, null, `/etc/${ctx.req.params.project_id}/output.pdf`)
})
it('should send a 404', async function (ctx) {
await new Promise((resolve, reject) => {
ctx.res.sendStatus = function (resCode) {
resCode.should.equal(404)
resolve()
}
ctx.StaticServerForbidSymlinks(ctx.req, ctx.res)
})
})
})
describe('with a relative file', function () {
beforeEach(function (ctx) {
ctx.req.url = '/12345/../67890/output.pdf'
})
it('should send a 404', async function (ctx) {
await new Promise((resolve, reject) => {
ctx.res.sendStatus = function (resCode) {
resCode.should.equal(404)
resolve()
}
ctx.StaticServerForbidSymlinks(ctx.req, ctx.res)
})
})
})
describe('with a unnormalized file containing .', function () {
beforeEach(function (ctx) {
ctx.req.url = '/12345/foo/./output.pdf'
})
it('should send a 404', async function (ctx) {
await new Promise((resolve, reject) => {
ctx.res.sendStatus = function (resCode) {
resCode.should.equal(404)
resolve()
}
ctx.StaticServerForbidSymlinks(ctx.req, ctx.res)
})
})
})
describe('with a file containing an empty path', function () {
beforeEach(function (ctx) {
ctx.req.url = '/12345/foo//output.pdf'
})
it('should send a 404', async function (ctx) {
await new Promise((resolve, reject) => {
ctx.res.sendStatus = function (resCode) {
resCode.should.equal(404)
resolve()
}
ctx.StaticServerForbidSymlinks(ctx.req, ctx.res)
})
})
})
describe('with a non-project file', function () {
beforeEach(function (ctx) {
ctx.req.url = '/.foo/output.pdf'
})
it('should send a 404', async function (ctx) {
await new Promise((resolve, reject) => {
ctx.res.sendStatus = function (resCode) {
resCode.should.equal(404)
resolve()
}
ctx.StaticServerForbidSymlinks(ctx.req, ctx.res)
})
})
})
describe('with a file outside the compiledir', function () {
beforeEach(function (ctx) {
ctx.req.url = '/../bar/output.pdf'
})
it('should send a 404', async function (ctx) {
await new Promise((resolve, reject) => {
ctx.res.sendStatus = function (resCode) {
resCode.should.equal(404)
resolve()
}
ctx.StaticServerForbidSymlinks(ctx.req, ctx.res)
})
})
})
describe('with a file with no leading /', function () {
beforeEach(function (ctx) {
ctx.req.url = './../bar/output.pdf'
})
it('should send a 404', async function (ctx) {
await new Promise((resolve, reject) => {
ctx.res.sendStatus = function (resCode) {
resCode.should.equal(404)
resolve()
}
ctx.StaticServerForbidSymlinks(ctx.req, ctx.res)
})
})
})
describe('with a github style path', function () {
beforeEach(function (ctx) {
ctx.req.url = '/henryoswald-latex_example/output/output.log'
ctx.fs.realpath = sinon
.stub()
.callsArgWith(
1,
null,
`${ctx.settings.path.compilesDir}/henryoswald-latex_example/output/output.log`
)
})
it('should call next', async function (ctx) {
await new Promise((resolve, reject) => {
ctx.res.sendStatus = function (resCode) {
resCode.should.equal(200)
resolve()
}
ctx.StaticServerForbidSymlinks(ctx.req, ctx.res, err => {
if (err) reject(err)
resolve()
})
})
})
})
describe('with an error from fs.realpath', function () {
beforeEach(function (ctx) {
ctx.fs.realpath = sinon.stub().callsArgWith(1, 'error')
})
it('should send a 500', async function (ctx) {
await new Promise((resolve, reject) => {
ctx.res.sendStatus = function (resCode) {
resCode.should.equal(500)
resolve()
}
ctx.StaticServerForbidSymlinks(ctx.req, ctx.res)
})
})
})
})