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.
- package/lib/handlers/patch.js +5 -1
- package/lib/handlers/put.js +46 -5
- package/lib/ldp-middleware.js +1 -1
- package/lib/ldp.js +25 -43
- package/package.json +2 -2
package/lib/handlers/patch.js
CHANGED
|
@@ -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
|
-
|
|
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 })
|
package/lib/handlers/put.js
CHANGED
|
@@ -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
|
-
//
|
|
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
|
-
|
|
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
|
}
|
package/lib/ldp-middleware.js
CHANGED
|
@@ -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('
|
|
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
|
-
//
|
|
176
|
-
|
|
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(
|
|
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,
|
|
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
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
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.
|
|
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": "^
|
|
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",
|