@@ -4,6 +4,7 @@ const { promisify } = require('util')
44const { types, extensions } = require ( 'mime-types' )
55const readdir = promisify ( fs . readdir )
66const HTTPError = require ( './http-error' )
7+ const pathUtil = require ( 'path' )
78
89/*
910 * A ResourceMapper maintains the mapping between HTTP URLs and server filenames,
@@ -33,7 +34,7 @@ class ResourceMapper {
3334 this . _defaultContentType = defaultContentType
3435 this . _types = { ...types , ...overrideTypes }
3536 this . _indexFilename = indexFilename
36- this . _indexContentType = this . _getContentTypeByExtension ( indexFilename )
37+ this . _indexContentType = this . _getContentTypeFromExtension ( indexFilename )
3738
3839 // If the host needs to be replaced on every call, pre-split the root URL
3940 if ( includeHost ) {
@@ -78,8 +79,10 @@ class ResourceMapper {
7879
7980 // Determine the URL by chopping off everything after the dollar sign
8081 const pathname = this . _removeDollarExtension ( path )
81- const url = `${ this . resolveUrl ( hostname ) } ${ encodeURI ( pathname ) } `
82- return { url, contentType : this . _getContentTypeByExtension ( path ) }
82+ const url = `${ this . resolveUrl ( hostname ) } ${
83+ pathname . split ( pathUtil . sep ) . map ( ( component ) => encodeURIComponent ( component ) ) . join ( '/' )
84+ } `
85+ return { url, contentType : this . _getContentTypeFromExtension ( path ) }
8386 }
8487
8588 // Maps the request for a given resource and representation format to a server file
@@ -94,7 +97,7 @@ class ResourceMapper {
9497 let isFolder = filePath . endsWith ( '/' )
9598 let isIndex = searchIndex && filePath . endsWith ( '/' )
9699
97- // Create the path for a new ressource
100+ // Create the path for a new resource
98101 let path
99102 if ( createIfNotExists ) {
100103 path = filePath
@@ -106,8 +109,8 @@ class ResourceMapper {
106109 path += this . _indexFilename
107110 }
108111 // If the extension is not correct for the content type, append the correct extension
109- if ( ! isFolder && this . _getContentTypeByExtension ( path ) !== contentType ) {
110- path += `$ ${ contentType in extensions ? `. ${ extensions [ contentType ] [ 0 ] } ` : '.unknown' } `
112+ if ( ! isFolder ) {
113+ path = this . _addContentTypeExtension ( path , contentType )
111114 }
112115 // Determine the path of an existing file
113116 } else {
@@ -136,7 +139,7 @@ class ResourceMapper {
136139 }
137140 }
138141 path = `${ folder } ${ match } `
139- contentType = this . _getContentTypeByExtension ( match )
142+ contentType = this . _getContentTypeFromExtension ( match )
140143 }
141144 return { path, contentType : contentType || this . _defaultContentType }
142145 }
@@ -157,11 +160,25 @@ class ResourceMapper {
157160 }
158161
159162 // Gets the expected content type based on the extension of the path
160- _getContentTypeByExtension ( path ) {
163+ _getContentTypeFromExtension ( path ) {
161164 const extension = / \. ( [ ^ / . ] + ) $ / . exec ( path )
162165 return extension && this . _types [ extension [ 1 ] . toLowerCase ( ) ] || this . _defaultContentType
163166 }
164167
168+ // Appends an extension for the specific content type, if needed
169+ _addContentTypeExtension ( path , contentType ) {
170+ // If we would guess the wrong content type from the extension, try appending a better one
171+ const contentTypeFromExtension = this . _getContentTypeFromExtension ( path )
172+ if ( contentTypeFromExtension !== contentType ) {
173+ // Some extensions fit multiple content types, so only switch if there's an improvement
174+ const newExtension = contentType in extensions ? extensions [ contentType ] [ 0 ] : 'unknown'
175+ if ( this . _types [ newExtension ] !== contentTypeFromExtension ) {
176+ path += `$.${ newExtension } `
177+ }
178+ }
179+ return path
180+ }
181+
165182 // Removes possible trailing slashes from a path
166183 _removeTrailingSlash ( path ) {
167184 return path . replace ( / \/ + $ / , '' )
0 commit comments