From 2cfa32c40959dd8c66eadc5bd4426f95134770d2 Mon Sep 17 00:00:00 2001 From: SarthakDudhe Date: Wed, 6 May 2026 20:05:25 +0530 Subject: [PATCH 1/2] Add Last-Modified header for GET and HEAD --- lib/handlers/get.mjs | 17 ++++--- lib/ldp.mjs | 6 +-- test/integration/http-test.mjs | 86 ++++++++++++++++++++++------------ 3 files changed, 70 insertions(+), 39 deletions(-) diff --git a/lib/handlers/get.mjs b/lib/handlers/get.mjs index a76b42527..b65ca616e 100644 --- a/lib/handlers/get.mjs +++ b/lib/handlers/get.mjs @@ -77,13 +77,16 @@ export default async function handler (req, res, next) { let contentRange let chunksize - if (ret) { - stream = ret.stream - contentType = ret.contentType - container = ret.container - contentRange = ret.contentRange - chunksize = ret.chunksize - } + if (ret) { + stream = ret.stream + contentType = ret.contentType + container = ret.container + contentRange = ret.contentRange + chunksize = ret.chunksize + if (ret.modified) { + res.header('Last-Modified', ret.modified.toUTCString()) + } + } // Till here it must exist if (!includeBody) { diff --git a/lib/ldp.mjs b/lib/ldp.mjs index 83dc25904..83f5f9264 100644 --- a/lib/ldp.mjs +++ b/lib/ldp.mjs @@ -451,7 +451,7 @@ class LDP { } if (!options.includeBody) { - return { stream: stats, contentType, container: stats.isDirectory() } + return { stream: stats, contentType, container: stats.isDirectory(), modified: stats.mtime } } if (stats.isDirectory()) { @@ -465,7 +465,7 @@ class LDP { throw err } const stream = stringToStream(data) - return { stream, contentType, container: true } + return { stream, contentType, container: true, modified: stats.mtime } } else { let chunksize, contentRange, start, end if (options.range) { @@ -487,7 +487,7 @@ class LDP { }) .on('open', function () { debug.handlers(`GET -- Reading ${pathLocal}`) - return resolve({ stream, contentType, container: false, contentRange, chunksize }) + return resolve({ stream, contentType, container: false, contentRange, chunksize, modified: stats.mtime }) }) })) } diff --git a/test/integration/http-test.mjs b/test/integration/http-test.mjs index cac2c886f..9c4b944c9 100644 --- a/test/integration/http-test.mjs +++ b/test/integration/http-test.mjs @@ -9,9 +9,9 @@ import { assert, expect } from 'chai' const __filename = fileURLToPath(import.meta.url) const __dirname = path.dirname(__filename) -const suffixAcl = '.acl' -const suffixMeta = '.meta' -const server = setupSupertestServer({ +const suffixAcl = '.acl' +const suffixMeta = '.meta' +const server = setupSupertestServer({ live: true, dataBrowserPath: 'default', root: path.join(__dirname, '../resources'), @@ -253,13 +253,20 @@ describe('HTTP APIs', function () { .expect('content-type', /text\/turtle/) .expect('Access-Control-Allow-Origin', 'http://example.com') .expect(200, done) - }) - it('should have set Link as resource', function (done) { - server.get('/sampleContainer2/example1.ttl') - .expect('content-type', /text\/turtle/) - .expect('Link', /; rel="type"/) - .expect(200, done) - }) + }) + it('should have set Link as resource', function (done) { + server.get('/sampleContainer2/example1.ttl') + .expect('content-type', /text\/turtle/) + .expect('Link', /; rel="type"/) + .expect(200, done) + }) + it('should have set Last-Modified for resource', function (done) { + const modified = fs.statSync(path.join(__dirname, + '../resources/sampleContainer2/example1.ttl')).mtime.toUTCString() + server.get('/sampleContainer2/example1.ttl') + .expect('Last-Modified', modified) + .expect(200, done) + }) it('should have set Updates-Via to use WebSockets', function (done) { server.get('/sampleContainer2/example1.ttl') .expect('updates-via', /wss?:\/\//) @@ -273,13 +280,20 @@ describe('HTTP APIs', function () { .expect(hasHeader('describedBy', 'example1.ttl' + suffixMeta)) .end(done) }) - it('should have set Link as Container/BasicContainer', function (done) { - server.get('/sampleContainer2/') - .expect('content-type', /text\/turtle/) - .expect('Link', /; rel="type"/) - .expect('Link', /; rel="type"/) - .expect(200, done) - }) + it('should have set Link as Container/BasicContainer', function (done) { + server.get('/sampleContainer2/') + .expect('content-type', /text\/turtle/) + .expect('Link', /; rel="type"/) + .expect('Link', /; rel="type"/) + .expect(200, done) + }) + it('should have set Last-Modified for container', function (done) { + const modified = fs.statSync(path.join(__dirname, + '../resources/sampleContainer2')).mtime.toUTCString() + server.get('/sampleContainer2/') + .expect('Last-Modified', modified) + .expect(200, done) + }) it('should load skin (mashlib) if resource was requested as text/html', function (done) { server.get('/sampleContainer2/example1.ttl') .set('Accept', 'text/html') @@ -505,11 +519,18 @@ describe('HTTP APIs', function () { .expect('updates-via', /wss?:\/\//) .expect(200, done) }) - it('should have set Link as Resource', function (done) { - server.head('/sampleContainer2/example1.ttl') - .expect('Link', /; rel="type"/) - .expect(200, done) - }) + it('should have set Link as Resource', function (done) { + server.head('/sampleContainer2/example1.ttl') + .expect('Link', /; rel="type"/) + .expect(200, done) + }) + it('should have set Last-Modified for resource', function (done) { + const modified = fs.statSync(path.join(__dirname, + '../resources/sampleContainer2/example1.ttl')).mtime.toUTCString() + server.head('/sampleContainer2/example1.ttl') + .expect('Last-Modified', modified) + .expect(200, done) + }) it('should have set acl and describedBy Links for resource', function (done) { server.head('/sampleContainer2/example1.ttl') @@ -523,13 +544,20 @@ describe('HTTP APIs', function () { .expect('Content-Type', /text\/turtle/) .expect(200, done) }) - it('should have set Link as Container/BasicContainer', - function (done) { - server.head('/sampleContainer2/') - .expect('Link', /; rel="type"/) - .expect('Link', /; rel="type"/) - .expect(200, done) - }) + it('should have set Link as Container/BasicContainer', + function (done) { + server.head('/sampleContainer2/') + .expect('Link', /; rel="type"/) + .expect('Link', /; rel="type"/) + .expect(200, done) + }) + it('should have set Last-Modified for container', function (done) { + const modified = fs.statSync(path.join(__dirname, + '../resources/sampleContainer2')).mtime.toUTCString() + server.head('/sampleContainer2/') + .expect('Last-Modified', modified) + .expect(200, done) + }) it('should have set acl and describedBy Links for container', function (done) { server.head('/sampleContainer2/') From d11aed7ef4d7a7b1db4726e124791c7b274d1854 Mon Sep 17 00:00:00 2001 From: SarthakDudhe Date: Thu, 7 May 2026 19:12:15 +0530 Subject: [PATCH 2/2] Enable WebID-TLS integration tests --- .github/workflows/ci.yml | 4 ++++ test/integration/acl-tls-test.mjs | 10 +++++----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bc7a972e3..5d9eca059 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,6 +49,10 @@ jobs: uses: actions/setup-node@v1 with: node-version: ${{ matrix.node-version }} + - name: Setup hosts for TLS tests + run: | + echo "127.0.0.1 tim.localhost" | sudo tee -a /etc/hosts + echo "127.0.0.1 nicola.localhost" | sudo tee -a /etc/hosts - run: npm ci # test code - run: npm run lint diff --git a/test/integration/acl-tls-test.mjs b/test/integration/acl-tls-test.mjs index 0ee4f6828..9c9f841ca 100644 --- a/test/integration/acl-tls-test.mjs +++ b/test/integration/acl-tls-test.mjs @@ -64,7 +64,7 @@ const userCredentials = { // a webid-tls client certificate can still use their certificate (and not a // username/password pair or other login method) to "bridge" from webid-tls to // webid-oidc. -describe.skip('ACL with WebID+TLS', function () { +describe('ACL with WebID+TLS', function () { let ldpHttpsServer const serverConfig = { root: rootPath, @@ -131,7 +131,7 @@ describe.skip('ACL with WebID+TLS', function () { }) }) - it.skip('should return a 401 and WWW-Authenticate header without credentials', (done) => { + it('should return a 401 and WWW-Authenticate header without credentials', (done) => { rm('.acl') const options = { url: address + '/acl-tls/no-acl/', @@ -568,7 +568,7 @@ describe.skip('ACL with WebID+TLS', function () { }) }) - describe.skip('Glob', function () { + describe('Glob', function () { it('user2 should be able to send glob request', function (done) { const options = createOptions(globFile, 'user2') request.get(options, function (error, response, body) { @@ -613,7 +613,7 @@ describe.skip('ACL with WebID+TLS', function () { done() }) }) - it.skip('user1 should be able to PATCH a resource', function (done) { + it('user1 should be able to PATCH a resource', function (done) { const options = createOptions('/acl-tls/append-inherited/test.ttl', 'user1') options.headers = { 'content-type': 'application/sparql-update' @@ -943,7 +943,7 @@ describe.skip('ACL with WebID+TLS', function () { // }) }) - describe.skip('Cleanup', function () { + describe('Cleanup', function () { it('should remove all files and dirs created', function (done) { try { // must remove the ACLs in sync