solid-server 5.7.9-alpha → 5.7.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -17,7 +17,7 @@ jobs:
17
17
 
18
18
  strategy:
19
19
  matrix:
20
- node-version: [16.x, 18.x]
20
+ node-version: [18.x]
21
21
  os: [ubuntu-latest]
22
22
 
23
23
  steps:
package/README.md CHANGED
@@ -69,6 +69,15 @@ $ solid start --root path/to/folder --port 8443 --ssl-key path/to/ssl-key.pem --
69
69
  # Solid server (solid v0.2.24) running on https://localhost:8443/
70
70
  ```
71
71
 
72
+ By default, `solid` runs in `debug all` mode. To stop the debug logs, use `-q`, the quiet parameter.
73
+
74
+ ```bash
75
+ $ DEBUG="solid:*" solid start -q
76
+ # use quiet mode and set debug to all
77
+ # DEBUG="solid:ACL" logs only debug.ACL's
78
+
79
+ ```
80
+
72
81
  ### Running in development environments
73
82
 
74
83
  Solid requires SSL certificates to be valid, so you cannot use self-signed certificates. To switch off this security feature in development environments, you can use the `bin/solid-test` executable, which unsets the `NODE_TLS_REJECT_UNAUTHORIZED` flag and sets the `rejectUnauthorized` option.
@@ -4,7 +4,7 @@
4
4
  const { dirname } = require('path')
5
5
  const rdf = require('rdflib')
6
6
  const debug = require('./debug').ACL
7
- const debugCache = require('./debug').cache
7
+ // const debugCache = require('./debug').cache
8
8
  const HTTPError = require('./http-error')
9
9
  const aclCheck = require('@solid/acl-check')
10
10
  const { URL } = require('url')
@@ -55,7 +55,8 @@ class ACLChecker {
55
55
  }
56
56
  this.messagesCached[cacheKey] = this.messagesCached[cacheKey] || []
57
57
 
58
- const acl = await this.getNearestACL().catch(err => {
58
+ // for method DELETE nearestACL and ACL from parent resource
59
+ const acl = await this.getNearestACL(method).catch(err => {
59
60
  this.messagesCached[cacheKey].push(new HTTPError(err.status || 500, err.message || err))
60
61
  })
61
62
  if (!acl) {
@@ -63,21 +64,23 @@ class ACLChecker {
63
64
  return this.aclCached[cacheKey]
64
65
  }
65
66
  let resource = rdf.sym(this.resource)
67
+ let parentResource = resource
68
+ if (!this.resource.endsWith('/')) { parentResource = rdf.sym(ACLChecker.getDirectory(this.resource)) }
66
69
  if (this.resource.endsWith('/' + this.suffix)) {
67
70
  resource = rdf.sym(ACLChecker.getDirectory(this.resource))
71
+ parentResource = resource
68
72
  }
69
73
  // If this is an ACL, Control mode must be present for any operations
70
74
  if (this.isAcl(this.resource)) {
71
75
  mode = 'Control'
72
- resource = rdf.sym(this.resource.substring(0, this.resource.length - this.suffix.length))
76
+ const thisResource = this.resource.substring(0, this.resource.length - this.suffix.length)
77
+ resource = rdf.sym(thisResource)
78
+ parentResource = resource
79
+ if (!thisResource.endsWith('/')) parentResource = rdf.sym(ACLChecker.getDirectory(thisResource))
73
80
  }
74
- // If the slug is an acl, reject
75
- /* if (this.isAcl(this.slug)) {
76
- this.aclCached[cacheKey] = Promise.resolve(false)
77
- return this.aclCached[cacheKey]
78
- } */
79
- const directory = acl.isContainer ? rdf.sym(ACLChecker.getDirectory(acl.acl)) : null
80
- const aclFile = rdf.sym(acl.acl)
81
+ const directory = acl.isContainer ? rdf.sym(ACLChecker.getDirectory(acl.docAcl)) : null
82
+ const aclFile = rdf.sym(acl.docAcl)
83
+ const aclGraph = acl.docGraph
81
84
  const agent = user ? rdf.sym(user) : null
82
85
  const modes = [ACL(mode)]
83
86
  const agentOrigin = this.agentOrigin
@@ -85,38 +88,65 @@ class ACLChecker {
85
88
  let originTrustedModes = []
86
89
  try {
87
90
  this.fetch(aclFile.doc().value)
88
- originTrustedModes = await aclCheck.getTrustedModesForOrigin(acl.graph, resource, directory, aclFile, agentOrigin, (uriNode) => {
89
- return this.fetch(uriNode.doc().value, acl.graph)
91
+ originTrustedModes = await aclCheck.getTrustedModesForOrigin(aclGraph, resource, directory, aclFile, agentOrigin, (uriNode) => {
92
+ return this.fetch(uriNode.doc().value, aclGraph)
90
93
  })
91
94
  } catch (e) {
92
95
  // FIXME: https://github.com/solid/acl-check/issues/23
93
96
  // console.error(e.message)
94
97
  }
95
- let accessDenied = aclCheck.accessDenied(acl.graph, resource, directory, aclFile, agent, modes, agentOrigin, trustedOrigins, originTrustedModes)
96
98
 
97
- function accessDeniedForAccessTo (mode) {
98
- const accessDeniedAccessTo = aclCheck.accessDenied(acl.graph, directory, null, aclFile, agent, [ACL(mode)], agentOrigin, trustedOrigins, originTrustedModes)
99
+ function resourceAccessDenied (modes) {
100
+ return aclCheck.accessDenied(aclGraph, resource, directory, aclFile, agent, modes, agentOrigin, trustedOrigins, originTrustedModes)
101
+ }
102
+ function accessDeniedForAccessTo (modes) {
103
+ const accessDeniedAccessTo = aclCheck.accessDenied(aclGraph, directory, null, aclFile, agent, modes, agentOrigin, trustedOrigins, originTrustedModes)
99
104
  const accessResult = !accessDenied && !accessDeniedAccessTo
100
- accessDenied = accessResult ? false : accessDenied || accessDeniedAccessTo
101
- // debugCache('accessDenied result ' + accessDenied)
105
+ return accessResult ? false : accessDenied || accessDeniedAccessTo
106
+ }
107
+ async function accessdeniedFromParent (modes) {
108
+ const parentAclDirectory = ACLChecker.getDirectory(acl.parentAcl)
109
+ const parentDirectory = parentResource === parentAclDirectory ? null : rdf.sym(parentAclDirectory)
110
+ const accessDeniedParent = aclCheck.accessDenied(acl.parentGraph, parentResource, parentDirectory, rdf.sym(acl.parentAcl), agent, modes, agentOrigin, trustedOrigins, originTrustedModes)
111
+ const accessResult = !accessDenied && !accessDeniedParent
112
+ return accessResult ? false : accessDenied || accessDeniedParent
102
113
  }
114
+
115
+ let accessDenied = resourceAccessDenied(modes)
116
+ // debugCache('accessDenied resource ' + accessDenied)
117
+
103
118
  // For create and update HTTP methods
104
- if ((method === 'PUT' || method === 'PATCH' || method === 'COPY') && directory) {
119
+ if ((method === 'PUT' || method === 'PATCH' || method === 'COPY')) {
105
120
  // if resource and acl have same parent container,
106
121
  // and resource does not exist, then accessTo Append from parent is required
107
- if (directory.value === dirname(aclFile.value) + '/' && !resourceExists) {
108
- accessDeniedForAccessTo('Append')
122
+ if (directory && directory.value === dirname(aclFile.value) + '/' && !resourceExists) {
123
+ accessDenied = accessDeniedForAccessTo([ACL('Append')])
109
124
  }
125
+ // debugCache('accessDenied PUT/PATCH ' + accessDenied)
110
126
  }
111
127
 
112
128
  // For delete HTTP method
113
- if ((method === 'DELETE') && directory) {
114
- // if resource and acl have same parent container,
115
- // then accessTo Write from parent is required
116
- if (directory.value === dirname(aclFile.value) + '/') {
117
- accessDeniedForAccessTo('Write')
118
- }
129
+ if ((method === 'DELETE')) {
130
+ if (resourceExists) {
131
+ // deleting a Container
132
+ // without Read, the response code will reveal whether a Container is empty or not
133
+ if (directory && this.resource.endsWith('/')) accessDenied = resourceAccessDenied([ACL('Read'), ACL('Write')])
134
+ // if resource and acl have same parent container,
135
+ // then both Read and Write on parent is required
136
+ else if (!directory && aclFile.value.endsWith(`/${this.suffix}`)) accessDenied = await accessdeniedFromParent([ACL('Read'), ACL('Write')])
137
+
138
+ // deleting a Document
139
+ else if (directory && directory.value === dirname(aclFile.value) + '/') {
140
+ accessDenied = accessDeniedForAccessTo([ACL('Write')])
141
+ } else {
142
+ accessDenied = await accessdeniedFromParent([ACL('Write')])
143
+ }
144
+
145
+ // https://github.com/solid/specification/issues/14#issuecomment-1712773516
146
+ } else { accessDenied = true }
147
+ // debugCache('accessDenied DELETE ' + accessDenied)
119
148
  }
149
+
120
150
  if (accessDenied && user) {
121
151
  this.messagesCached[cacheKey].push(HTTPError(403, accessDenied))
122
152
  } else if (accessDenied) {
@@ -140,14 +170,21 @@ class ACLChecker {
140
170
  return `${parts.join('/')}/`
141
171
  }
142
172
 
143
- // Gets the ACL that applies to the resource
144
- async getNearestACL () {
173
+ // Gets any ACLs that apply to the resource
174
+ // DELETE uses docAcl when docAcl is parent to the resource
175
+ // or docAcl and parentAcl when docAcl is the ACL of the Resource
176
+ async getNearestACL (method) {
145
177
  const { resource } = this
146
178
  let isContainer = false
147
179
  const possibleACLs = this.getPossibleACLs()
148
180
  const acls = [...possibleACLs]
149
181
  let returnAcl = null
150
- while (possibleACLs.length > 0 && !returnAcl) {
182
+ let returnParentAcl = null
183
+ let parentAcl = null
184
+ let parentGraph = null
185
+ let docAcl = null
186
+ let docGraph = null
187
+ while (possibleACLs.length > 0 && !returnParentAcl) {
151
188
  const acl = possibleACLs.shift()
152
189
  let graph
153
190
  try {
@@ -155,28 +192,52 @@ class ACLChecker {
155
192
  graph = await this.requests[acl]
156
193
  } catch (err) {
157
194
  if (err && (err.code === 'ENOENT' || err.status === 404)) {
158
- isContainer = true
195
+ // only set isContainer before docAcl
196
+ if (!docAcl) isContainer = true
159
197
  continue
160
198
  }
161
199
  debug(err)
162
200
  throw err
163
201
  }
164
- const relative = resource.replace(acl.replace(/[^/]+$/, ''), './')
165
- debug(`Using ACL ${acl} for ${relative}`)
166
- returnAcl = { acl, graph, isContainer }
202
+ // const relative = resource.replace(acl.replace(/[^/]+$/, ''), './')
203
+ // debug(`Using ACL ${acl} for ${relative}`)
204
+ if (!docAcl) {
205
+ docAcl = acl
206
+ docGraph = graph
207
+ // parentAcl is only needed for DELETE
208
+ if (method !== 'DELETE') returnParentAcl = true
209
+ } else {
210
+ parentAcl = acl
211
+ parentGraph = graph
212
+ returnParentAcl = true
213
+ }
214
+
215
+ returnAcl = { docAcl, docGraph, isContainer, parentAcl, parentGraph }
167
216
  }
168
217
  if (!returnAcl) {
169
218
  throw new HTTPError(500, `No ACL found for ${resource}, searched in \n- ${acls.join('\n- ')}`)
170
219
  }
171
- const groupNodes = returnAcl.graph.statementsMatching(null, ACL('agentGroup'), null)
172
- const groupUrls = groupNodes.map(node => node.object.value.split('#')[0])
220
+ // fetch group
221
+ let groupNodes = returnAcl.docGraph.statementsMatching(null, ACL('agentGroup'), null)
222
+ let groupUrls = groupNodes.map(node => node.object.value.split('#')[0])
173
223
  await Promise.all(groupUrls.map(async groupUrl => {
174
224
  try {
175
- const graph = await this.fetch(groupUrl, returnAcl.graph)
176
- this.requests[groupUrl] = this.requests[groupUrl] || graph
225
+ const docGraph = await this.fetch(groupUrl, returnAcl.docGraph)
226
+ this.requests[groupUrl] = this.requests[groupUrl] || docGraph
177
227
  } catch (e) {} // failed to fetch groupUrl
178
228
  }))
229
+ if (parentAcl) {
230
+ groupNodes = returnAcl.parentGraph.statementsMatching(null, ACL('agentGroup'), null)
231
+ groupUrls = groupNodes.map(node => node.object.value.split('#')[0])
232
+ await Promise.all(groupUrls.map(async groupUrl => {
233
+ try {
234
+ const docGraph = await this.fetch(groupUrl, returnAcl.parentGraph)
235
+ this.requests[groupUrl] = this.requests[groupUrl] || docGraph
236
+ } catch (e) {} // failed to fetch groupUrl
237
+ }))
238
+ }
179
239
 
240
+ // debugAccounts('ALAIN returnACl ' + '\ndocAcl ' + returnAcl.docAcl + '\nparentAcl ' + returnAcl.parentAcl)
180
241
  return returnAcl
181
242
  }
182
243
 
@@ -264,7 +325,7 @@ function fetchLocalOrRemote (mapper, serverUri) {
264
325
  // debugCache('Expunging from cache', url)
265
326
  delete temporaryCache[url]
266
327
  if (Object.keys(temporaryCache).length === 0) {
267
- debugCache('Cache is empty again')
328
+ // debugCache('Cache is empty again')
268
329
  }
269
330
  }, EXPIRY_MS),
270
331
  promise: doFetch(url)
package/lib/create-app.js CHANGED
@@ -32,7 +32,7 @@ const corsSettings = cors({
32
32
  methods: [
33
33
  'OPTIONS', 'HEAD', 'GET', 'PATCH', 'POST', 'PUT', 'DELETE'
34
34
  ],
35
- exposedHeaders: 'Authorization, User, Location, Link, Vary, Last-Modified, ETag, Accept-Patch, Accept-Post, Updates-Via, Allow, WAC-Allow, Content-Length, WWW-Authenticate, MS-Author-Via, X-Powered-By',
35
+ exposedHeaders: 'Authorization, User, Location, Link, Vary, Last-Modified, ETag, Accept-Patch, Accept-Post, Accept-Put, Updates-Via, Allow, WAC-Allow, Content-Length, WWW-Authenticate, MS-Author-Via, X-Powered-By',
36
36
  credentials: true,
37
37
  maxAge: 1728000,
38
38
  origin: true,
@@ -117,6 +117,33 @@ function createApp (argv = {}) {
117
117
  // Attach the LDP middleware
118
118
  app.use('/', LdpMiddleware(corsSettings))
119
119
 
120
+ // https://stackoverflow.com/questions/51741383/nodejs-express-return-405-for-un-supported-method
121
+ app.use(function (req, res, next) {
122
+ const AllLayers = app._router.stack
123
+ const Layers = AllLayers.filter(x => x.name === 'bound dispatch' && x.regexp.test(req.path))
124
+
125
+ const Methods = []
126
+ Layers.forEach(layer => {
127
+ for (const method in layer.route.methods) {
128
+ if (layer.route.methods[method] === true) {
129
+ Methods.push(method.toUpperCase())
130
+ }
131
+ }
132
+ })
133
+
134
+ if (Layers.length !== 0 && !Methods.includes(req.method)) {
135
+ // res.setHeader('Allow', Methods.join(','))
136
+
137
+ if (req.method === 'OPTIONS') {
138
+ return res.send(Methods.join(', '))
139
+ } else {
140
+ return res.status(405).send()
141
+ }
142
+ } else {
143
+ next()
144
+ }
145
+ })
146
+
120
147
  // Errors
121
148
  app.use(errorPages.handler)
122
149
 
@@ -2,7 +2,7 @@ module.exports = allow
2
2
 
3
3
  // const path = require('path')
4
4
  const ACL = require('../acl-checker')
5
- const debug = require('../debug.js').ACL
5
+ // const debug = require('../debug.js').ACL
6
6
  // const error = require('../http-error')
7
7
 
8
8
  function allow (mode) {
@@ -77,7 +77,7 @@ function allow (mode) {
77
77
  if (resourceUrl.endsWith('.acl') && (await ldp.isOwner(userId, req.hostname))) return next()
78
78
  } catch (err) {}
79
79
  const error = req.authError || await req.acl.getError(userId, mode)
80
- debug(`${mode} access denied to ${userId || '(none)'}: ${error.status} - ${error.message}`)
80
+ // debug(`${mode} access denied to ${userId || '(none)'}: ${error.status} - ${error.message}`)
81
81
  next(error)
82
82
  }
83
83
  }
@@ -27,8 +27,13 @@ async function handler (req, res, next) {
27
27
  const requestedType = negotiator.mediaType()
28
28
  const possibleRDFType = negotiator.mediaType(RDFs)
29
29
 
30
+ // deprecated kept for compatibility
30
31
  res.header('MS-Author-Via', 'SPARQL')
31
32
 
33
+ res.header('Accept-Patch', 'text/n3, application/sparql-update, application/sparql-update-single-match')
34
+ res.header('Accept-Post', '*/*')
35
+ if (!path.endsWith('/') && !glob.hasMagic(path)) res.header('Accept-Put', '*/*')
36
+
32
37
  // Set live updates
33
38
  if (ldp.live) {
34
39
  res.header('Updates-Via', ldp.resourceMapper.resolveUrl(req.hostname).replace(/^http/, 'ws'))
@@ -49,6 +54,8 @@ async function handler (req, res, next) {
49
54
  try {
50
55
  ret = await ldp.get(options, req.accepts(['html', 'turtle', 'rdf+xml', 'n3', 'ld+json']) === 'html')
51
56
  } catch (err) {
57
+ // set Accept-Put if container do not exist
58
+ if (err.status === 404 && path.endsWith('/')) res.header('Accept-Put', 'text/turtle')
52
59
  // use globHandler if magic is detected
53
60
  if (err.status === 404 && glob.hasMagic(path)) {
54
61
  debug('forwarding to glob request')
@@ -116,10 +123,9 @@ async function handler (req, res, next) {
116
123
 
117
124
  // If it is not in our RDFs we can't even translate,
118
125
  // Sorry, we can't help
119
- if (!possibleRDFType) {
126
+ if (!possibleRDFType || !RDFs.includes(contentType)) { // possibleRDFType defaults to text/turtle
120
127
  return next(error(406, 'Cannot serve requested type: ' + contentType))
121
128
  }
122
-
123
129
  try {
124
130
  // Translate from the contentType found to the possibleRDFType desired
125
131
  const data = await translate(stream, baseUri, contentType, possibleRDFType)
@@ -128,8 +134,8 @@ async function handler (req, res, next) {
128
134
  res.send(data)
129
135
  return next()
130
136
  } catch (err) {
131
- debug('error translating: ' + req.originalUrl + ' ' + contentType + ' -> ' + possibleRDFType + ' -- ' + 500 + ' ' + err.message)
132
- return next(error(500, 'Error translating between RDF formats'))
137
+ debug('error translating: ' + req.originalUrl + ' ' + contentType + ' -> ' + possibleRDFType + ' -- ' + 406 + ' ' + err.message)
138
+ return next(error(500, 'Cannot serve requested type: ' + requestedType))
133
139
  }
134
140
  }
135
141
 
@@ -8,7 +8,7 @@ module.exports = handler
8
8
  function handler (req, res, next) {
9
9
  linkServiceEndpoint(req, res)
10
10
  linkAuthProvider(req, res)
11
- linkSparqlEndpoint(res)
11
+ linkAcceptEndpoint(res)
12
12
 
13
13
  res.status(204)
14
14
 
@@ -28,6 +28,8 @@ function linkServiceEndpoint (req, res) {
28
28
  addLink(res, serviceEndpoint, 'service')
29
29
  }
30
30
 
31
- function linkSparqlEndpoint (res) {
32
- res.header('Accept-Patch', 'application/sparql-update')
31
+ function linkAcceptEndpoint (res) {
32
+ res.header('Accept-Patch', 'text/n3, application/sparql-update, application/sparql-update-single-match')
33
+ res.header('Accept-Post', '*/*')
34
+ res.header('Accept-Put', '*/*')
33
35
  }
@@ -39,7 +39,6 @@ function contentForNew (contentType) {
39
39
  // Handles a PATCH request
40
40
  async function patchHandler (req, res, next) {
41
41
  debug(`PATCH -- ${req.originalUrl}`)
42
- res.header('MS-Author-Via', 'SPARQL')
43
42
  try {
44
43
  // Obtain details of the target resource
45
44
  const ldp = req.app.locals.ldp
@@ -69,12 +68,15 @@ async function patchHandler (req, res, next) {
69
68
  patch.text = req.body ? req.body.toString() : ''
70
69
  patch.uri = `${url}#patch-${hash(patch.text)}`
