@@ -15,8 +15,7 @@ class ResourceMapper {
1515 includeHost = false ,
1616 defaultContentType = 'application/octet-stream' ,
1717 indexFilename = 'index.html' ,
18- overrideTypes = { acl : 'text/turtle' , meta : 'text/turtle' } ,
19- fileSuffixes = [ '.acl' , '.meta' ]
18+ overrideTypes = { acl : 'text/turtle' , meta : 'text/turtle' }
2019 } ) {
2120 this . _rootUrl = this . _removeTrailingSlash ( rootUrl )
2221 this . _rootPath = this . _removeTrailingSlash ( rootPath )
@@ -26,7 +25,6 @@ class ResourceMapper {
2625 this . _types = { ...types , ...overrideTypes }
2726 this . _indexFilename = indexFilename
2827 this . _indexContentType = this . _getContentTypeByExtension ( indexFilename )
29- this . _isControlFile = new RegExp ( `(?:${ fileSuffixes . map ( fs => fs . replace ( '.' , '\\.' ) ) . join ( '|' ) } )$` )
3028
3129 // If the host needs to be replaced on every call, pre-split the root URL
3230 if ( includeHost ) {
@@ -49,7 +47,7 @@ class ResourceMapper {
4947 : `${ this . _protocol } //${ hostname } ${ this . _port } ${ this . _rootUrl } ${ pathname } `
5048 }
5149
52- // Gets the base file path for the given hostname
50+ // Gets the base file path for the given host name
5351 getBaseFilePath ( hostname ) {
5452 return ! this . _includeHost ? this . _rootPath : `${ this . _rootPath } /${ hostname } `
5553 }
@@ -68,44 +66,55 @@ class ResourceMapper {
6866 }
6967
7068 // Maps the request for a given resource and representation format to a server file
71- // When searchIndex is true and the URL ends with a '/', and contentType includes 'text/html' indexFilename will be matched.
69+ // Will look for an index file if a folder is given and searchIndex is true
7270 async mapUrlToFile ( { url, contentType, createIfNotExists, searchIndex = true } ) {
73- let fullFilePath = this . _getFilePath ( url )
74- let isIndex = searchIndex && fullFilePath . endsWith ( '/' )
75- let path
76-
77- // Append index filename if the URL ends with a '/'
78- if ( isIndex ) {
79- if ( createIfNotExists && contentType !== this . _indexContentType ) {
80- throw new Error ( `Index file needs to have ${ this . _indexContentType } as content type` )
81- }
82- if ( contentType && contentType . includes ( this . _indexContentType ) ) {
83- fullFilePath += this . _indexFilename
84- }
71+ // Parse the URL and find the base file path
72+ const { pathname, hostname } = this . _parseUrl ( url )
73+ let filePath = `${ this . getBaseFilePath ( hostname ) } ${ decodeURIComponent ( pathname ) } `
74+ if ( filePath . indexOf ( '/..' ) >= 0 ) {
75+ throw new Error ( 'Disallowed /.. segment in URL' )
8576 }
77+ let isIndex = searchIndex && filePath . endsWith ( '/' )
78+
8679 // Create the path for a new file
80+ let path
8781 if ( createIfNotExists ) {
88- path = fullFilePath
82+ path = filePath
83+ // Append index filename if needed
84+ if ( isIndex ) {
85+ if ( contentType !== this . _indexContentType ) {
86+ throw new Error ( `Index file needs to have ${ this . _indexContentType } as content type` )
87+ }
88+ path += this . _indexFilename
89+ }
8990 // If the extension is not correct for the content type, append the correct extension
9091 if ( searchIndex && this . _getContentTypeByExtension ( path ) !== contentType ) {
9192 path += `$${ contentType in extensions ? `.${ extensions [ contentType ] [ 0 ] } ` : '.unknown' } `
9293 }
9394 // Determine the path of an existing file
9495 } else {
9596 // Read all files in the corresponding folder
96- const filename = fullFilePath . substr ( fullFilePath . lastIndexOf ( '/' ) + 1 )
97- const folder = fullFilePath . substr ( 0 , fullFilePath . length - filename . length )
97+ const filename = filePath . substr ( filePath . lastIndexOf ( '/' ) + 1 )
98+ const folder = filePath . substr ( 0 , filePath . length - filename . length )
9899
99100 // Find a file with the same name (minus the dollar extension)
100- let match = searchIndex ? await this . _getMatchingFile ( folder , filename , isIndex , contentType ) : ''
101+ let match = ''
102+ if ( searchIndex ) {
103+ const files = await this . _readdir ( folder )
104+ // Search for files with the same name (disregarding a dollar extension)
105+ if ( ! isIndex ) {
106+ match = files . find ( f => this . _removeDollarExtension ( f ) === filename )
107+ // Check if the index file exists
108+ } else if ( files . includes ( this . _indexFilename ) ) {
109+ match = this . _indexFilename
110+ }
111+ }
112+ // Error if no match was found (unless URL ends with '/', then fall back to the folder)
101113 if ( match === undefined ) {
102- // Error if no match was found,
103- // unless the URL ends with a '/',
104- // in that case we fallback to the folder itself.
105114 if ( isIndex ) {
106115 match = ''
107116 } else {
108- throw new HTTPError ( 404 , `File not found: ${ fullFilePath } ` )
117+ throw new HTTPError ( 404 , `Resource not found: ${ pathname } ` )
109118 }
110119 }
111120 path = `${ folder } ${ match } `
@@ -115,28 +124,7 @@ class ResourceMapper {
115124 return { path, contentType : contentType || this . _defaultContentType }
116125 }
117126
118- async _getMatchingFile ( folder , filename , isIndex , contentType ) {
119- const files = await this . _readdir ( folder )
120- // Search for files with the same name (disregarding a dollar extension)
121- if ( ! isIndex ) {
122- return files . find ( f => this . _removeDollarExtension ( f ) === filename )
123- // Check if the index file exists
124- } else if ( files . includes ( this . _indexFilename ) && contentType && contentType . includes ( this . _indexContentType ) ) {
125- return this . _indexFilename
126- }
127- }
128-
129- // Determine the full file path corresponding to a URL
130- _getFilePath ( url ) {
131- const { pathname, hostname } = this . _parseUrl ( url )
132- const fullFilePath = `${ this . getBaseFilePath ( hostname ) } ${ decodeURIComponent ( pathname ) } `
133- if ( fullFilePath . indexOf ( '/..' ) >= 0 ) {
134- throw new Error ( 'Disallowed /.. segment in URL' )
135- }
136- return fullFilePath
137- }
138-
139- // Parses a URL into a hostname and pathname
127+ // Parses a URL into hostname and pathname
140128 _parseUrl ( url ) {
141129 // URL specified as string
142130 if ( typeof url === 'string' ) {
@@ -157,16 +145,14 @@ class ResourceMapper {
157145 return extension && this . _types [ extension [ 1 ] . toLowerCase ( ) ] || this . _defaultContentType
158146 }
159147
160- // Removes a possible trailing slash from a path
148+ // Removes possible trailing slashes from a path
161149 _removeTrailingSlash ( path ) {
162- const lastPos = path . length - 1
163- return lastPos < 0 || path [ lastPos ] !== '/' ? path : path . substr ( 0 , lastPos )
150+ return path . replace ( / \/ + $ / , '' )
164151 }
165152
166- // Removes everything beyond the dollar sign from a path
153+ // Removes dollar extensions from files (index$.html becomes index)
167154 _removeDollarExtension ( path ) {
168- const dollarPos = path . lastIndexOf ( '$' )
169- return dollarPos < 0 ? path : path . substr ( 0 , dollarPos )
155+ return path . replace ( / \$ \. [ ^ $ ] * $ / , '' )
170156 }
171157}
172158
0 commit comments