Skip to content

Commit c7a95b3

Browse files
committed
Add file to URL mapping.
1 parent 0e9a0e6 commit c7a95b3

File tree

2 files changed

+80
-5
lines changed

2 files changed

+80
-5
lines changed

lib/handlers/resource-mapper.js

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,21 @@
11
const URL = require('url')
2-
const { extensions } = require('mime-types')
2+
const { types, extensions } = require('mime-types')
33

44
// A ResourceMapper maintains the mapping between HTTP URLs and server filenames,
55
// following the principles of the “sweet spot” discussed in
66
// https://www.w3.org/DesignIssues/HTTPFilenameMapping.html
77
class ResourceMapper {
8-
constructor ({ rootPath, multiuser }) {
9-
this._rootPath = rootPath.replace(/\/+$/, '')
8+
constructor ({ rootUrl, rootPath, multiuser }) {
9+
this._rootUrl = removeTrailingSlash(rootUrl)
10+
this._rootPath = removeTrailingSlash(rootPath)
1011
this._multiuser = multiuser
1112
}
1213

1314
// Maps the request for a given resource and representation format to a server file
1415
async mapUrlToFile ({ url, contentTypes }) {
1516
// Split the URL into components
1617
const { pathname } = typeof url === 'string' ? URL.parse(url) : url
17-
const urlExtension = /(\.[^/.]+)?$/.exec(pathname)[0]
18+
const urlExtension = getExtension(pathname)
1819
const urlPath = pathname.substr(0, pathname.length - urlExtension.length)
1920

2021
// Sanity checks
@@ -30,6 +31,27 @@ class ResourceMapper {
3031

3132
return { path }
3233
}
34+
35+
// Maps a given server file to a URL
36+
async mapFileToUrl ({ path }) {
37+
// Determine the URL by shopping off everything after the dollar sign
38+
const pathname = path.substring(this._rootPath.length).replace(/\$.*/, '')
39+
const url = `${this._rootUrl}${pathname}`
40+
41+
// Determine the content type
42+
const extension = getExtension(path)
43+
const contentType = types[extension.substr(1)] || 'application/octet-stream'
44+
45+
return { url, contentType }
46+
}
47+
}
48+
49+
function removeTrailingSlash (path) {
50+
return path ? path.replace(/\/+$/, '') : ''
51+
}
52+
53+
function getExtension (path) {
54+
return /(\.[^/.]+)?$/.exec(path)[0]
3355
}
3456

3557
module.exports = ResourceMapper

test/unit/resource-mapper-test.js

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@ const { expect } = chai
44
chai.use(require('chai-as-promised'))
55

66
const itMapsUrl = asserter(mapsUrl)
7+
const itMapsFile = asserter(mapsFile)
78

89
describe('ResourceMapper', () => {
910
describe('A ResourceMapper instance for a single-user setup', () => {
11+
const rootUrl = 'http://localhost/'
1012
const rootPath = '/var/www/folder/'
11-
const mapper = new ResourceMapper({ rootPath })
13+
const mapper = new ResourceMapper({ rootUrl, rootPath })
1214

1315
// PUT base cases from https://www.w3.org/DesignIssues/HTTPFilenameMapping.html
1416

@@ -123,6 +125,50 @@ describe('ResourceMapper', () => {
123125
url: 'http://localhost/space/../bar'
124126
},
125127
new Error('Disallowed /.. segment in URL'))
128+
129+
// File to URL mapping
130+
131+
itMapsFile(mapper, 'an HTML file',
132+
{ path: `${rootPath}space/foo.html` },
133+
{
134+
url: 'http://localhost/space/foo.html',
135+
contentType: 'text/html'
136+
})
137+
138+
itMapsFile(mapper, 'a Turtle file',
139+
{ path: `${rootPath}space/foo.ttl` },
140+
{
141+
url: 'http://localhost/space/foo.ttl',
142+
contentType: 'text/turtle'
143+
})
144+
145+
itMapsFile(mapper, 'an unknown file type',
146+
{ path: `${rootPath}space/foo.bar` },
147+
{
148+
url: 'http://localhost/space/foo.bar',
149+
contentType: 'application/octet-stream'
150+
})
151+
152+
itMapsFile(mapper, 'an extensionless HTML file',
153+
{ path: `${rootPath}space/foo$.html` },
154+
{
155+
url: 'http://localhost/space/foo',
156+
contentType: 'text/html'
157+
})
158+
159+
itMapsFile(mapper, 'an extensionless Turtle file',
160+
{ path: `${rootPath}space/foo$.ttl` },
161+
{
162+
url: 'http://localhost/space/foo',
163+
contentType: 'text/turtle'
164+
})
165+
166+
itMapsFile(mapper, 'an extensionless unknown file type',
167+
{ path: `${rootPath}space/foo$.bar` },
168+
{
169+
url: 'http://localhost/space/foo',
170+
contentType: 'application/octet-stream'
171+
})
126172
})
127173
})
128174

@@ -154,3 +200,10 @@ function mapsUrl (it, mapper, label, options, files, expected) {
154200
})
155201
}
156202
}
203+
204+
function mapsFile (it, mapper, label, options, expected) {
205+
it(`maps ${label}`, async () => {
206+
const actual = await mapper.mapFileToUrl(options)
207+
expect(actual).to.deep.equal(expected)
208+
})
209+
}

0 commit comments

Comments
 (0)