solid-server 5.7.8 → 5.7.9-alpha

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.
@@ -53,7 +53,11 @@ async function patchHandler (req, res, next) {
53
53
  ({ path, contentType } = await ldp.resourceMapper.mapUrlToFile(
54
54
  { url: req, createIfNotExists: true, contentType: contentTypeForNew(req) }))
55
55
  // check if a folder with same name exists
56
- await ldp.checkItemName(req)
56
+ try {
57
+ await ldp.checkItemName(req)
58
+ } catch (err) {
59
+ return next(err)
60
+ }
57
61
  resourceExists = false
58
62
  }
59
63
  const { url } = await ldp.resourceMapper.mapFileToUrl({ path, hostname: req.hostname })
@@ -9,10 +9,17 @@ const { stringToStream } = require('../utils')
9
9
  async function handler (req, res, next) {
10
10
  debug(req.originalUrl)
11
11
  res.header('MS-Author-Via', 'SPARQL')
12
-
13
12
  const contentType = req.get('content-type')
13
+
14
+ // check if a folder or resource with same name exists
15
+ try {
16
+ const ldp = req.app.locals.ldp
17
+ await ldp.checkItemName(req)
18
+ } catch (e) {
19
+ return next(e)
20
+ }
14
21
  // check for valid rdf content for auxiliary resource and /profile/card
15
- // in future we may check that /profile/card is a minimal valid WebID card
22
+ // TODO check that /profile/card is a minimal valid WebID card
16
23
  if (isAuxiliary(req) || req.originalUrl === '/profile/card') {
17
24
  if (contentType === 'text/turtle') {
18
25
  return bodyParser.text({ type: () => true })(req, res, () => putValidRdf(req, res, next))
@@ -21,17 +28,51 @@ async function handler (req, res, next) {
21
28
  return putStream(req, res, next)
22
29
  }
23
30
 
31
+ // Verifies whether the user is allowed to perform Append PUT on the target
32
+ async function checkPermission (request, resourceExists) {
33
+ // If no ACL object was passed down, assume permissions are okay.
34
+ if (!request.acl) return Promise.resolve()
35
+ // At this point, we already assume append access,
36
+ // we might need to perform additional checks.
37
+ let modes = []
38
+ // acl:default Write is required for PUT when Resource Exists
39
+ if (resourceExists) modes = ['Write']
40
+ // if (resourceExists && request.originalUrl.endsWith('.acl')) modes = ['Control']
41
+ const { acl, session: { userId } } = request
42
+
43
+ const allowed = await Promise.all(modes.map(mode => acl.can(userId, mode, request.method, resourceExists)))
44
+ const allAllowed = allowed.reduce((memo, allowed) => memo && allowed, true)
45
+ if (!allAllowed) {
46
+ // check owner with Control
47
+ // const ldp = request.app.locals.ldp
48
+ // if (request.path.endsWith('.acl') && userId === await ldp.getOwner(request.hostname)) return Promise.resolve()
49
+
50
+ const errors = await Promise.all(modes.map(mode => acl.getError(userId, mode)))
51
+ const error = errors.filter(error => !!error)
52
+ .reduce((prevErr, err) => prevErr.status > err.status ? prevErr : err, { status: 0 })
53
+ return Promise.reject(error)
54
+ }
55
+ return Promise.resolve()
56
+ }
57
+
24
58
  // TODO could be renamed as putResource (it now covers container and non-container)
25
59
  async function putStream (req, res, next, stream = req) {
26
60
  const ldp = req.app.locals.ldp
61
+ // try {
62
+ // Obtain details of the target resource
63
+ let resourceExists = true
64
+ try {
65
+ // First check if the file already exists
66
+ await ldp.resourceMapper.mapUrlToFile({ url: req })
67
+ } catch (err) {
68
+ resourceExists = false
69
+ }
27
70
  try {
28
- debug('test ' + req.get('content-type') + getContentType(req.headers))
71
+ if (!req.originalUrl.endsWith('.acl')) await checkPermission(req, resourceExists)
29
72
  await ldp.put(req, stream, getContentType(req.headers))
30
- debug('succeded putting the file/folder')
31
73
  res.sendStatus(201)
32
74
  return next()
33
75
  } catch (err) {
34
- debug('error putting the file/folder:' + err.message)
35
76
  err.message = 'Can\'t write file/folder: ' + err.message
36
77
  return next(err)
37
78
  }
@@ -25,7 +25,7 @@ function LdpMiddleware (corsSettings) {
25
25
  router.get('/*', index, allow('Read'), header.addPermissions, get)
26
26
  router.post('/*', allow('Append'), post)
27
27
  router.patch('/*', allow('Append'), patch)
28
- router.put('/*', allow('Write'), put)
28
+ router.put('/*', allow('Append'), put)
29
29
  router.delete('/*', allow('Write'), del)
30
30
 
31
31
  return router
package/lib/ldp.js CHANGED
@@ -157,37 +157,13 @@ class LDP {
157
157
  if (container) {
158
158
  extension = ''
159
159
  }
160
- // Check for file or folder with same name
161
- let testName, fileName
162
- try {
163
- // check for defaulted slug in NSS POST (slug with extension)
164
- fileName = slug.endsWith(extension) || slug.endsWith(this.suffixAcl) || slug.endsWith(this.suffixMeta) ? slug : slug + extension
165
- fileName = container ? fileName : fileName + '/'
166
- const { url: testUrl } = await this.resourceMapper.mapFileToUrl({ hostname, path: containerPath + fileName })
167
- const { path: testPath } = await this.resourceMapper.mapUrlToFile({ url: testUrl })
168
- testName = container ? fs.lstatSync(testPath).isFile() : fs.lstatSync(testPath).isDirectory()
169
- } catch (err) { testName = false }
170
-
171
- if (testName) {
172
- throw error(404, 'Container and resource cannot have the same name in URI')
173
- }
174
160
 
175
- // TODO: possibly package this in ldp.post
176
- let resourceUrl = await ldp.getAvailableUrl(hostname, containerPath, { slug, extension })
161
+ // allways return a valid URL.
162
+ const resourceUrl = await ldp.getAvailableUrl(hostname, containerPath, { slug, extension, container })
177
163
  debug.handlers('POST -- Will create at: ' + resourceUrl)
178
- let originalUrl = resourceUrl
179
-
180
- if (container) {
181
- // Create directory by an LDP PUT to the container's .meta resource
182
- resourceUrl = `${resourceUrl}${resourceUrl.endsWith('/') ? '' : '/'}` // ${ldp.suffixMeta}`
183
- if (originalUrl && !originalUrl.endsWith('/')) {
184
- originalUrl += '/'
185
- }
186
- }
187
- // const { url: putUrl } = await this.resourceMapper.mapFileToUrl({ path: resourceUrl, hostname })
188
164
 
189
165
  await ldp.put(resourceUrl, stream, contentType)
190
- return URL.parse(originalUrl).path
166
+ return URL.parse(resourceUrl).path
191
167
  }
192
168
 
193
169
  isAuxResource (slug, extension) {
@@ -249,10 +225,6 @@ class LDP {
249
225
  throw error(400, 'Resource with a $.ext is not allowed by the server')
250
226
  }
251
227
 
252
- // check if a folder or file with same name exists
253
- // const urlItem = url.url || url
254
- await this.checkItemName(url)
255
-
256
228
  // First check if we are above quota
257
229
  let isOverQuota
258
230
  // Someone had a reason to make url actually a req sometimes but not
@@ -355,8 +327,9 @@ class LDP {
355
327
  } catch (err) { }
356
328
  }
357
329
 
330
+ // check if a document (or container) has the same name than a document (or container)
358
331
  async checkItemName (url) {
359
- let testName
332
+ let testName, testPath
360
333
  const { hostname, pathname } = this.resourceMapper._parseUrl(url) // (url.url || url)
361
334
  let itemUrl = this.resourceMapper.resolveUrl(hostname, pathname)
362
335
  const container = itemUrl.endsWith('/')
@@ -373,11 +346,11 @@ class LDP {
373
346
  const { pathname } = this.resourceMapper._parseUrl(itemUrl) // (url.url || url)
374
347
  // check not at root
375
348
  if (pathname !== '/') {
376
- await this.checkItemName(itemUrl)
349
+ return await this.checkItemName(itemUrl)
377
350
  }
378
351
  }
379
352
  if (testName) {
380
- throw error(404, 'Container and resource cannot have the same name in URI')
353
+ throw error(404, `${testPath}: Container and resource cannot have the same name in URI`)
381
354
  }
382
355
  }
383
356
 
@@ -606,17 +579,26 @@ class LDP {
606
579
  }
607
580
  }
608
581
 
609
- async getAvailableUrl (hostname, containerURI, { slug = uuid.v1(), extension }) {
582
+ async getAvailableUrl (hostname, containerURI, { slug = uuid.v1(), extension, container }) {
610
583
  let requestUrl = this.resourceMapper.resolveUrl(hostname, containerURI)
611
- requestUrl = requestUrl.replace(/\/*$/, '/')
584
+ requestUrl = requestUrl.replace(/\/*$/, '/') // ??? what for
612
585
 
613
- const { path: containerFilePath } = await this.resourceMapper.mapUrlToFile({ url: requestUrl })
614
- let fileName = slug.endsWith(extension) || slug.endsWith(this.suffixAcl) || slug.endsWith(this.suffixMeta) ? slug : slug + extension
615
- if (await promisify(fs.exists)(utilPath.join(containerFilePath, fileName))) {
616
- fileName = `${uuid.v1()}-${fileName}`
617
- }
618
-
619
- return requestUrl + fileName
586
+ let itemName = slug.endsWith(extension) || slug.endsWith(this.suffixAcl) || slug.endsWith(this.suffixMeta) ? slug : slug + extension
587
+ try {
588
+ // check resource exists
589
+ const context = container ? '/' : ''
590
+ await this.resourceMapper.mapUrlToFile({ url: (requestUrl + itemName + context) })
591
+ itemName = `${uuid.v1()}-${itemName}`
592
+ } catch (e) {
593
+ try {
594
+ // check resource with same name exists
595
+ const context = !container ? '/' : ''
596
+ await this.resourceMapper.mapUrlToFile({ url: (requestUrl + itemName + context) })
597
+ itemName = `${uuid.v1()}-${itemName}`
598
+ } catch (e) {}
599
+ }
600
+ if (container) itemName += '/'
601
+ return requestUrl + itemName
620
602
  }
621
603
 
622
604
  getTrustedOrigins (req) {
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.8",
4
+ "version": "5.7.9-alpha",
5
5
  "author": {
6
6
  "name": "Tim Berners-Lee",
7
7
  "email": "timbl@w3.org"
@@ -121,7 +121,7 @@
121
121
  "dirty-chai": "2.0.1",
122
122
  "eslint": "^7.32.0",
123
123
  "localstorage-memory": "1.0.3",
124
- "mocha": "^9.2.2",
124
+ "mocha": "^10.2.0",
125
125
  "nock": "^13.4.0",
126
126
  "node-mocks-http": "^1.14.0",
127
127
  "nyc": "15.1.0",