diff --git a/lib/resource-mapper.js b/lib/resource-mapper.js index 653c1c76a..2abeeb1dd 100644 --- a/lib/resource-mapper.js +++ b/lib/resource-mapper.js @@ -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'] }) { @@ -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 @@ -41,8 +42,7 @@ 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('/') @@ -50,6 +50,9 @@ class ResourceMapper { // 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 } @@ -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 { @@ -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) { diff --git a/test/unit/resource-mapper-test.js b/test/unit/resource-mapper-test.js index d2aa7cb4d..bc669fdc7 100644 --- a/test/unit/resource-mapper-test.js +++ b/test/unit/resource-mapper-test.js @@ -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/' }, @@ -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/' }, @@ -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', @@ -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', @@ -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