-
Notifications
You must be signed in to change notification settings - Fork 306
Expand file tree
/
Copy pathheader.mjs
More file actions
138 lines (128 loc) · 5.23 KB
/
header.mjs
File metadata and controls
138 lines (128 loc) · 5.23 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
import li from 'li'
import path from 'path'
import metadata from './metadata.mjs'
import debug from './debug.mjs'
import { pathBasename } from './utils.mjs'
import HTTPError from './http-error.mjs'
const MODES = ['Read', 'Write', 'Append', 'Control']
const PERMISSIONS = MODES.map(m => m.toLowerCase())
export function addLink (res, value, rel) {
const oldLink = res.get('Link')
if (oldLink === undefined) {
res.set('Link', '<' + value + '>; rel="' + rel + '"')
} else {
res.set('Link', oldLink + ', ' + '<' + value + '>; rel="' + rel + '"')
}
}
export function addLinks (res, fileMetadata) {
if (fileMetadata.isResource) {
addLink(res, 'http://www.w3.org/ns/ldp#Resource', 'type')
}
if (fileMetadata.isSourceResource) {
addLink(res, 'http://www.w3.org/ns/ldp#RDFSource', 'type')
}
if (fileMetadata.isContainer) {
addLink(res, 'http://www.w3.org/ns/ldp#Container', 'type')
}
if (fileMetadata.isBasicContainer) {
addLink(res, 'http://www.w3.org/ns/ldp#BasicContainer', 'type')
}
if (fileMetadata.isDirectContainer) {
addLink(res, 'http://www.w3.org/ns/ldp#DirectContainer', 'type')
}
if (fileMetadata.isStorage) {
addLink(res, 'http://www.w3.org/ns/pim/space#Storage', 'type')
}
}
export async function linksHandler (req, res, next) {
const ldp = req.app.locals.ldp
let filename
try {
// Hack: createIfNotExists is set to true for PUT or PATCH requests
// because the file might not exist yet at this point.
// But it will be created afterwards.
// This should be improved with the new server architecture.
({ path: filename } = await ldp.resourceMapper
.mapUrlToFile({ url: req, createIfNotExists: req.method === 'PUT' || req.method === 'PATCH' }))
} catch (e) {
// Silently ignore errors here
// Later handlers will error as well, but they will be able to given a more concrete error message (like 400 or 404)
return next()
}
if (path.extname(filename) === ldp.suffixMeta) {
debug.metadata('Trying to access metadata file as regular file.')
return next(HTTPError(404, 'Trying to access metadata file as regular file'))
}
const fileMetadata = new metadata.Metadata()
if (req.path.endsWith('/')) {
// do not add storage header in serverUri
if (req.path === '/') fileMetadata.isStorage = true
fileMetadata.isContainer = true
fileMetadata.isBasicContainer = true
} else {
fileMetadata.isResource = true
}
// Add LDP-required Accept-Post header for OPTIONS request to containers
if (fileMetadata.isContainer && req.method === 'OPTIONS') {
res.header('Accept-Post', '*/*')
}
// Add ACL and Meta Link in header
addLink(res, pathBasename(req.path) + ldp.suffixAcl, 'acl')
addLink(res, pathBasename(req.path) + ldp.suffixMeta, 'describedBy')
// Add other Link headers
addLinks(res, fileMetadata)
next()
}
export function parseMetadataFromHeader (linkHeader) {
const fileMetadata = new metadata.Metadata()
if (linkHeader === undefined) {
return fileMetadata
}
const links = linkHeader.split(',')
for (const linkIndex in links) {
const link = links[linkIndex]
const parsedLinks = li.parse(link)
for (const rel in parsedLinks) {
if (rel === 'type') {
if (parsedLinks[rel] === 'http://www.w3.org/ns/ldp#Resource') {
fileMetadata.isResource = true
} else if (parsedLinks[rel] === 'http://www.w3.org/ns/ldp#RDFSource') {
fileMetadata.isSourceResource = true
} else if (parsedLinks[rel] === 'http://www.w3.org/ns/ldp#Container') {
fileMetadata.isContainer = true
} else if (parsedLinks[rel] === 'http://www.w3.org/ns/ldp#BasicContainer') {
fileMetadata.isBasicContainer = true
} else if (parsedLinks[rel] === 'http://www.w3.org/ns/ldp#DirectContainer') {
fileMetadata.isDirectContainer = true
} else if (parsedLinks[rel] === 'http://www.w3.org/ns/pim/space#Storage') {
fileMetadata.isStorage = true
}
}
}
}
return fileMetadata
}
// Adds a header that describes the user's permissions
export async function addPermissions (req, res, next) {
const { acl, session } = req
if (!acl) return next()
// Turn permissions for the public and the user into a header
const ldp = req.app.locals.ldp
const resource = ldp.resourceMapper.resolveUrl(req.hostname, req.path)
let [publicPerms, userPerms] = await Promise.all([
getPermissionsFor(acl, null, req),
getPermissionsFor(acl, session.userId, req)
])
if (resource.endsWith('.acl') && userPerms === '' && await ldp.isOwner(session.userId, req.hostname)) userPerms = 'control'
debug.ACL(`Permissions on ${resource} for ${session.userId || '(none)'}: ${userPerms}`)
debug.ACL(`Permissions on ${resource} for public: ${publicPerms}`)
// Set the header
res.set('WAC-Allow', `user="${userPerms}",public="${publicPerms}"`)
next()
}
// Gets the permissions string for the given user and resource
async function getPermissionsFor (acl, user, req) {
const accesses = MODES.map(mode => acl.can(user, mode))
const allowed = await Promise.all(accesses)
return PERMISSIONS.filter((mode, i) => allowed[i]).join(' ')
}