Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
62 changes: 45 additions & 17 deletions lib/handlers/patch.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,24 @@ const PATCH_PARSERS = {
'text/n3': require('./patch/n3-patch-parser.js')
}

// use media-type as contentType for new RDF resource
const DEFAULT_FOR_NEW_CONTENT_TYPE = 'text/turtle'

function contentTypeForNew (req) {
let contentTypeForNew = DEFAULT_FOR_NEW_CONTENT_TYPE
if (req.path.endsWith('.jsonld')) contentTypeForNew = 'application/ld+json'
else if (req.path.endsWith('.n3')) contentTypeForNew = 'text/n3'
else if (req.path.endsWith('.rdf')) contentTypeForNew = 'application/rdf+xml'
return contentTypeForNew
}

function contentForNew (contentType) {
let contentForNew = ''
if (contentType.includes('ld+json')) contentForNew = JSON.stringify('{}')
else if (contentType.includes('rdf+xml')) contentForNew = '<rdf:RDF\n xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">\n\n</rdf:RDF>'
return contentForNew
}

// Handles a PATCH request
async function patchHandler (req, res, next) {
debug(`PATCH -- ${req.originalUrl}`)
Expand All @@ -33,9 +49,9 @@ async function patchHandler (req, res, next) {
// 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
// If the file doesn't exist, request to create one with the file media type as contentType
({ path, contentType } = await ldp.resourceMapper.mapUrlToFile(
{ url: req, createIfNotExists: true, contentType: DEFAULT_FOR_NEW_CONTENT_TYPE }))
{ url: req, createIfNotExists: true, contentType: contentTypeForNew(req) }))
// check if a folder with same name exists
await ldp.checkItemName(req)
resourceExists = false
Expand Down Expand Up @@ -93,13 +109,14 @@ function readGraph (resource) {
// If the file does not exist, assume empty contents
// (it will be created after a successful patch)
if (err.code === 'ENOENT') {
fileContents = ''
fileContents = contentForNew(resource.contentType)
// Fail on all other errors
} else {
return reject(error(500, `Original file read error: ${err}`))
}
}
debug('PATCH -- Read target file (%d bytes)', fileContents.length)
fileContents = resource.contentType.includes('json') ? JSON.parse(fileContents) : fileContents
resolve(fileContents)
})
)
Expand Down Expand Up @@ -177,23 +194,34 @@ function writeGraph (graph, resource, root, serverUri) {
debug('PATCH -- Writing patched file')
return new Promise((resolve, reject) => {
const resourceSym = graph.sym(resource.url)
const serialized = $rdf.serialize(resourceSym, graph, resource.url, resource.contentType)

// First check if we are above quota
overQuota(root, serverUri).then((isOverQuota) => {
if (isOverQuota) {
return reject(error(413,
'User has exceeded their storage quota'))
}

fs.writeFile(resource.path, serialized, { encoding: 'utf8' }, function (err) {
if (err) {
return reject(error(500, `Failed to write file after patch: ${err}`))
function doWrite (serialized) {
// First check if we are above quota
overQuota(root, serverUri).then((isOverQuota) => {
if (isOverQuota) {
return reject(error(413,
'User has exceeded their storage quota'))
}
debug('PATCH -- applied successfully')
resolve('Patch applied successfully.\n')

fs.writeFile(resource.path, serialized, { encoding: 'utf8' }, function (err) {
if (err) {
return reject(error(500, `Failed to write file after patch: ${err}`))
}
debug('PATCH -- applied successfully')
resolve('Patch applied successfully.\n')
})
}).catch(() => reject(error(500, 'Error finding user quota')))
}

if (resource.contentType === 'application/ld+json') {
$rdf.serialize(resourceSym, graph, resource.url, resource.contentType, function (err, result) {
if (err) return reject(error(500, `Failed to serialize after patch: ${err}`))
doWrite(result)
})
}).catch(() => reject(error(500, 'Error finding user quota')))
} else {
const serialized = $rdf.serialize(resourceSym, graph, resource.url, resource.contentType)
doWrite(serialized)
}
})
}

Expand Down
31 changes: 27 additions & 4 deletions lib/resource-mapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,7 @@ class ResourceMapper {

// Determine the URL by chopping off everything after the dollar sign
const pathname = this._removeDollarExtension(path)
const url = `${this.resolveUrl(hostname)}${
pathname.split('/').map((component) => encodeURIComponent(component)).join('/')
}`
const url = `${this.resolveUrl(hostname)}${this._encodePath(pathname)}`
return { url, contentType: this._getContentTypeFromExtension(path) }
}

Expand All @@ -95,7 +93,7 @@ class ResourceMapper {
contentType = contentType ? contentType.replace(/\s*;.*/, '') : ''
// Parse the URL and find the base file path
const { pathname, hostname } = this._parseUrl(url)
const filePath = this.resolveFilePath(hostname, decodeURIComponent(pathname))
const filePath = this.resolveFilePath(hostname, this._decodePath(pathname))
if (filePath.indexOf('/..') >= 0) {
throw new Error('Disallowed /.. segment in URL')
}
Expand Down Expand Up @@ -149,6 +147,31 @@ class ResourceMapper {
return { path, contentType: contentType || this._defaultContentType }
}

// encode/decode path except slash (/), %encodedSlash (%2F|%2f), or ntimes%encodedSlash (%2525...2F|%2525...2f)
// see https://github.com/solid/node-solid-server/issues/1666
_exceptSlash () { return /(\/|%(?:25)*(?:2f))/gi }

_encodePath (pathname) {
return pathname.split(this._exceptSlash())
.map((el, i) => i % 2 === 0 ? encodeURIComponent(el) : el)
.join('')
/* pathArray.forEach((el, i) => {
if (i % 2 === 0) pathArray[i] = encodeURIComponent(el)
}) */
// return pathArray.join('')
}

_decodePath (pathname) {
return pathname.split(this._exceptSlash())
.map((el, i) => i % 2 === 0 ? decodeURIComponent(el) : el)
.join('')
/* const pathArray = pathname.split(this._exceptSlash())
pathArray.forEach((el, i) => {
if (i % 2 === 0) pathArray[i] = decodeURIComponent(el)
})
return pathArray.join('') */
}

// Parses a URL into hostname and pathname
_parseUrl (url) {
// URL specified as string
Expand Down
Loading