71
70
  patch.contentType = getContentType(req.headers)
71
+ if (!patch.contentType) {
72
+ throw error(400, 'PATCH request requires a content-type via the Content-Type header')
73
+ }
72
74
  debug('PATCH -- Received patch (%d bytes, %s)', patch.text.length, patch.contentType)
73
75
  const parsePatch = PATCH_PARSERS[patch.contentType]
74
76
  if (!parsePatch) {
75
77
  throw error(415, `Unsupported patch content type: ${patch.contentType}`)
76
78
  }
77
-
79
+ res.header('Accept-Patch', patch.contentType) // is this needed ?
78
80
  // Parse the patch document and verify permissions
79
81
  const patchObject = await parsePatch(url, patch.uri, patch.text)
80
82
  await checkPermission(req, patchObject, resourceExists)
@@ -8,10 +8,11 @@ const { stringToStream } = require('../utils')
8
8
 
9
9
  async function handler (req, res, next) {
10
10
  debug(req.originalUrl)
11
- res.header('MS-Author-Via', 'SPARQL')
11
+ // deprecated kept for compatibility
12
+ res.header('MS-Author-Via', 'SPARQL') // is this needed ?
12
13
  const contentType = req.get('content-type')
13
14
 
14
- // check if a folder or resource with same name exists
15
+ // check whether a folder or resource with same name exists
15
16
  try {
16
17
  const ldp = req.app.locals.ldp
17
18
  await ldp.checkItemName(req)
@@ -64,10 +65,16 @@ async function putStream (req, res, next, stream = req) {
64
65
  try {
65
66
  // First check if the file already exists
66
67
  await ldp.resourceMapper.mapUrlToFile({ url: req })
68
+ // Fails on if-none-match asterisk precondition
69
+ if ((req.headers['if-none-match'] === '*') && !req.path.endsWith('/')) {
70
+ res.sendStatus(412)
71
+ return next()
72
+ }
67
73
  } catch (err) {
68
74
  resourceExists = false
69
75
  }
70
76
  try {
77
+ // Fails with Append on existing resource
71
78
  if (!req.originalUrl.endsWith('.acl')) await checkPermission(req, resourceExists)
72
79
  await ldp.put(req, stream, getContentType(req.headers))
73
80
  res.sendStatus(201)
package/lib/ldp.js CHANGED
@@ -158,7 +158,7 @@ class LDP {
158
158
  extension = ''
159
159
  }
160
160
 
161
- // allways return a valid URL.
161
+ // always return a valid URL.
162
162
  const resourceUrl = await ldp.getAvailableUrl(hostname, containerPath, { slug, extension, container })
163
163
  debug.handlers('POST -- Will create at: ' + resourceUrl)
164
164
 
@@ -327,7 +327,7 @@ class LDP {
327
327
  } catch (err) { }
328
328
  }
329
329
 
330
- // check if a document (or container) has the same name than a document (or container)
330
+ // check whether a document (or container) has the same name as another document (or container)
331
331
  async checkItemName (url) {
332
332
  let testName, testPath
333
333
  const { hostname, pathname } = this.resourceMapper._parseUrl(url) // (url.url || url)
@@ -350,7 +350,7 @@ class LDP {
350
350
  }
351
351
  }
352
352
  if (testName) {
353
- throw error(404, `${testPath}: Container and resource cannot have the same name in URI`)
353
+ throw error(409, `${testPath}: Container and resource cannot have the same name in URI`)
354
354
  }
355
355
  }
356
356
 
@@ -470,9 +470,9 @@ class LDP {
470
470
  throw err
471
471
  }
472
472
  const stream = stringToStream(data)
473
- // TODO 'text/turtle' is fixed, should be contentType instead
474
- // This forces one more translation turtle -> desired
475
- return { stream, contentType: 'text/turtle', container: true }
473
+ // TODO contentType is defaultContainerContentType ('text/turtle'),
474
+ // This forces one translation turtle -> desired
475
+ return { stream, contentType, container: true }
476
476
  } else {
477
477
  let chunksize, contentRange, start, end
478
478
  if (options.range) {
@@ -585,13 +585,13 @@ class LDP {
585
585
 
586
586
  let itemName = slug.endsWith(extension) || slug.endsWith(this.suffixAcl) || slug.endsWith(this.suffixMeta) ? slug : slug + extension
587
587
  try {
588
- // check resource exists
588
+ // check whether resource exists
589
589
  const context = container ? '/' : ''
590
590
  await this.resourceMapper.mapUrlToFile({ url: (requestUrl + itemName + context) })
591
591
  itemName = `${uuid.v1()}-${itemName}`
592
592
  } catch (e) {
593
593
  try {
594
- // check resource with same name exists
594
+ // check whether resource with same name exists
595
595
  const context = !container ? '/' : ''
596
596
  await this.resourceMapper.mapUrlToFile({ url: (requestUrl + itemName + context) })
597
597
  itemName = `${uuid.v1()}-${itemName}`
@@ -140,7 +140,9 @@ class PasswordAuthenticator extends Authenticator {
140
140
  })
141
141
  .then(foundUser => {
142
142
  if (!foundUser) {
143
- error = new Error('No user found for that username')
143
+ // CWE - CWE-200: Exposure of Sensitive Information to an Unauthorized Actor (4.13)
144
+ // https://cwe.mitre.org/data/definitions/200.html
145
+ error = new Error('Invalid username/password combination.') // no detail for security 'No user found for that username')
144
146
  error.statusCode = 400
145
147
  throw error
146
148
  }
@@ -151,7 +153,9 @@ class PasswordAuthenticator extends Authenticator {
151
153
  })
152
154
  .then(validUser => {
153
155
  if (!validUser) {
154
- error = new Error('User found but no password match')
156
+ // CWE - CWE-200: Exposure of Sensitive Information to an Unauthorized Actor (4.13)
157
+ // https://cwe.mitre.org/data/definitions/200.html
158
+ error = new Error('Invalid username/password combination.') // no detail for security 'User found but no password match')
155
159
  error.statusCode = 400
156
160
  throw error
157
161
  }
@@ -25,6 +25,7 @@ class ResourceMapper {
25
25
  rootPath,
26
26
  includeHost = false,
27
27
  defaultContentType = 'application/octet-stream',
28
+ defaultContainerContentType = 'text/turtle',
28
29
  indexFilename = 'index.html',
29
30
  overrideTypes = { acl: 'text/turtle', meta: 'text/turtle' }
30
31
  }) {
@@ -33,6 +34,7 @@ class ResourceMapper {
33
34
  this._includeHost = includeHost
34
35
  this._readdir = readdir
35
36
  this._defaultContentType = defaultContentType
37
+ this._defaultContainerContentType = defaultContainerContentType
36
38
  this._types = { ...types, ...overrideTypes }
37
39
  this._indexFilename = indexFilename
38
40
  this._indexContentType = this._getContentTypeFromExtension(indexFilename)
@@ -187,10 +189,11 @@ class ResourceMapper {
187
189
  return url
188
190
  }
189
191
 
190
- // Gets the expected content type based on the extension of the path
192
+ // Gets the expected content type based on resource type and the extension of the path
191
193
  _getContentTypeFromExtension (path) {
194
+ const defaultContentType = (path === '' || path.endsWith('/')) ? this._defaultContainerContentType : this._defaultContentType
192
195
  const extension = /\.([^/.]+)$/.exec(path)
193
- return extension && this._types[extension[1].toLowerCase()] || this._defaultContentType
196
+ return extension && this._types[extension[1].toLowerCase()] || defaultContentType
194
197
  }
195
198
 
196
199
  // Appends an extension for the specific content type, if needed
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "solid-server",
3
3
  "description": "Solid server on top of the file-system",
4
- "version": "5.7.9-alpha",
4
+ "version": "5.7.9",
5
5
  "author": {
6
6
  "name": "Tim Berners-Lee",
7
7
  "email": "timbl@w3.org"
@@ -63,7 +63,7 @@
63
63
  "@solid/acl-check": "^0.4.5",
64
64
  "@solid/oidc-auth-manager": "^0.24.3",
65
65
  "@solid/oidc-op": "^0.11.6",
66
- "async-lock": "^1.4.0",
66
+ "async-lock": "^1.4.1",
67
67
  "body-parser": "^1.20.2",
68
68
  "bootstrap": "^3.4.1",
69
69
  "cached-path-relative": "^1.1.0",
@@ -73,9 +73,9 @@
73
73
  "commander": "^8.3.0",
74
74
  "cors": "^2.8.5",
75
75
  "debug": "^4.3.4",
76
- "express": "^4.18.2",
76
+ "express": "^4.18.3",
77
77
  "express-handlebars": "^5.3.5",
78
- "express-session": "^1.17.3",
78
+ "express-session": "^1.18.0",
79
79
  "extend": "^3.0.2",
80
80
  "from2": "^2.3.0",
81
81
  "fs-extra": "^10.1.0",
@@ -95,7 +95,7 @@
95
95
  "node-fetch": "^2.7.0",
96
96
  "node-forge": "^1.3.1",
97
97
  "node-mailer": "^0.1.1",
98
- "nodemailer": "^6.9.7",
98
+ "nodemailer": "^6.9.11",
99
99
  "oidc-op-express": "^0.0.3",
100
100
  "owasp-password-strength-test": "^1.3.0",
101
101
  "recursive-readdir": "^2.2.3",
@@ -115,23 +115,23 @@
115
115
  },
116
116
  "devDependencies": {
117
117
  "@solid/solid-auth-oidc": "0.3.0",
118
- "chai": "^4.3.10",
118
+ "chai": "^4.4.1",
119
119
  "chai-as-promised": "7.1.1",
120
120
  "cross-env": "7.0.3",
121
121
  "dirty-chai": "2.0.1",
122
122
  "eslint": "^7.32.0",
123
123
  "localstorage-memory": "1.0.3",
124
- "mocha": "^10.2.0",
125
- "nock": "^13.4.0",
126
- "node-mocks-http": "^1.14.0",
124
+ "mocha": "^10.3.0",
125
+ "nock": "^13.5.4",
126
+ "node-mocks-http": "^1.14.1",
127
127
  "nyc": "15.1.0",
128
128
  "pre-commit": "1.2.2",
129
129
  "randombytes": "2.1.0",
130
130
  "sinon": "12.0.1",
131
131
  "sinon-chai": "3.7.0",
132
- "snyk": "^1.1264.0",
132
+ "snyk": "^1.1283.0",
133
133
  "standard": "16.0.4",
134
- "supertest": "^6.3.3",
134
+ "supertest": "^6.3.4",
135
135
  "turtle-validator": "1.1.1",
136
136
  "whatwg-url": "11.0.0"
137
137
  },