Skip to content

Commit 21a41b3

Browse files
authored
Merge pull request #937 from solid/feature/simple-validation
Simple RDF validation for ACL files
2 parents 7d706d5 + d939f7c commit 21a41b3

File tree

12 files changed

+156
-42
lines changed

12 files changed

+156
-42
lines changed

lib/handlers/get.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ const allow = require('./allow')
1414
const translate = require('../utils.js').translate
1515
const error = require('../http-error')
1616

17-
const RDFs = require('../ldp').RDF_MIME_TYPES
17+
const RDFs = require('../ldp').mimeTypesAsArray()
1818

1919
async function handler (req, res, next) {
2020
const ldp = req.app.locals.ldp

lib/handlers/put.js

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,27 @@
11
module.exports = handler
22

3+
const bodyParser = require('body-parser')
34
const debug = require('debug')('solid:put')
45
const getContentType = require('../utils').getContentType
6+
const HTTPError = require('../http-error')
7+
const { stringToStream } = require('../utils')
8+
const LDP = require('../ldp')
59

610
async function handler (req, res, next) {
7-
const ldp = req.app.locals.ldp
811
debug(req.originalUrl)
912
res.header('MS-Author-Via', 'SPARQL')
1013

14+
const contentType = req.get('content-type')
15+
if (LDP.mimeTypeIsRdf(contentType) && isAclFile(req)) {
16+
return bodyParser.text({ type: () => true })(req, res, () => putAcl(req, res, next))
17+
}
18+
return putStream(req, res, next)
19+
}
20+
21+
async function putStream (req, res, next, stream = req) {
22+
const ldp = req.app.locals.ldp
1123
try {
12-
await ldp.put(req, req, getContentType(req.headers))
24+
await ldp.put(req, stream, getContentType(req.headers))
1325
debug('succeded putting the file')
1426

1527
res.sendStatus(201)
@@ -20,3 +32,19 @@ async function handler (req, res, next) {
2032
return next(err)
2133
}
2234
}
35+
36+
async function putAcl (req, res, next) {
37+
const ldp = req.app.locals.ldp
38+
const contentType = req.get('content-type')
39+
const requestUri = `${req.protocol}//${req.get('host')}${req.originalUrl}`
40+
if (ldp.isValidRdf(req.body, requestUri, contentType)) {
41+
const stream = stringToStream(req.body)
42+
return putStream(req, res, next, stream)
43+
}
44+
next(new HTTPError(400, 'RDF file contains invalid syntax'))
45+
}
46+
47+
function isAclFile (req) {
48+
const originalUrlParts = req.originalUrl.split('.')
49+
return originalUrlParts[originalUrlParts.length - 1] === 'acl'
50+
}

lib/ldp.js

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ const parse = require('./utils').parse
1717
const fetch = require('node-fetch')
1818
const { promisify } = require('util')
1919

20-
const RDF_MIME_TYPES = [
20+
const RDF_MIME_TYPES = new Set([
2121
'text/turtle', // .ttl
2222
'text/n3', // .n3
2323
'text/html', // RDFa
@@ -28,7 +28,7 @@ const RDF_MIME_TYPES = [
2828
'application/rdf+xml', // .rdf
2929
'application/ld+json', // .jsonld
3030
'application/x-turtle'
31-
]
31+
])
3232

3333
class LDP {
3434
constructor (argv = {}) {
@@ -203,6 +203,17 @@ class LDP {
203203
return await this.put(path, stream, contentType)
204204
}
205205

206+
isValidRdf (body, requestUri, contentType) {
207+
const resourceGraph = $rdf.graph()
208+
try {
209+
$rdf.parse(body, resourceGraph, requestUri, contentType)
210+
} catch (err) {
211+
debug.ldp('VALIDATE -- Error parsing data: ' + err)
212+
return false
213+
}
214+
return true
215+
}
216+
206217
async put (url, stream, contentType) {
207218
// PUT requests not supported on containers. Use POST instead
208219
if ((url.url || url).endsWith('/')) {
@@ -451,6 +462,13 @@ class LDP {
451462
}
452463
return ensureNotExists(this, path.join(containerURI, filename))
453464
}
465+
466+
static mimeTypeIsRdf (mimeType) {
467+
return RDF_MIME_TYPES.has(mimeType)
468+
}
469+
470+
static mimeTypesAsArray () {
471+
return Array.from(RDF_MIME_TYPES)
472+
}
454473
}
455474
module.exports = LDP
456-
module.exports.RDF_MIME_TYPES = RDF_MIME_TYPES

lib/models/account-template.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ const mime = require('mime-types')
55
const recursiveRead = require('recursive-readdir')
66
const fsUtils = require('../common/fs-utils')
77
const templateUtils = require('../common/template-utils')
8+
const LDP = require('../ldp')
89

9-
const RDF_MIME_TYPES = require('../ldp').RDF_MIME_TYPES
1010
const TEMPLATE_EXTENSIONS = [ '.acl', '.meta', '.json', '.hbs', '.handlebars' ]
1111
const TEMPLATE_FILES = [ 'card' ]
1212

@@ -31,7 +31,6 @@ class AccountTemplate {
3131
*/
3232
constructor (options = {}) {
3333
this.substitutions = options.substitutions || {}
34-
this.rdfMimeTypes = options.rdfMimeTypes || RDF_MIME_TYPES
3534
this.templateExtensions = options.templateExtensions || TEMPLATE_EXTENSIONS
3635
this.templateFiles = options.templateFiles || TEMPLATE_FILES
3736
}
@@ -136,11 +135,12 @@ class AccountTemplate {
136135
* @return {boolean}
137136
*/
138137
isTemplate (filePath) {
139-
let parsed = path.parse(filePath)
138+
const parsed = path.parse(filePath)
140139

141-
let isRdf = this.rdfMimeTypes.includes(mime.lookup(filePath))
142-
let isTemplateExtension = this.templateExtensions.includes(parsed.ext)
143-
let isTemplateFile = this.templateFiles.includes(parsed.base) ||
140+
const mimeType = mime.lookup(filePath)
141+
const isRdf = LDP.mimeTypeIsRdf(mimeType)
142+
const isTemplateExtension = this.templateExtensions.includes(parsed.ext)
143+
const isTemplateFile = this.templateFiles.includes(parsed.base) ||
144144
this.templateExtensions.includes(parsed.base) // the '/.acl' case
145145

146146
return isRdf || isTemplateExtension || isTemplateFile

test/integration/cors-proxy-test.js

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,14 @@
11
var assert = require('chai').assert
2-
var supertest = require('supertest')
32
var path = require('path')
43
var nock = require('nock')
5-
var { checkDnsSettings } = require('../utils')
6-
7-
var ldnode = require('../../index')
4+
var { checkDnsSettings, setupSupertestServer } = require('../utils')
85

96
describe('CORS Proxy', () => {
10-
var ldp = ldnode({
7+
var server = setupSupertestServer({
118
root: path.join(__dirname, '../resources'),
129
corsProxy: '/proxy',
1310
webid: false
1411
})
15-
var server = supertest(ldp)
1612

1713
before(checkDnsSettings)
1814

test/integration/errors-test.js

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,20 @@
1-
var supertest = require('supertest')
21
var path = require('path')
3-
4-
// Helper functions for the FS
5-
// var rm = require('./utils').rm
6-
// var write = require('./utils').write
7-
// var cp = require('./utils').cp
8-
var read = require('./../utils').read
9-
10-
var ldnode = require('../../index')
2+
const { read, setupSupertestServer } = require('./../utils')
113

124
describe('Error pages', function () {
135
// LDP with error pages
14-
var errorLdp = ldnode({
6+
const errorServer = setupSupertestServer({
157
root: path.join(__dirname, '../resources'),
168
errorPages: path.join(__dirname, '../resources/errorPages'),
179
webid: false
1810
})
19-
var errorServer = supertest(errorLdp)
2011

2112
// LDP with no error pages
22-
var noErrorLdp = ldnode({
13+
const noErrorServer = setupSupertestServer({
2314
root: path.join(__dirname, '../resources'),
2415
noErrorPages: true,
2516
webid: false
2617
})
27-
var noErrorServer = supertest(noErrorLdp)
2818

2919
function defaultErrorPage (filepath, expected) {
3020
var handler = function (res) {

test/integration/formats-test.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
1-
var supertest = require('supertest')
2-
var ldnode = require('../../index')
31
var path = require('path')
42
const assert = require('chai').assert
3+
const { setupSupertestServer } = require('../utils')
54

65
describe('formats', function () {
7-
var ldp = ldnode.createServer({
6+
const server = setupSupertestServer({
87
root: path.join(__dirname, '../resources'),
98
webid: false
109
})
1110

12-
var server = supertest(ldp)
1311
describe('HTML', function () {
1412
it('should return HTML containing "Hello, World!" if Accept is set to text/html', function (done) {
1513
server.get('/hello.html')

test/integration/http-test.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,19 @@
1-
var supertest = require('supertest')
21
var fs = require('fs')
32
var li = require('li')
4-
var ldnode = require('../../index')
53
var rm = require('./../utils').rm
64
var path = require('path')
75
const rdf = require('rdflib')
6+
const { setupSupertestServer } = require('../utils')
87

98
var suffixAcl = '.acl'
109
var suffixMeta = '.meta'
11-
var ldpServer = ldnode.createServer({
10+
var server = setupSupertestServer({
1211
live: true,
1312
dataBrowserPath: 'default',
1413
root: path.join(__dirname, '../resources'),
1514
auth: 'oidc',
1615
webid: false
1716
})
18-
var server = supertest(ldpServer)
1917
var { assert, expect } = require('chai')
2018

2119
/**

test/integration/validate-tts.js

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
const fs = require('fs')
2+
const path = require('path')
3+
const { setupSupertestServer } = require('../utils')
4+
5+
const server = setupSupertestServer({
6+
live: true,
7+
dataBrowserPath: 'default',
8+
root: path.join(__dirname, '../resources'),
9+
auth: 'oidc',
10+
webid: false
11+
})
12+
13+
const invalidTurtleBody = fs.readFileSync(path.join(__dirname, '../resources/invalid1.ttl'), {
14+
'encoding': 'utf8'
15+
})
16+
17+
describe('HTTP requests with invalid Turtle syntax', () => {
18+
describe('PUT API', () => {
19+
it('is allowed with invalid TTL files in general', (done) => {
20+
server.put('/invalid1.ttl')
21+
.send(invalidTurtleBody)
22+
.set('content-type', 'text/turtle')
23+
.expect(201, done)
24+
})
25+
26+
it('is not allowed with invalid ACL files', (done) => {
27+
server.put('/invalid1.ttl.acl')
28+
.send(invalidTurtleBody)
29+
.set('content-type', 'text/turtle')
30+
.expect(400, done)
31+
})
32+
})
33+
34+
describe('PATCH API', () => {
35+
it('does not support patching of TTL files', (done) => {
36+
server.patch('/patch-1-initial.ttl')
37+
.send(invalidTurtleBody)
38+
.set('content-type', 'text/turtle')
39+
.expect(415, done)
40+
})
41+
})
42+
43+
describe('POST API (multipart)', () => {
44+
it('does not validate files that are posted', (done) => {
45+
server.post('/')
46+
.attach('invalid1', path.join(__dirname, '../resources/invalid1.ttl'))
47+
.attach('invalid2', path.join(__dirname, '../resources/invalid2.ttl'))
48+
.expect(200, done)
49+
})
50+
})
51+
})

test/resources/invalid1.ttl

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
@prefix ldp: <http://www.w3.org/ns/ldp#>.
2+
@prefix o: <http://example.org/ontology#>.
3+
4+
<http://example.org/netWorth/nw1/>
5+
test o:NetWorth;
6+
o:netWorthOf <http://example.org/users/JohnZSmith>;
7+
o:asset
8+
<assets/a1>,
9+
<assets/a2>;
10+
o:liability
11+
<liabilities/l1>,
12+
<liabilities/l2>,
13+
<liabilities/l3>.

0 commit comments

Comments
 (0)