Skip to content

Commit 2e4ce40

Browse files
authored
Merge pull request #1695 from nodeSolidServer/patchJsonld
PATCH jsonld, rdf+xml and n3
2 parents 73e73c8 + c791802 commit 2e4ce40

File tree

6 files changed

+2045
-405
lines changed

6 files changed

+2045
-405
lines changed

lib/handlers/patch.js

Lines changed: 45 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,24 @@ const PATCH_PARSERS = {
1818
'text/n3': require('./patch/n3-patch-parser.js')
1919
}
2020

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

24+
function contentTypeForNew (req) {
25+
let contentTypeForNew = DEFAULT_FOR_NEW_CONTENT_TYPE
26+
if (req.path.endsWith('.jsonld')) contentTypeForNew = 'application/ld+json'
27+
else if (req.path.endsWith('.n3')) contentTypeForNew = 'text/n3'
28+
else if (req.path.endsWith('.rdf')) contentTypeForNew = 'application/rdf+xml'
29+
return contentTypeForNew
30+
}
31+
32+
function contentForNew (contentType) {
33+
let contentForNew = ''
34+
if (contentType.includes('ld+json')) contentForNew = JSON.stringify('{}')
35+
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>'
36+
return contentForNew
37+
}
38+
2339
// Handles a PATCH request
2440
async function patchHandler (req, res, next) {
2541
debug(`PATCH -- ${req.originalUrl}`)
@@ -33,9 +49,9 @@ async function patchHandler (req, res, next) {
3349
// First check if the file already exists
3450
({ path, contentType } = await ldp.resourceMapper.mapUrlToFile({ url: req }))
3551
} catch (err) {
36-
// If the file doesn't exist, request one to be created with the default content type
52+
// If the file doesn't exist, request to create one with the file media type as contentType
3753
({ path, contentType } = await ldp.resourceMapper.mapUrlToFile(
38-
{ url: req, createIfNotExists: true, contentType: DEFAULT_FOR_NEW_CONTENT_TYPE }))
54+
{ url: req, createIfNotExists: true, contentType: contentTypeForNew(req) }))
3955
// check if a folder with same name exists
4056
await ldp.checkItemName(req)
4157
resourceExists = false
@@ -93,13 +109,14 @@ function readGraph (resource) {
93109
// If the file does not exist, assume empty contents
94110
// (it will be created after a successful patch)
95111
if (err.code === 'ENOENT') {
96-
fileContents = ''
112+
fileContents = contentForNew(resource.contentType)
97113
// Fail on all other errors
98114
} else {
99115
return reject(error(500, `Original file read error: ${err}`))
100116
}
101117
}
102118
debug('PATCH -- Read target file (%d bytes)', fileContents.length)
119+
fileContents = resource.contentType.includes('json') ? JSON.parse(fileContents) : fileContents
103120
resolve(fileContents)
104121
})
105122
)
@@ -177,23 +194,34 @@ function writeGraph (graph, resource, root, serverUri) {
177194
debug('PATCH -- Writing patched file')
178195
return new Promise((resolve, reject) => {
179196
const resourceSym = graph.sym(resource.url)
180-
const serialized = $rdf.serialize(resourceSym, graph, resource.url, resource.contentType)
181-
182-
// First check if we are above quota
183-
overQuota(root, serverUri).then((isOverQuota) => {
184-
if (isOverQuota) {
185-
return reject(error(413,
186-
'User has exceeded their storage quota'))
187-
}
188197

189-
fs.writeFile(resource.path, serialized, { encoding: 'utf8' }, function (err) {
190-
if (err) {
191-
return reject(error(500, `Failed to write file after patch: ${err}`))
198+
function doWrite (serialized) {
199+
// First check if we are above quota
200+
overQuota(root, serverUri).then((isOverQuota) => {
201+
if (isOverQuota) {
202+
return reject(error(413,
203+
'User has exceeded their storage quota'))
192204
}
193-
debug('PATCH -- applied successfully')
194-
resolve('Patch applied successfully.\n')
205+
206+
fs.writeFile(resource.path, serialized, { encoding: 'utf8' }, function (err) {
207+
if (err) {
208+
return reject(error(500, `Failed to write file after patch: ${err}`))
209+
}
210+
debug('PATCH -- applied successfully')
211+
resolve('Patch applied successfully.\n')
212+
})
213+
}).catch(() => reject(error(500, 'Error finding user quota')))
214+
}
215+
216+
if (resource.contentType === 'application/ld+json') {
217+
$rdf.serialize(resourceSym, graph, resource.url, resource.contentType, function (err, result) {
218+
if (err) return reject(error(500, `Failed to serialize after patch: ${err}`))
219+
doWrite(result)
195220
})
196-
}).catch(() => reject(error(500, 'Error finding user quota')))
221+
} else {
222+
const serialized = $rdf.serialize(resourceSym, graph, resource.url, resource.contentType)
223+
doWrite(serialized)
224+
}
197225
})
198226
}
199227

lib/resource-mapper.js

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,7 @@ class ResourceMapper {
8282

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

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

150+
// encode/decode path except slash (/), %encodedSlash (%2F|%2f), or ntimes%encodedSlash (%2525...2F|%2525...2f)
151+
// see https://github.com/solid/node-solid-server/issues/1666
152+
_exceptSlash () { return /(\/|%(?:25)*(?:2f))/gi }
153+
154+
_encodePath (pathname) {
155+
return pathname.split(this._exceptSlash())
156+
.map((el, i) => i % 2 === 0 ? encodeURIComponent(el) : el)
157+
.join('')
158+
/* pathArray.forEach((el, i) => {
159+
if (i % 2 === 0) pathArray[i] = encodeURIComponent(el)
160+
}) */
161+
// return pathArray.join('')
162+
}
163+
164+
_decodePath (pathname) {
165+
return pathname.split(this._exceptSlash())
166+
.map((el, i) => i % 2 === 0 ? decodeURIComponent(el) : el)
167+
.join('')
168+
/* const pathArray = pathname.split(this._exceptSlash())
169+
pathArray.forEach((el, i) => {
170+
if (i % 2 === 0) pathArray[i] = decodeURIComponent(el)
171+
})
172+
return pathArray.join('') */
173+
}
174+
152175
// Parses a URL into hostname and pathname
153176
_parseUrl (url) {
154177
// URL specified as string

0 commit comments

Comments
 (0)