Skip to content

Commit e576ea9

Browse files
committed
Merge branch 'master' of https://github.com/solid/node-solid-server into fix/#1171
2 parents b5ef317 + 9a20318 commit e576ea9

File tree

8 files changed

+176
-102
lines changed

8 files changed

+176
-102
lines changed

bin/lib/cli-utils.js

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
const fs = require('fs-extra')
2-
const { cyan, bold } = require('colorette')
2+
const { red, cyan, bold } = require('colorette')
33
const { URL } = require('url')
44
const LDP = require('../../lib/ldp')
55
const AccountManager = require('../../lib/models/account-manager')
@@ -41,7 +41,22 @@ function loadConfig (program, options) {
4141
const config = JSON.parse(file)
4242
argv = { ...config, ...argv }
4343
} catch (err) {
44-
// No file exists, not a problem
44+
// If config file was specified, but it doesn't exist, stop with error message
45+
if (typeof argv['configFile'] !== 'undefined') {
46+
if (!fs.existsSync(configFile)) {
47+
console.log(red(bold('ERR')), 'Config file ' + configFile + ' doesn\'t exist.')
48+
process.exit(1)
49+
}
50+
}
51+
52+
// If the file exists, but parsing failed, stop with error message
53+
if (fs.existsSync(configFile)) {
54+
console.log(red(bold('ERR')), 'config file ' + configFile + ' couldn\'t be parsed: ' + err)
55+
process.exit(1)
56+
}
57+
58+
// Legacy behavior - if config file does not exist, start with default
59+
// values, but an info message to create a config file.
4560
console.log(cyan(bold('TIP')), 'create a config.json: `$ solid init`')
4661
}
4762

lib/acl-checker.js

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ const aclCheck = require('@solid/acl-check')
77
const { URL } = require('url')
88
const { promisify } = require('util')
99
const fs = require('fs')
10+
const Url = require('url')
11+
const httpFetch = require('node-fetch')
1012

