Skip to content

Commit

Permalink
Merge pull request #33 from RyanJ93/dev
Browse files Browse the repository at this point in the history
Fixed a bug that was causing hidden files to not be served despite th…
  • Loading branch information
Enrico authored Jun 4, 2020
2 parents 263bf7a + 4d7d530 commit 058a1f3
Show file tree
Hide file tree
Showing 14 changed files with 93 additions and 45 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ All notable changes to this project will be documented in this file.
### Changed
- Fixed a bug that was causing CSRF to not working in view routes.
- Fixed a bug in response helpers.
- Fixed a bug that was causing hidden files to not be served despite the "serveHiddenFiles" was turned on.

## [0.1.2]
### Added
Expand Down
2 changes: 1 addition & 1 deletion lib/Routing/ResolvedRoute.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class ResolvedRoute {
* @param {?ResolvedRouteOptions} options An object containing all the parameters associated to this route.
*/
constructor(route, options = null){
if ( !( route instanceof BaseRoute ) && !( route instanceof ParameterizedRoute ) ){
if ( !( route instanceof BaseRoute ) ){
throw new InvalidArgumentException('Invalid route.', 1);
}
if ( options === null || typeof options !== 'object' ){
Expand Down
2 changes: 1 addition & 1 deletion lib/Routing/RouteResolver.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ class RouteResolver {
* @returns {?RouteResolutionResult} An object representing the URL found and its properties or null if no route matching current request URL has been found.
*/
resolve(request){
let resolvedRoute = null;
let resolvedRoute;
switch ( this._algorithm ){
case 'linear':{
resolvedRoute = LinearResolverAlgorithm.resolve(request, this._routers);
Expand Down
2 changes: 1 addition & 1 deletion lib/Routing/Router.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class Router extends mixin(Middlewares, ParamMiddlewares, Permissions, Policies,
this._prefix = '';

/**
* @type {RouteStorage} _routeStorage
* @type {RouteStorage} _routeStorage An instance of the "RouteStorage" class containing all the routes that have been defined for this router.
*
* @protected
*/
Expand Down
11 changes: 7 additions & 4 deletions lib/Routing/resolverAlgorithms/LinearResolverAlgorithm.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,10 @@ class LinearResolverAlgorithm extends ResolverAlgorithm {
const pathFound = routerData[length].relativeURL.substr(base.length);
// Add this result to the list of all the eligible options indexing it by language.
variants.set(language, {
route: routeFound,
route: route,
router: router,
path: pathFound
path: pathFound,
matches: null
});
}
}
Expand Down Expand Up @@ -77,7 +78,8 @@ class LinearResolverAlgorithm extends ResolverAlgorithm {
if ( routerData[i].relativeURL === path ){
variants.set(language, {
route: route,
router: routerData[i].router
router: routerData[i].router,
matches: null
});
}
continue;
Expand All @@ -96,7 +98,8 @@ class LinearResolverAlgorithm extends ResolverAlgorithm {
if ( routerData[i].relativeURL === path ){
variants.set(language, {
route: route,
router: routerData[i].router
router: routerData[i].router,
matches: null
});
}
continue;
Expand Down
3 changes: 2 additions & 1 deletion lib/Routing/resolverAlgorithms/SubsetResolverAlgorithm.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,9 @@ class SubsetResolverAlgorithm extends ResolverAlgorithm {
const routes = router.getRouteStorage().getResourceRoutesFromIndex();
if ( typeof routes !== 'undefined' ){
for ( const [path, versions] of routes ){
const prefixPath = path === '' || path === '/' ? '/' : path + '/';
// If the request URL begins with this route path, then it references an assets mapped by this route.
if ( relativeURL === path || relativeURL.indexOf(path + '/') === 0 ){
if ( relativeURL === path || relativeURL.indexOf(prefixPath) === 0 ){
// Check if more variants of this route ara available based on their languages and which one should be used.
const route = SubsetResolverAlgorithm._pickRouteVersionByLanguage(request, versions);
if ( route !== null ){
Expand Down
11 changes: 5 additions & 6 deletions lib/Server/processors/RequestProcessor.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
'use strict';

// Including native modules.
const path = require('path');

// Including Lala's modules.
const Processor = require('./Processor');
const {
Expand Down Expand Up @@ -177,12 +180,8 @@ class RequestProcessor extends Processor {
if ( request.URLLength > this._maxURLLength ){
throw new URITooLongHTTPException('Client provided URL exceeded the allowed size.', 1);
}
// Cleaning up the request path removing components such as "../" and "./".
request.url = request.url.replace(/\/?\.\.\/|\/\.\.\/?|\/?\.\/|\/\.\/?/g, '/');
// Removing multiple slashes from the request URL.
if ( request.url.indexOf('//') !== -1 ){
request.url = request.url.replace(/\/{2,}/g, '/');
}
// Cleaning up the request path removing components such as "../" and "./" and removing multiple slashes.
request.url = path.normalize(request.url.replace(/\/\.\.\/|\/\.\.|\/\.\//g, '/'));
// Add some additional properties.
response.rawOutput = response.charset = response.contentType = null;
this._addRequestProperties(request);
Expand Down
57 changes: 34 additions & 23 deletions lib/Server/processors/RouteProcessor.js
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,40 @@ class RouteProcessor extends Processor {
};
}

/**
* Looks up the route who matches the request path.
*
* @param {module:http.IncomingMessage} request An instance of the built-in class "IncomingMessage" containing all the connection properties.
* @param {RouteResolutionStats} stats An object containing the information about times taken in route resolution, lookup time will be added to this object.
*
* @returns {Promise<?ResolvedRoute>} An instance of the "ResolvedRoute" class containing the route found and related information or null if no matching route were found.
*
* @async
* @protected
*/
async _resolveRoute(request, stats){
// throw new NotFoundHTTPException('No route matching given URL were found.', 1);
let resolvedRoute = null;
// Generate the key to use in resolution result caching, it must take care of server ID, request path and all the user languages being considered.
const cacheKey = RouteProcessor._generateCacheKey(request);
// Check if a route matching this URL has been cached and retrieve it.
let resolvedRouteDraft = cacheKey === null ? null : await this._resolveByCache(request, cacheKey, stats);
const fromCache = resolvedRouteDraft !== null;
if ( !fromCache ){
// No cached version for this route is found or cache is disabled, process it from scratch.
resolvedRouteDraft = this._resolveByProcessing(request, stats);
if ( resolvedRouteDraft !== null ){
// Save the route found into the cache.
resolvedRouteDraft.options.cached = cacheKey === null ? false : this._saveToCache(cacheKey, resolvedRouteDraft);
resolvedRoute = new ResolvedRoute(resolvedRouteDraft.route, resolvedRouteDraft.options);
}
}else{
resolvedRouteDraft.options.fromCache = fromCache;
resolvedRoute = new ResolvedRoute(resolvedRouteDraft.route, resolvedRouteDraft.options);
}
return resolvedRoute;
}

/**
* The class constructor.
*
Expand Down Expand Up @@ -436,29 +470,6 @@ class RouteProcessor extends Processor {
throw new NotImplementedYetException('This method has not been implemented yet.');
}

async _resolveRoute(request, stats){
// throw new NotFoundHTTPException('No route matching given URL were found.', 1);
let resolvedRoute = null;
// Generate the key to use in resolution result caching, it must take care of server ID, request path and all the user languages being considered.
const cacheKey = RouteProcessor._generateCacheKey(request);
// Check if a route matching this URL has been cached and retrieve it.
let resolvedRouteDraft = cacheKey === null ? null : await this._resolveByCache(request, cacheKey, stats);
const fromCache = resolvedRouteDraft !== null;
if ( !fromCache ){
// No cached version for this route is found or cache is disabled, process it from scratch.
resolvedRouteDraft = this._resolveByProcessing(request, stats);
if ( resolvedRouteDraft !== null ){
// Save the route found into the cache.
resolvedRouteDraft.options.cached = cacheKey === null ? false : this._saveToCache(cacheKey, resolvedRouteDraft);
resolvedRoute = new ResolvedRoute(resolvedRouteDraft.route, resolvedRouteDraft.options);
}
}else{
resolvedRouteDraft.options.fromCache = fromCache;
resolvedRoute = new ResolvedRoute(resolvedRouteDraft.route, resolvedRouteDraft.options);
}
return resolvedRoute;
}

/**
* Processes request URL in order to find out the route that corresponds the most.
*
Expand Down
16 changes: 15 additions & 1 deletion lib/Server/processors/factories/RouteProcessorFactory.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,14 +156,28 @@ class RouteProcessorFactory extends ProcessorFactory {
return this._properties.defaultLanguage;
}

/**
* Sets the algorithm to use when resolving routes, this method is chainable.
*
* @param {string} algorithm A string containing the algorithm name, currently only "linear" and "subset" (default) are supported.
*
* @returns {RouteProcessorFactory}
*
* @throws {InvalidArgumentException} If an invalid routing algorithm name is given.
*/
setRouteResolverAlgorithm(algorithm){
if ( !RouteResolver.isSupportedAlgorithm(algorithm) ){
//
throw new InvalidArgumentException('Invalid routing algorithm name.', 1);
}
this._properties.routeResolverAlgorithm = algorithm;
return this;
}

/**
* Returns the algorithm to use when resolving routes.
*
* @returns {string} A string containing the algorithm name.
*/
getRouteResolverAlgorithm(){
return this._properties.routeResolverAlgorithm;
}
Expand Down
Binary file removed test/public/assets/icon.png
Binary file not shown.
File renamed without changes.
Empty file.
24 changes: 17 additions & 7 deletions test/routing/routing.js
Original file line number Diff line number Diff line change
Expand Up @@ -311,16 +311,14 @@ describe('Testing the routing engine.', () => {
assert.deepEqual(result, true);
});

it('Defining a resource route.', () => {
router.resource('/assets', './test/public/assets');
});

it('Accessing to a hidden file through a resource route.', async () => {
router.resource('/assets/other', './test/public/other_assets');
it('Defining a resource route.', async () => {
const route = router.resource('/assets', './test/resource/listing');
const resolvedRoute = await factory.craft().process({
url: '/assets/other/.hidden_file',
url: '/assets/1.txt',
method: 'GET'
}, {});
const result = resolvedRoute !== null && resolvedRoute.getRoute().getID() === route.getID();
assert.deepEqual(result, true);
});

it('Creating a route and check the generated tag.', () => {
Expand Down Expand Up @@ -506,4 +504,16 @@ describe('Testing the routing engine.', () => {
const result = resolvedRoute instanceof lala.ResolvedRoute && resolvedRoute.getRoute().getPath() === '/' && resolvedRoute.getRoute().getMethod() === 'GET';
assert.deepEqual(result, true);
});

it('Defining and triggering a resource route defined on the website route.', async () => {
const tmpRouter = new lala.Router();
const route = tmpRouter.resource('/', './test/resources/listing');
const factory = new lala.processors.factories.RouteProcessorFactory();
const resolvedRoute = await factory.setRoutersAsArray([tmpRouter]).craft().process({
url: '/sub_dir/1.txt',
method: 'GET'
}, {});
const result = resolvedRoute !== null && resolvedRoute.getRoute().getID() === route.getID();
assert.deepEqual(result, true);
});
});
9 changes: 9 additions & 0 deletions test/server/HTTPServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -1038,6 +1038,15 @@ describe('Testing HTTP server capabilities.', () => {
assert.deepStrictEqual(response.headers.location, '/test');
});

it('Accessing to a hidden file through a resource route.', async () => {
const route = router.resource('/hidden-assets', './test/resources/listing');
const deniedResponse = await fetchHTTPResponse('http://127.0.0.1:' + port + '/hidden-assets/sub_dir/.hidden_file');
route.setServeHiddenFiles(true);
const allowedResponse = await fetchHTTPResponse('http://127.0.0.1:' + port + '/hidden-assets/sub_dir/.hidden_file');
const result = deniedResponse.statusCode === 403 && allowedResponse.statusCode === 200;
assert.deepStrictEqual(result, true);
});

it('Stopping the server.', async () => {
await server.stop();
let exception = null;
Expand Down

0 comments on commit 058a1f3

Please sign in to comment.