Skip to content

Commit 665b84d

Browse files
committed
Make ResourceMapper interpret URLs ending with '/' as index pages
Essentially, URLs ending with a '/' will internally be translated to paths such as 'index.html', 'index.ttl', ... depending on the content type
1 parent 825472e commit 665b84d

File tree

2 files changed

+67
-5
lines changed

2 files changed

+67
-5
lines changed

lib/resource-mapper.js

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,13 @@ const readdir = promisify(fs.readdir)
88
// following the principles of the “sweet spot” discussed in
99
// https://www.w3.org/DesignIssues/HTTPFilenameMapping.html
1010
class ResourceMapper {
11-
constructor ({ rootUrl, rootPath, includeHost, defaultContentType }) {
11+
constructor ({ rootUrl, rootPath, includeHost, defaultContentType, indexName = 'index' }) {
1212
this._rootUrl = this._removeTrailingSlash(rootUrl)
1313
this._rootPath = this._removeTrailingSlash(rootPath)
1414
this._includeHost = includeHost
1515
this._readdir = readdir
1616
this._defaultContentType = defaultContentType
17+
this._indexName = indexName
1718

1819
// If the host needs to be replaced on every call, pre-split the root URL
1920
if (includeHost) {
@@ -29,16 +30,28 @@ class ResourceMapper {
2930
}
3031

3132
// Maps the request for a given resource and representation format to a server file
33+
// When the URL ends with a '/', then files with the prefix 'index.' will be matched,
34+
// such as 'index.html' and 'index.ttl'.
3235
async mapUrlToFile ({ url, contentType, createIfNotExists }) {
33-
const fullPath = this._getFullPath(url)
36+
let fullPath = this._getFullPath(url)
37+
let isIndex = fullPath.endsWith('/')
3438
let path
3539

40+
// Append index filename if the URL ends with a '/'
41+
if (isIndex) {
42+
fullPath += this._indexName
43+
}
44+
3645
// Create the path for a new file
3746
if (createIfNotExists) {
3847
path = fullPath
3948
// If the extension is not correct for the content type, append the correct extension
4049
if (this._getContentTypeByExtension(path) !== contentType) {
41-
path += contentType in extensions ? `$.${extensions[contentType][0]}` : '$.unknown'
50+
// Append a '$', unless we map for the index
51+
if (!isIndex) {
52+
path += '$'
53+
}
54+
path += contentType in extensions ? `.${extensions[contentType][0]}` : '.unknown'
4255
}
4356
// Determine the path of an existing file
4457
} else {
@@ -48,7 +61,8 @@ class ResourceMapper {
4861
const files = await this._readdir(folder)
4962

5063
// Find a file with the same name (minus the dollar extension)
51-
const match = files.find(f => this._removeDollarExtension(f) === filename)
64+
const match = files.find(f => this._removeDollarExtension(f) === filename ||
65+
(isIndex && f.startsWith(this._indexName + '.')))
5266
if (!match) {
5367
throw new Error('File not found')
5468
}

test/unit/resource-mapper-test.js

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,54 @@ describe('ResourceMapper', () => {
192192
contentType: 'text/html'
193193
})
194194

195+
itMapsUrl(mapper, 'a URL ending with a slash to an index file when index.html is available',
196+
{
197+
url: 'http://localhost/space/'
198+
},
199+
[
200+
`${rootPath}space/index.html`,
201+
`${rootPath}space/index$.ttl`
202+
],
203+
{
204+
path: `${rootPath}space/index.html`,
205+
contentType: 'text/html'
206+
})
207+
208+
itMapsUrl(mapper, 'a URL ending with a slash to an index file when index$.html is available',
209+
{
210+
url: 'http://localhost/space/'
211+
},
212+
[
213+
`${rootPath}space/index$.html`,
214+
`${rootPath}space/index$.ttl`
215+
],
216+
{
217+
path: `${rootPath}space/index$.html`,
218+
contentType: 'text/html'
219+
})
220+
221+
itMapsUrl(mapper, 'a URL ending with a slash to an index file for text/html when index.html not is available',
222+
{
223+
url: 'http://localhost/space/',
224+
contentType: 'text/html',
225+
createIfNotExists: true
226+
},
227+
{
228+
path: `${rootPath}space/index.html`,
229+
contentType: 'text/html'
230+
})
231+
232+
itMapsUrl(mapper, 'a URL ending with a slash to an index file for text/turtle when index.ttl not is available',
233+
{
234+
url: 'http://localhost/space/',
235+
contentType: 'text/turtle',
236+
createIfNotExists: true
237+
},
238+
{
239+
path: `${rootPath}space/index.ttl`,
240+
contentType: 'text/turtle'
241+
})
242+
195243
// Security cases
196244

197245
itMapsUrl(mapper, 'a URL with an unknown content type',
@@ -448,7 +496,7 @@ function mapsUrl (it, mapper, label, options, files, expected) {
448496
// Mock filesystem
449497
function mockReaddir () {
450498
mapper._readdir = async (path) => {
451-
expect(path).to.equal(`${rootPath}space/`)
499+
expect(path.startsWith(`${rootPath}space/`)).to.equal(true)
452500
return files.map(f => f.replace(/.*\//, ''))
453501
}
454502
}

0 commit comments

Comments
 (0)