Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
665b84d
Make ResourceMapper interpret URLs ending with '/' as index pages
rubensworks Nov 20, 2018
22168ac
Improve error message when file is not found in ResourceMapper
rubensworks Nov 21, 2018
f76710e
Map URLs to folders when no index is available
rubensworks Nov 21, 2018
0c1ce05
Make text/turtle the default content type for mapping .acl files
rubensworks Nov 21, 2018
31860bb
Require Content-Type in POST request
rubensworks Nov 21, 2018
62e6863
Inherit content type from source in COPY handler
rubensworks Nov 21, 2018
692b4e2
Transition to new ResourceMapper
rubensworks Nov 21, 2018
f996761
Allow linksHandler to map resources that do (not) exist yet
rubensworks Nov 21, 2018
2af9560
Allow patch handler to map resources that do no exist yet
rubensworks Nov 21, 2018
d776c20
Fix incorrect response content type being passed in HEAD
rubensworks Nov 21, 2018
2f7fbb3
Fix databrowser being used instead of index.html files
rubensworks Nov 21, 2018
f9e1b70
Fix globbing crash with new ResourceMapper
rubensworks Nov 21, 2018
73cae8a
Fix minor LDP issues with new ResourceMapper
rubensworks Nov 21, 2018
c3abe25
Update unit tests to new ResourceMapper
rubensworks Nov 21, 2018
d6f6323
Set default content type to application/octet-stream
rubensworks Nov 22, 2018
0dc8fe2
Remove unneeded field ldp#turtleExtensions
rubensworks Nov 22, 2018
81df5c6
Fix .meta files not being returned as Turtle
rubensworks Nov 22, 2018
e71d5c3
Remove hardcoded default content types where unneeded
rubensworks Nov 22, 2018
592c378
Improve comment on globbing hack
rubensworks Nov 23, 2018
460d0ed
Improve naming of constant for new files content types
rubensworks Nov 23, 2018
04667fa
Use application/octet-stream as default content type in ResourceMapper
rubensworks Nov 23, 2018
4924bc6
Add helper function to safely extract content type from headers
rubensworks Nov 23, 2018
adb0691
Improve comment on hack in header.js
rubensworks Nov 23, 2018
868a975
Extract content type from headers when fetching remote graphs
rubensworks Nov 23, 2018
eb73ae2
Consistently call getContentType helper when needed
rubensworks Nov 23, 2018
fe0e456
Improve index variable name in ResourceMapper
rubensworks Nov 23, 2018
19843ac
Document removal of $ with indexes in ResourceMapper
rubensworks Nov 23, 2018
a7b5e6d
Resolve linting errors due to ResourceMapper changes
rubensworks Nov 23, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions config/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ module.exports = {
'webid': true,
'strictOrigin': true,
'originsAllowed': ['https://apps.solid.invalid'],
'dataBrowserPath': 'default',
'defaultContentType': 'text/turtle'
'dataBrowserPath': 'default'

// For use in Enterprises to configure a HTTP proxy for all outbound HTTP requests from the SOLID server (we use
// https://www.npmjs.com/package/global-tunnel-ng).
Expand Down
4 changes: 2 additions & 2 deletions lib/create-app.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const options = require('./handlers/options')
const debug = require('./debug').authentication
const path = require('path')
const { routeResolvedFile } = require('./utils')
const LegacyResourceMapper = require('./legacy-resource-mapper')
const ResourceMapper = require('./resource-mapper')

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

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

argv.resourceMapper = new LegacyResourceMapper({
argv.resourceMapper = new ResourceMapper({
rootUrl: argv.serverUri,
rootPath: argv.root || process.cwd(),
includeHost: argv.multiuser,
Expand Down
25 changes: 12 additions & 13 deletions lib/handlers/copy.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,16 @@ async function handler (req, res, next) {
const ldp = req.app.locals.ldp
const serverRoot = ldp.resourceMapper.resolveUrl(req.hostname)
const copyFromUrl = fromExternal ? copyFrom : serverRoot + copyFrom
const copyTo = res.locals.path || req.path
const { path: copyToPath } = await ldp.resourceMapper.mapUrlToFile({ url: req })
ldpCopy(copyToPath, copyFromUrl, function (err) {
if (err) {
let statusCode = err.statusCode || 500
let errorMessage = err.statusMessage || err.message
debug.handlers('Error with COPY request:' + errorMessage)
return next(error(statusCode, errorMessage))
}
res.set('Location', copyTo)
res.sendStatus(201)
next()
})
const copyToUrl = res.locals.path || req.path
try {
await ldpCopy(ldp.resourceMapper, copyToUrl, copyFromUrl)
} catch (err) {
let statusCode = err.statusCode || 500
let errorMessage = err.statusMessage || err.message
debug.handlers('Error with COPY request:' + errorMessage)
return next(error(statusCode, errorMessage))
}
res.set('Location', copyToUrl)
res.sendStatus(201)
next()
}
11 changes: 6 additions & 5 deletions lib/handlers/get.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,16 +75,15 @@ async function handler (req, res, next) {
// Till here it must exist
if (!includeBody) {
debug('HEAD only')
const mappedFile = await ldp.resourceMapper.mapFileToUrl({ path })
contentType = mappedFile.contentType
res.setHeader('Content-Type', contentType)
res.setHeader('Content-Type', ret.contentType)
res.status(200).send('OK')
return next()
}

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

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

const globOptions = {
Expand Down
3 changes: 2 additions & 1 deletion lib/handlers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ async function handler (req, res, next) {
const ldp = req.app.locals.ldp
const negotiator = new Negotiator(req)
const requestedType = negotiator.mediaType()
const { path: filename } = await req.app.locals.ldp.resourceMapper.mapUrlToFile({ url: req })

try {
const { path: filename } = await req.app.locals.ldp.resourceMapper.mapUrlToFile({ url: req })

const stats = await ldp.stat(filename)
if (!stats.isDirectory()) {
return next()
Expand Down
15 changes: 13 additions & 2 deletions lib/handlers/patch.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,32 @@ const error = require('../http-error')
const $rdf = require('rdflib')
const crypto = require('crypto')
const overQuota = require('../utils').overQuota
const getContentType = require('../utils').getContentType

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

const DEFAULT_FOR_NEW_CONTENT_TYPE = 'text/turtle'

// Handles a PATCH request
async function patchHandler (req, res, next) {
debug(`PATCH -- ${req.originalUrl}`)
res.header('MS-Author-Via', 'SPARQL')
try {
// Obtain details of the target resource
const ldp = req.app.locals.ldp
const { path, contentType } = await ldp.resourceMapper.mapUrlToFile({ url: req })
let path, contentType
try {
// First check if the file already exists
({ path, contentType } = await ldp.resourceMapper.mapUrlToFile({ url: req }))
} catch (err) {
// If the file doesn't exist, request one to be created with the default content type
({ path, contentType } = await ldp.resourceMapper.mapUrlToFile(
{ url: req, createIfNotExists: true, contentType: DEFAULT_FOR_NEW_CONTENT_TYPE }))
}
const { url } = await ldp.resourceMapper.mapFileToUrl({ path, hostname: req.hostname })
const resource = { path, contentType, url }
debug('PATCH -- Target <%s> (%s)', url, contentType)
Expand All @@ -32,7 +43,7 @@ async function patchHandler (req, res, next) {
const patch = {}
patch.text = req.body ? req.body.toString() : ''
patch.uri = `${url}#patch-${hash(patch.text)}`
patch.contentType = (req.get('content-type') || '').match(/^[^;\s]*/)[0]
patch.contentType = getContentType(req.headers)
debug('PATCH -- Received patch (%d bytes, %s)', patch.text.length, patch.contentType)
const parsePatch = PATCH_PARSERS[patch.contentType]
if (!parsePatch) {
Expand Down
5 changes: 3 additions & 2 deletions lib/handlers/post.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ const header = require('../header')
const patch = require('./patch')
const error = require('../http-error')
const { extensions } = require('mime-types')
const getContentType = require('../utils').getContentType

async function handler (req, res, next) {
const ldp = req.app.locals.ldp
const contentType = req.get('content-type')
const contentType = getContentType(req.headers)
debug('content-type is ', contentType)
// Handle SPARQL(-update?) query
if (contentType === 'application/sparql' ||
Expand Down Expand Up @@ -58,7 +59,7 @@ async function handler (req, res, next) {
const { url: putUrl } = await ldp.resourceMapper.mapFileToUrl(
{ path: ldp.resourceMapper._rootPath + path.join(containerPath, filename), hostname: req.hostname })
try {
await ldp.put(putUrl, file)
await ldp.put(putUrl, file, mimetype)
} catch (err) {
busboy.emit('error', err)
}
Expand Down
3 changes: 2 additions & 1 deletion lib/handlers/put.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
module.exports = handler

const debug = require('debug')('solid:put')
const getContentType = require('../utils').getContentType

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

try {
await ldp.put(req, req)
await ldp.put(req, req, getContentType(req.headers))
debug('succeded putting the file')

res.sendStatus(201)
Expand Down
14 changes: 13 additions & 1 deletion lib/header.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,19 @@ function addLinks (res, fileMetadata) {

async function linksHandler (req, res, next) {
const ldp = req.app.locals.ldp
const { path: filename } = await ldp.resourceMapper.mapUrlToFile(req)
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.')
Expand Down
60 changes: 33 additions & 27 deletions lib/ldp-copy.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ module.exports = copy
const debug = require('./debug')
const fs = require('fs')
const mkdirp = require('fs-extra').mkdirp
const error = require('./http-error')
const path = require('path')
const http = require('http')
const https = require('https')
const getContentType = require('./utils').getContentType

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

/**
* Performs an LDP Copy operation, imports a remote resource to a local path.
* @param copyToPath {String} Local path to copy the resource into
* @param resourceMapper {ResourceMapper} A resource mapper instance.
* @param copyToUri {Object} The location (in the current domain) to copy to.
* @param copyFromUri {String} Location of remote resource to copy from
* @param callback {Function} Node error callback
* @return A promise resolving when the copy operation is finished
*/
function copy (copyToPath, copyFromUri, callback) {
mkdirp(path.dirname(copyToPath), function (err) {
if (err) {
debug.handlers('COPY -- Error creating destination directory: ' + err)
return callback(
new Error('Failed to create the path to the destination resource: ' +
err))
}
const destinationStream = fs.createWriteStream(copyToPath)
.on('error', function (err) {
cleanupFileStream(this)
return callback(new Error('Error writing data: ' + err))
})
.on('finish', function () {
// Success
debug.handlers('COPY -- Wrote data to: ' + copyToPath)
callback()
})
function copy (resourceMapper, copyToUri, copyFromUri) {
return new Promise((resolve, reject) => {
const request = /^https:/.test(copyFromUri) ? https : http
request.get(copyFromUri)
.on('error', function (err) {
debug.handlers('COPY -- Error requesting source file: ' + err)
this.end()
cleanupFileStream(destinationStream)
return callback(new Error('Error writing data: ' + err))
return reject(new Error('Error writing data: ' + err))
})
.on('response', function (response) {
if (response.statusCode !== 200) {
debug.handlers('COPY -- HTTP error reading source file: ' +
response.statusMessage)
debug.handlers('COPY -- HTTP error reading source file: ' + response.statusMessage)
this.end()
cleanupFileStream(destinationStream)
let error = new Error('Error reading source file: ' + response.statusMessage)
error.statusCode = response.statusCode
return callback(error)
return reject(error)
}
response.pipe(destinationStream)
// Grab the content type from the source
const contentType = getContentType(response.headers)
resourceMapper.mapUrlToFile({ url: copyToUri, createIfNotExists: true, contentType })
.then(({ path: copyToPath }) => {
mkdirp(path.dirname(copyToPath), function (err) {
if (err) {
debug.handlers('COPY -- Error creating destination directory: ' + err)
return reject(new Error('Failed to create the path to the destination resource: ' + err))
}
const destinationStream = fs.createWriteStream(copyToPath)
.on('error', function (err) {
cleanupFileStream(this)
return reject(new Error('Error writing data: ' + err))
})
.on('finish', function () {
// Success
debug.handlers('COPY -- Wrote data to: ' + copyToPath)
resolve()
})
response.pipe(destinationStream)
})
})
.catch(() => reject(error(500, 'Could not find target file to copy')))
})
})
}
Loading