Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix/issue#1692 #1778

Merged
merged 21 commits into from
Apr 3, 2024
Merged
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
16.14.0
v18.19.0
38 changes: 32 additions & 6 deletions lib/ldp.js
Original file line number Diff line number Diff line change
@@ -137,6 +137,7 @@ class LDP {
}

async post (hostname, containerPath, stream, { container, slug, extension, contentType }) {
console.log('container:', container)
// POST without content type is forbidden
if (!contentType) {
throw error(400,
@@ -145,18 +146,29 @@ class LDP {

const ldp = this
debug.handlers('POST -- On parent: ' + containerPath)
// prepare slug
if (container) {
// Containers should not receive an extension
extension = ''
}
// pepare slug
if (slug) {
if (this.isAuxResource(slug, extension)) throw error(403, 'POST is not allowed for auxiliary resources')
slug = decodeURIComponent(slug)

if (container) {
// the name of a container cannot be a valid auxiliary resource document
while (this._containsInvalidSuffixes(slug + '/')) {
console.log('splitting slug', slug)
zg009 marked this conversation as resolved.
Show resolved Hide resolved
// slug = slug.split('.')[0]
const idx = slug.lastIndexOf('.')
slug = slug.substr(0, idx)
console.log('new slug', slug)
}
} else if (this.isAuxResource(slug, extension)) throw error(403, 'POST is not allowed for auxiliary resources')
zg009 marked this conversation as resolved.
Show resolved Hide resolved

if (slug.match(/\/|\||:/)) {
throw error(400, 'The name of new file POSTed may not contain : | or /')
zg009 marked this conversation as resolved.
Show resolved Hide resolved
}
}
// Containers should not receive an extension
if (container) {
extension = ''
}

// always return a valid URL.
const resourceUrl = await ldp.getAvailableUrl(hostname, containerPath, { slug, extension, container })
@@ -327,11 +339,25 @@ class LDP {
} catch (err) { }
}

/**
* This function is used to make sure a resource or container which contains
* reserved suffixes for auxiliary documents cannot be created.
* @param {string} path - the uri to check for invalid suffixes
* @returns {boolean} true is fail - if the path contains reserved suffixes
*/
_containsInvalidSuffixes (path) {
return AUXILIARY_RESOURCES.some(suffix => path.endsWith(suffix + '/'))
}

// check whether a document (or container) has the same name as another document (or container)
async checkItemName (url) {
let testName, testPath
const { hostname, pathname } = this.resourceMapper._parseUrl(url) // (url.url || url)
let itemUrl = this.resourceMapper.resolveUrl(hostname, pathname)
// make sure the resource being created does not attempt invalid resource creation
if (this._containsInvalidSuffixes(itemUrl)) {
throw error(400, `${itemUrl} contained reserved suffixes in path`)
}
const container = itemUrl.endsWith('/')
try {
const testUrl = container ? itemUrl.slice(0, -1) : itemUrl + '/'
374 changes: 43 additions & 331 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -146,6 +146,7 @@
"validate": "node ./test/validate-turtle.js",
"nyc": "cross-env NODE_TLS_REJECT_UNAUTHORIZED=0 nyc --reporter=text-summary mocha --recursive test/integration/ test/unit/",
"mocha": "cross-env NODE_TLS_REJECT_UNAUTHORIZED=0 mocha --recursive test/integration/ test/unit/",
"mocha-integration": "cross-env NODE_TLS_REJECT_UNAUTHORIZED=0 mocha --recursive test/integration/http-test.js",
"prepublishOnly": "npm test",
"postpublish": "git push --follow-tags",
"test": "npm run standard && npm run validate && npm run nyc",
82 changes: 74 additions & 8 deletions test/integration/http-test.js
Original file line number Diff line number Diff line change
@@ -335,7 +335,7 @@ describe('HTTP APIs', function () {
server.get('/invalidfile.foo')
.expect(404, done)
})
it('should return 404 for non-existent container', function (done) { // alain
it('should return 404 for non-existent container', function (done) {
server.get('/inexistant/')
.expect('Accept-Put', 'text/turtle')
.expect(404, done)
@@ -670,6 +670,23 @@ describe('HTTP APIs', function () {
.expect(201, done)
}
)
it('should return a 400 error when trying to put a container that contains a reserved suffix',
zg009 marked this conversation as resolved.
Show resolved Hide resolved
function (done) {
server.put('/foo/bar.acl/test/')
.set('content-type', 'text/turtle')
.set('link', '<http://www.w3.org/ns/ldp#BasicContainer>; rel="type"')
.expect(400, done)
}
)
it('should return a 400 error when trying to put a resource that contains a reserved suffix',
zg009 marked this conversation as resolved.
Show resolved Hide resolved
function (done) {
server.put('/foo/bar.acl/test.ttl')
.send(putRequestBody)
.set('content-type', 'text/turtle')
.set('link', '<http://www.w3.org/ns/ldp#BasicContainer>; rel="type"')
.expect(400, done)
}
)
// Cleanup
after(function () {
rm('/foo/')
@@ -846,7 +863,7 @@ describe('HTTP APIs', function () {
if (err) return done(err)
try {
postLocation = res.headers.location
console.log('location ' + postLocation)
// console.log('location ' + postLocation)
const createdDir = fs.statSync(path.join(__dirname, '../resources', postLocation.slice(0, -1)))
assert(createdDir.isDirectory(), 'Container should have been created')
} catch (err) {
@@ -897,6 +914,24 @@ describe('HTTP APIs', function () {
.expect(hasHeader('acl', suffixAcl))
.expect(201, done)
})
it('should create new resource even if slug contains invalid suffix', function (done) {
server.post('/post-tests/')
.set('slug', 'put-resource.acl.ttl')
.send(postRequest1Body)
.set('content-type', 'text-turtle')
.expect(hasHeader('describedBy', suffixMeta))
.expect(hasHeader('acl', suffixAcl))
.expect(201, done)
})
it('create container with recursive example', function (done) {
server.post('/post-tests/')
.set('content-type', 'text/turtle')
.set('slug', 'foo.bar.acl.meta')
.set('link', '<http://www.w3.org/ns/ldp#BasicContainer>; rel="type"')
.send(postRequest2Body)
.expect('location', /\/post-tests\/foo.bar\//)
.expect(201, done)
})
it('should fail return 404 if no parent container found', function (done) {
server.post('/hello.html/')
.send(postRequest1Body)
@@ -935,19 +970,50 @@ describe('HTTP APIs', function () {
.set('slug', 'loans.ttl')
.set('link', '<http://www.w3.org/ns/ldp#BasicContainer>; rel="type"')
.send(postRequest2Body)
.expect('location', /\/post-tests\/loans.ttl\//)
.expect(201)
.end(function (err) {
.end((err, res) => {
if (err) return done(err)
const stats = fs.statSync(path.join(__dirname, '../resources/post-tests/loans.ttl/'))
if (!stats.isDirectory()) {
return done(new Error('Cannot read container just created'))
try {
postLocation = res.headers.location
console.log('location ' + postLocation)
const createdDir = fs.statSync(path.join(__dirname, '../resources', postLocation.slice(0, -1)))
assert(createdDir.isDirectory(), 'Container should have been created')
} catch (err) {
return done(err)
}
done()
})
})
it('should be able to access newly container', function (done) {
server.get('/post-tests/loans.ttl/')
.expect('content-type', /text\/turtle/)
console.log(postLocation)
server.get(postLocation)
// .expect('content-type', /text\/turtle/)
.expect(200, done)
})
it('should create container', function (done) {
server.post('/post-tests/')
.set('content-type', 'text/turtle')
.set('slug', 'loans.acl.meta')
.set('link', '<http://www.w3.org/ns/ldp#BasicContainer>; rel="type"')
.send(postRequest2Body)
.expect('location', /\/post-tests\/loans\//)
.expect(201)
.end((err, res) => {
if (err) return done(err)
try {
postLocation = res.headers.location
assert(!postLocation.endsWith('.acl/') && !postLocation.endsWith('.meta/'), 'Container name should not end with .acl or .meta')
zg009 marked this conversation as resolved.
Show resolved Hide resolved
} catch (err) {
return done(err)
}
done()
})
})
it('should be able to access newly created container', function (done) {
console.log(postLocation)
server.get(postLocation)
// .expect('content-type', /text\/turtle/)
.expect(200, done)
})
it('should create a new slug if there is a container with same name', function (done) {
20 changes: 20 additions & 0 deletions test/integration/patch-test.js
Original file line number Diff line number Diff line change
@@ -131,6 +131,26 @@ describe('PATCH through text/n3', () => {
result: '@prefix : </new.n3#>.\n@prefix tim: </>.\n\ntim:x tim:y tim:z.\n\n'
}))

describe('on an N3 file that has an invalid uri (*.acl)', describePatch({
path: '/foo/bar.acl/test.n3',
exists: false,
patch: `<> a solid:InsertDeletePatch;
solid:inserts { <x> <y> <z>. }.`
}, {
status: 400,
text: 'contained reserved suffixes in path'
}))

describe('on an N3 file that has an invalid uri (*.meta)', describePatch({
path: '/foo/bar/xyz.meta/test.n3',
exists: false,
patch: `<> a solid:InsertDeletePatch;
solid:insers { <x> <y> <z>. }.`
}, {
status: 400,
text: 'contained reserved suffixes in path'
}))

describe('on a resource with read-only access', describePatch({
path: '/read-only.ttl',
patch: `<> a solid:InsertDeletePatch;