1113
const DEFAULT_ACL_SUFFIX = '.acl'
1214
const ACL = rdf.Namespace('http://www.w3.org/ns/auth/acl#')
@@ -156,7 +158,7 @@ class ACLChecker {
156158
return new ACLChecker(resource, {
157159
agentOrigin: req.get('origin'),
158160
// host: req.get('host'),
159-
fetch: fetchFromLdp(ldp.resourceMapper),
161+
fetch: fetchLocalOrRemote(ldp.resourceMapper, ldp.serverUri),
160162
fetchGraph: (uri, options) => {
161163
// first try loading from local fs
162164
return ldp.getGraph(uri, options.contentType)
@@ -178,17 +180,27 @@ class ACLChecker {
178180
* - `callback(null, graph)` with the parsed RDF graph of the fetched resource
179181
* @return {Function} Returns a `fetch(uri, callback)` handler
180182
*/
181-
function fetchFromLdp (mapper) {
183+
function fetchLocalOrRemote (mapper, serverUri) {
182184
return async function fetch (url, graph = rdf.graph()) {
183185
// Convert the URL into a filename
184-
let path, contentType
185-
try {
186-
({ path, contentType } = await mapper.mapUrlToFile({ url }))
187-
} catch (err) {
188-
throw new HTTPError(404, err)
186+
let body, path, contentType
187+
188+
if (Url.parse(url).host.includes(Url.parse(serverUri).host)) {
189+
// Fetch the acl from local
190+
try {
191+
({ path, contentType } = await mapper.mapUrlToFile({ url }))
192+
} catch (err) {
193+
throw new HTTPError(404, err)
194+
}
195+
// Read the file from disk
196+
body = await promisify(fs.readFile)(path, { 'encoding': 'utf8' })
197+
} else {
198+
// Fetch the acl from the internet
199+
const response = await httpFetch(url)
200+
body = await response.text()
201+
contentType = response.headers.get('content-type')
189202
}
190-
// Read the file from disk
191-
const body = await promisify(fs.readFile)(path, { 'encoding': 'utf8' })
203+
192204
// Parse the file as Turtle
193205
rdf.parse(body, graph, url, contentType)
194206
return graph

lib/handlers/patch.js

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const $rdf = require('rdflib')
1111
const crypto = require('crypto')
1212
const overQuota = require('../utils').overQuota
1313
const getContentType = require('../utils').getContentType
14-
const { lock } = require('proper-lockfile')
14+
const withLock = require('../lock')
1515

1616
// Patch parsers by request body content type
1717
const PATCH_PARSERS = {
@@ -25,7 +25,6 @@ const DEFAULT_FOR_NEW_CONTENT_TYPE = 'text/turtle'
2525
async function patchHandler (req, res, next) {
2626
debug(`PATCH -- ${req.originalUrl}`)
2727
res.header('MS-Author-Via', 'SPARQL')
28-
let releaseLock
2928
try {
3029
// Obtain details of the target resource
3130
const ldp = req.app.locals.ldp
@@ -65,19 +64,16 @@ async function patchHandler (req, res, next) {
6564
}
6665

6766
// Patch the graph and write it back to the file
68-
releaseLock = await lock(path, { retries: 10, realpath: false })
69-
const graph = await readGraph(resource)
70-
await applyPatch(patchObject, graph, url)
71-
const result = await writeGraph(graph, resource, ldp.resourceMapper.rootPath, ldp.serverUri)
67+
const result = await withLock(path, { mustExist: false }, async () => {
68+
const graph = await readGraph(resource)
69+
await applyPatch(patchObject, graph, url)
70+
return writeGraph(graph, resource, ldp.resourceMapper.rootPath, ldp.serverUri)
71+
})
7272

7373
// Send the result to the client
7474
res.send(result)
7575
} catch (err) {
7676
return next(err)
77-
} finally {
78-
if (releaseLock) {
79-
await releaseLock()
80-
}
8177
}
8278
return next()
8379
}

lib/ldp.js

Lines changed: 24 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ const parse = require('./utils').parse
1818
const fetch = require('node-fetch')
1919
const { promisify } = require('util')
2020
const URI = require('urijs')
21-
const { lock } = require('proper-lockfile')
21+
const withLock = require('./lock')
2222

2323
const RDF_MIME_TYPES = new Set([
2424
'text/turtle', // .ttl
@@ -78,17 +78,11 @@ class LDP {
7878
}
7979

8080
async readResource (url) {
81-
let releaseLock
8281
try {
8382
const { path } = await this.resourceMapper.mapUrlToFile({ url })
84-
releaseLock = await lock(path, { retries: 10 })
85-
return await promisify(fs.readFile)(path, {encoding: 'utf8'})
83+
return await withLock(path, () => promisify(fs.readFile)(path, {encoding: 'utf8'}))
8684
} catch (err) {
8785
throw error(err.status, err.message)
88-
} finally {
89-
if (releaseLock) {
90-
await releaseLock()
91-
}
9286
}
9387
}
9488

@@ -244,24 +238,16 @@ class LDP {
244238
}
245239

246240
// Directory created, now write the file
247-
let releaseLock
248-
try {
249-
releaseLock = await lock(path, { retries: 10, realpath: false })
250-
return await new Promise((resolve, reject) => {
251-
const file = stream.pipe(fs.createWriteStream(path))
252-
file.on('error', function () {
253-
reject(error(500, 'Error writing data'))
254-
})
255-
file.on('finish', function () {
256-
debug.handlers('PUT -- Wrote data to: ' + path)
257-
resolve()
258-
})
241+
return withLock(path, { mustExist: false }, () => new Promise((resolve, reject) => {
242+
const file = stream.pipe(fs.createWriteStream(path))
243+
file.on('error', function () {
244+
reject(error(500, 'Error writing data'))
259245
})
260-
} finally {
261-
if (releaseLock) {
262-
await releaseLock()
263-
}
264-
}
246+
file.on('finish', function () {
247+
debug.handlers('PUT -- Wrote data to: ' + path)
248+
resolve()
249+
})
250+
}))
265251
}
266252

267253
async exists (hostname, path, searchIndex = true) {
@@ -379,26 +365,18 @@ class LDP {
379365
chunksize = (end - start) + 1
380366
contentRange = 'bytes ' + start + '-' + end + '/' + total
381367
}
382-
let releaseLock
383-
try {
384-
releaseLock = await lock(path, { retries: 10 })
385-
return await new Promise((resolve, reject) => {
386-
const stream = fs.createReadStream(path, start && end ? {start, end} : {})
387-
stream
388-
.on('error', function (err) {
389-
debug.handlers(`GET -- error reading ${path}: ${err.message}`)
390-
return reject(error(err, "Can't read file " + err))
391-
})
392-
.on('open', function () {
393-
debug.handlers(`GET -- Reading ${path}`)
394-
return resolve({ stream, contentType, container: false, contentRange, chunksize })
395-
})
396-
})
397-
} finally {
398-
if (releaseLock) {
399-
await releaseLock()
400-
}
401-
}
368+
return withLock(path, () => new Promise((resolve, reject) => {
369+
const stream = fs.createReadStream(path, start && end ? {start, end} : {})
370+
stream
371+
.on('error', function (err) {
372+
debug.handlers(`GET -- error reading ${path}: ${err.message}`)
373+
return reject(error(err, "Can't read file " + err))
374+
})
375+
.on('open', function () {
376+
debug.handlers(`GET -- Reading ${path}`)
377+
return resolve({ stream, contentType, container: false, contentRange, chunksize })
378+
})
379+
}))
402380
}
403381
}
404382

@@ -447,17 +425,11 @@ class LDP {
447425
}
448426

449427
async deleteResource (path) {
450-
let releaseLock
451428
try {
452-
releaseLock = await lock(path, { retries: 10 })
453-
return await promisify(fs.unlink)(path)
429+
return await withLock(path, { mustExist: false }, () => promisify(fs.unlink)(path))
454430
} catch (err) {
455431
debug.container('DELETE -- unlink() error: ' + err)
456432
throw error(err, 'Failed to delete resource')
457-
} finally {
458-
if (releaseLock) {
459-
await releaseLock()
460-
}
461433
}
462434
}
463435

lib/lock.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
const { lock } = require('proper-lockfile')
2+
3+
const staleSeconds = 30
4+
5+
// Obtains a lock on the path, and maintains it until the callback finishes
6+
function withLock (path, options = {}, callback = options) {
7+
return new Promise(async (resolve, reject) => {
8+
let releaseLock, result
9+
try {
10+
// Obtain the lock
11+
releaseLock = await lock(path, {
12+
retries: 10,
13+
update: 1000,
14+
stale: staleSeconds * 1000,
15+
realpath: !!options.mustExist,
16+
onCompromised: () =>
17+
reject(new Error(`The file at ${path} was not updated within ${staleSeconds}s.`))
18+
})
19+
// Hold on to the lock until the callback's returned promise resolves
20+
result = await callback()
21+
} catch (error) {
22+
reject(error)
23+
// Ensure the lock is always released
24+
} finally {
25+
await releaseLock()
26+
}
27+
resolve(result)
28+
})
29+
}
30+
31+
module.exports = withLock

0 commit comments

Comments
 (0)