Skip to content

Commit 8a21094

Browse files
authored
Merge pull request #960 from rubensworks/feature/new-resource-mapper-2
Wire up ResourceMapper
2 parents 6170cc8 + a7b5e6d commit 8a21094

26 files changed

+476
-472
lines changed

config/defaults.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@ module.exports = {
1313
'webid': true,
1414
'strictOrigin': true,
1515
'originsAllowed': ['https://apps.solid.invalid'],
16-
'dataBrowserPath': 'default',
17-
'defaultContentType': 'text/turtle'
16+
'dataBrowserPath': 'default'
1817

1918
// For use in Enterprises to configure a HTTP proxy for all outbound HTTP requests from the SOLID server (we use
2019
// https://www.npmjs.com/package/global-tunnel-ng).

lib/create-app.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ const options = require('./handlers/options')
2323
const debug = require('./debug').authentication
2424
const path = require('path')
2525
const { routeResolvedFile } = require('./utils')
26-
const LegacyResourceMapper = require('./legacy-resource-mapper')
26+
const ResourceMapper = require('./resource-mapper')
2727

2828
const corsSettings = cors({
2929
methods: [
@@ -42,7 +42,7 @@ function createApp (argv = {}) {
4242

4343
argv.host = SolidHost.from({ port: argv.port, serverUri: argv.serverUri })
4444

45-
argv.resourceMapper = new LegacyResourceMapper({
45+
argv.resourceMapper = new ResourceMapper({
4646
rootUrl: argv.serverUri,
4747
rootPath: argv.root || process.cwd(),
4848
includeHost: argv.multiuser,

lib/handlers/copy.js

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,16 @@ async function handler (req, res, next) {
2222
const ldp = req.app.locals.ldp
2323
const serverRoot = ldp.resourceMapper.resolveUrl(req.hostname)
2424
const copyFromUrl = fromExternal ? copyFrom : serverRoot + copyFrom
25-
const copyTo = res.locals.path || req.path
26-
const { path: copyToPath } = await ldp.resourceMapper.mapUrlToFile({ url: req })
27-
ldpCopy(copyToPath, copyFromUrl, function (err) {
28-
if (err) {
29-
let statusCode = err.statusCode || 500
30-
let errorMessage = err.statusMessage || err.message
31-
debug.handlers('Error with COPY request:' + errorMessage)
32-
return next(error(statusCode, errorMessage))
33-
}
34-
res.set('Location', copyTo)
35-
res.sendStatus(201)
36-
next()
37-
})
25+
const copyToUrl = res.locals.path || req.path
26+
try {
27+
await ldpCopy(ldp.resourceMapper, copyToUrl, copyFromUrl)
28+
} catch (err) {
29+
let statusCode = err.statusCode || 500
30+
let errorMessage = err.statusMessage || err.message
31+
debug.handlers('Error with COPY request:' + errorMessage)
32+
return next(error(statusCode, errorMessage))
33+
}
34+
res.set('Location', copyToUrl)
35+
res.sendStatus(201)
36+
next()
3837
}

lib/handlers/get.js

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -75,16 +75,15 @@ async function handler (req, res, next) {
7575
// Till here it must exist
7676
if (!includeBody) {
7777
debug('HEAD only')
78-
const mappedFile = await ldp.resourceMapper.mapFileToUrl({ path })
79-
contentType = mappedFile.contentType
80-
res.setHeader('Content-Type', contentType)
78+
res.setHeader('Content-Type', ret.contentType)
8179
res.status(200).send('OK')
8280
return next()
8381
}
8482

8583
// Handle dataBrowser
8684
if (requestedType && requestedType.includes('text/html')) {
87-
let mimeTypeByExt = mime.lookup(_path.basename(path))
85+
const { path: filename } = await ldp.resourceMapper.mapUrlToFile({ url: options })
86+
let mimeTypeByExt = mime.lookup(_path.basename(filename))
8887
let isHtmlResource = mimeTypeByExt && mimeTypeByExt.includes('html')
8988
let useDataBrowser = ldp.dataBrowserPath && (
9089
container ||
@@ -136,7 +135,9 @@ async function handler (req, res, next) {
136135

137136
async function globHandler (req, res, next) {
138137
const ldp = req.app.locals.ldp
139-
const { path: filename } = await ldp.resourceMapper.mapUrlToFile({ url: req })
138+
// TODO: This is a hack, that does not check if the target file exists, as this is quite complex with globbing.
139+
// TODO: Proper support for this is not implemented, as globbing support might be removed in the future.
140+
const filename = ldp.resourceMapper._getFullPath(req)
140141
const requestUri = (await ldp.resourceMapper.mapFileToUrl({ path: filename, hostname: req.hostname })).url
141142

142143
const globOptions = {

lib/handlers/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@ async function handler (req, res, next) {
99
const ldp = req.app.locals.ldp
1010
const negotiator = new Negotiator(req)
1111
const requestedType = negotiator.mediaType()
12-
const { path: filename } = await req.app.locals.ldp.resourceMapper.mapUrlToFile({ url: req })
1312

1413
try {
14+
const { path: filename } = await req.app.locals.ldp.resourceMapper.mapUrlToFile({ url: req })
15+
1516
const stats = await ldp.stat(filename)
1617
if (!stats.isDirectory()) {
1718
return next()

lib/handlers/patch.js

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,32 @@ const error = require('../http-error')
99
const $rdf = require('rdflib')
1010
const crypto = require('crypto')
1111
const overQuota = require('../utils').overQuota
12+
const getContentType = require('../utils').getContentType
1213

1314
// Patch parsers by request body content type
1415
const PATCH_PARSERS = {
1516
'application/sparql-update': require('./patch/sparql-update-parser.js'),
1617
'text/n3': require('./patch/n3-patch-parser.js')
1718
}
1819

20+
const DEFAULT_FOR_NEW_CONTENT_TYPE = 'text/turtle'
21+
1922
// Handles a PATCH request
2023
async function patchHandler (req, res, next) {
2124
debug(`PATCH -- ${req.originalUrl}`)
2225
res.header('MS-Author-Via', 'SPARQL')
2326
try {
2427
// Obtain details of the target resource
2528
const ldp = req.app.locals.ldp
26-
const { path, contentType } = await ldp.resourceMapper.mapUrlToFile({ url: req })
29+
let path, contentType
30+
try {
31+
// First check if the file already exists
32+
({ path, contentType } = await ldp.resourceMapper.mapUrlToFile({ url: req }))
33+
} catch (err) {
34+
// If the file doesn't exist, request one to be created with the default content type
35+
({ path, contentType } = await ldp.resourceMapper.mapUrlToFile(
36+
{ url: req, createIfNotExists: true, contentType: DEFAULT_FOR_NEW_CONTENT_TYPE }))
37+
}
2738
const { url } = await ldp.resourceMapper.mapFileToUrl({ path, hostname: req.hostname })
2839
const resource = { path, contentType, url }
2940
debug('PATCH -- Target <%s> (%s)', url, contentType)
@@ -32,7 +43,7 @@ async function patchHandler (req, res, next) {
3243
const patch = {}
3344
patch.text = req.body ? req.body.toString() : ''
3445
patch.uri = `${url}#patch-${hash(patch.text)}`
35-
patch.contentType = (req.get('content-type') || '').match(/^[^;\s]*/)[0]
46+
patch.contentType = getContentType(req.headers)
3647
debug('PATCH -- Received patch (%d bytes, %s)', patch.text.length, patch.contentType)
3748
const parsePatch = PATCH_PARSERS[patch.contentType]
3849
if (!parsePatch) {

lib/handlers/post.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@ const header = require('../header')
77
const patch = require('./patch')
88
const error = require('../http-error')
99
const { extensions } = require('mime-types')
10+
const getContentType = require('../utils').getContentType
1011

1112
async function handler (req, res, next) {
1213
const ldp = req.app.locals.ldp
13-
const contentType = req.get('content-type')
14+
const contentType = getContentType(req.headers)
1415
debug('content-type is ', contentType)
1516
// Handle SPARQL(-update?) query
1617
if (contentType === 'application/sparql' ||
@@ -58,7 +59,7 @@ async function handler (req, res, next) {
5859
const { url: putUrl } = await ldp.resourceMapper.mapFileToUrl(
5960
{ path: ldp.resourceMapper._rootPath + path.join(containerPath, filename), hostname: req.hostname })
6061
try {
61-
await ldp.put(putUrl, file)
62+
await ldp.put(putUrl, file, mimetype)
6263
} catch (err) {
6364
busboy.emit('error', err)
6465
}

lib/handlers/put.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
module.exports = handler
22

33
const debug = require('debug')('solid:put')
4+
const getContentType = require('../utils').getContentType
45

56
async function handler (req, res, next) {
67
const ldp = req.app.locals.ldp
78
debug(req.originalUrl)
89
res.header('MS-Author-Via', 'SPARQL')
910

1011
try {
11-
await ldp.put(req, req)
12+
await ldp.put(req, req, getContentType(req.headers))
1213
debug('succeded putting the file')
1314

1415
res.sendStatus(201)

lib/header.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,19 @@ function addLinks (res, fileMetadata) {
4343

4444
async function linksHandler (req, res, next) {
4545
const ldp = req.app.locals.ldp
46-
const { path: filename } = await ldp.resourceMapper.mapUrlToFile(req)
46+
let filename
47+
try {
48+
// Hack: createIfNotExists is set to true for PUT or PATCH requests
49+
// because the file might not exist yet at this point.
50+
// But it will be created afterwards.
51+
// This should be improved with the new server architecture.
52+
({ path: filename } = await ldp.resourceMapper
53+
.mapUrlToFile({ url: req, createIfNotExists: req.method === 'PUT' || req.method === 'PATCH' }))
54+
} catch (e) {
55+
// Silently ignore errors here
56+
// Later handlers will error as well, but they will be able to given a more concrete error message (like 400 or 404)
57+
return next()
58+
}
4759

4860
if (path.extname(filename) === ldp.suffixMeta) {
4961
debug.metadata('Trying to access metadata file as regular file.')

lib/ldp-copy.js

Lines changed: 33 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ module.exports = copy
33
const debug = require('./debug')
44
const fs = require('fs')
55
const mkdirp = require('fs-extra').mkdirp
6+
const error = require('./http-error')
67
const path = require('path')
78
const http = require('http')
89
const https = require('https')
10+
const getContentType = require('./utils').getContentType
911

1012
/**
1113
* Cleans up a file write stream (ends stream, deletes the file).
@@ -21,47 +23,51 @@ function cleanupFileStream (stream) {
2123

2224
/**
2325
* Performs an LDP Copy operation, imports a remote resource to a local path.
24-
* @param copyToPath {String} Local path to copy the resource into
26+
* @param resourceMapper {ResourceMapper} A resource mapper instance.
27+
* @param copyToUri {Object} The location (in the current domain) to copy to.
2528
* @param copyFromUri {String} Location of remote resource to copy from
26-
* @param callback {Function} Node error callback
29+
* @return A promise resolving when the copy operation is finished
2730
*/
28-
function copy (copyToPath, copyFromUri, callback) {
29-
mkdirp(path.dirname(copyToPath), function (err) {
30-
if (err) {
31-
debug.handlers('COPY -- Error creating destination directory: ' + err)
32-
return callback(
33-
new Error('Failed to create the path to the destination resource: ' +
34-
err))
35-
}
36-
const destinationStream = fs.createWriteStream(copyToPath)
37-
.on('error', function (err) {
38-
cleanupFileStream(this)
39-
return callback(new Error('Error writing data: ' + err))
40-
})
41-
.on('finish', function () {
42-
// Success
43-
debug.handlers('COPY -- Wrote data to: ' + copyToPath)
44-
callback()
45-
})
31+
function copy (resourceMapper, copyToUri, copyFromUri) {
32+
return new Promise((resolve, reject) => {
4633
const request = /^https:/.test(copyFromUri) ? https : http
4734
request.get(copyFromUri)
4835
.on('error', function (err) {
4936
debug.handlers('COPY -- Error requesting source file: ' + err)
5037
this.end()
51-
cleanupFileStream(destinationStream)
52-
return callback(new Error('Error writing data: ' + err))
38+
return reject(new Error('Error writing data: ' + err))
5339
})
5440
.on('response', function (response) {
5541
if (response.statusCode !== 200) {
56-
debug.handlers('COPY -- HTTP error reading source file: ' +
57-
response.statusMessage)
42+
debug.handlers('COPY -- HTTP error reading source file: ' + response.statusMessage)
5843
this.end()
59-
cleanupFileStream(destinationStream)
6044
let error = new Error('Error reading source file: ' + response.statusMessage)
6145
error.statusCode = response.statusCode
62-
return callback(error)
46+
return reject(error)
6347
}
64-
response.pipe(destinationStream)
48+
// Grab the content type from the source
49+
const contentType = getContentType(response.headers)
50+
resourceMapper.mapUrlToFile({ url: copyToUri, createIfNotExists: true, contentType })
51+
.then(({ path: copyToPath }) => {
52+
mkdirp(path.dirname(copyToPath), function (err) {
53+
if (err) {
54+
debug.handlers('COPY -- Error creating destination directory: ' + err)
55+
return reject(new Error('Failed to create the path to the destination resource: ' + err))
56+
}
57+
const destinationStream = fs.createWriteStream(copyToPath)
58+
.on('error', function (err) {
59+
cleanupFileStream(this)
60+
return reject(new Error('Error writing data: ' + err))
61+
})
62+
.on('finish', function () {
63+
// Success
64+
debug.handlers('COPY -- Wrote data to: ' + copyToPath)
65+
resolve()
66+
})
67+
response.pipe(destinationStream)
68+
})
69+
})
70+
.catch(() => reject(error(500, 'Could not find target file to copy')))
6571
})
6672
})
6773
}

0 commit comments

Comments
 (0)