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
30 changes: 14 additions & 16 deletions lib/resource-mapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class ResourceMapper {
rootPath,
includeHost = false,
defaultContentType = 'application/octet-stream',
indexFilename = 'index',
indexFilename = 'index.html',
overrideTypes = { acl: 'text/turtle', meta: 'text/turtle' },
fileSuffixes = ['.acl', '.meta']
}) {
Expand All @@ -23,8 +23,9 @@ class ResourceMapper {
this._includeHost = includeHost
this._readdir = readdir
this._defaultContentType = defaultContentType
this._indexFilename = indexFilename
this._types = { ...types, ...overrideTypes }
this._indexFilename = indexFilename
this._indexContentType = this._getContentTypeByExtension(indexFilename)
this._isControlFile = new RegExp(`(?:${fileSuffixes.map(fs => fs.replace('.', '\\.')).join('|')})$`)

// If the host needs to be replaced on every call, pre-split the root URL
Expand All @@ -41,15 +42,17 @@ class ResourceMapper {
}

// Maps the request for a given resource and representation format to a server file
// When the URL ends with a '/', then files with the prefix 'index.' will be matched,
// such as 'index.html' and 'index.ttl', unless searchIndex is false.
// When searchIndex is true and the URL ends with a '/', indexFilename will be matched.
async mapUrlToFile ({ url, contentType, createIfNotExists, searchIndex = true }) {
let fullPath = this.getFullPath(url)
let isIndex = searchIndex && fullPath.endsWith('/')
let path

// Append index filename if the URL ends with a '/'
if (isIndex) {
if (createIfNotExists && contentType !== this._indexContentType) {
throw new Error(`Index file needs to have ${this._indexContentType} as content type`)
}
fullPath += this._indexFilename
}

Expand All @@ -58,12 +61,7 @@ class ResourceMapper {
path = fullPath
// If the extension is not correct for the content type, append the correct extension
if (searchIndex && this._getContentTypeByExtension(path) !== contentType) {
// Append a '$', unless we map for the index
// Because the index must _always_ have a proper extension when it is being created
if (!isIndex) {
path += '$'
}
path += contentType in extensions ? `.${extensions[contentType][0]}` : '.unknown'
path += `$${contentType in extensions ? `.${extensions[contentType][0]}` : '.unknown'}`
}
// Determine the path of an existing file
} else {
Expand Down Expand Up @@ -92,13 +90,13 @@ class ResourceMapper {

async _getMatchingFile (folder, filename, isIndex) {
const files = await this._readdir(folder)
const hasSameName = (f) => {
return this._removeDollarExtension(f) === filename
}
const isIndexFile = (f) => {
return isIndex && f.startsWith(this._indexFilename + '.') && !this._isControlFile.test(f)
// Search for files with the same name (disregarding a dollar extension)
if (!isIndex) {
return files.find(f => this._removeDollarExtension(f) === filename)
// Check if the index file exists
} else if (files.includes(this._indexFilename)) {
return this._indexFilename
}
return files.find(f => hasSameName(f) || isIndexFile(f))
}

async getRepresentationUrlForResource (resourceUrl) {
Expand Down
41 changes: 31 additions & 10 deletions test/unit/resource-mapper-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ describe('ResourceMapper', () => {
contentType: 'text/n3'
})

itMapsUrl(mapper, 'a URL ending with a slash to an index file when index.html is available',
itMapsUrl(mapper, 'a URL ending with a slash when index.html is available',
{
url: 'http://localhost/space/'
},
Expand All @@ -250,7 +250,19 @@ describe('ResourceMapper', () => {
contentType: 'text/html'
})

itMapsUrl(mapper, 'a URL ending with a slash to an index file when index$.html is available',
itMapsUrl(mapper, 'a URL ending with a slash when index.ttl is available',
{
url: 'http://localhost/space/'
},
[
`${rootPath}space/index.ttl`
],
{
path: `${rootPath}space/`,
contentType: 'application/octet-stream'
})

itMapsUrl(mapper, 'a URL ending with a slash when index$.html is available',
{
url: 'http://localhost/space/'
},
Expand All @@ -259,8 +271,20 @@ describe('ResourceMapper', () => {
`${rootPath}space/index$.ttl`
],
{
path: `${rootPath}space/index$.html`,
contentType: 'text/html'
path: `${rootPath}space/`,
contentType: 'application/octet-stream'
})

itMapsUrl(mapper, 'a URL ending with a slash when index$.ttl is available',
{
url: 'http://localhost/space/'
},
[
`${rootPath}space/index$.ttl`
],
{
path: `${rootPath}space/`,
contentType: 'application/octet-stream'
})

itMapsUrl(mapper, 'a URL ending with a slash to a folder when index.html is available but index is skipped',
Expand Down Expand Up @@ -298,7 +322,7 @@ describe('ResourceMapper', () => {
contentType: 'application/octet-stream'
})

itMapsUrl(mapper, 'a URL ending with a slash to an index file for text/html when index.html not is available',
itMapsUrl(mapper, 'a URL ending with a slash for text/html when index.html is not available',
{
url: 'http://localhost/space/',
contentType: 'text/html',
Expand Down Expand Up @@ -335,16 +359,13 @@ describe('ResourceMapper', () => {
contentType: 'application/octet-stream'
})

itMapsUrl(mapper, 'a URL ending with a slash to an index file for text/turtle when index.ttl not is available',
itMapsUrl(mapper, 'a URL ending with a slash for text/turtle',
{
url: 'http://localhost/space/',
contentType: 'text/turtle',
createIfNotExists: true
},
{
path: `${rootPath}space/index.ttl`,
contentType: 'text/turtle'
})
new Error('Index file needs to have text/html as content type'))

// Security cases

Expand Down