Skip to content

Commit

Permalink
fix bug which allow hidden dot files to be served by TStaticFilesMidd…
Browse files Browse the repository at this point in the history
…leware
  • Loading branch information
zamronypj committed Mar 24, 2023
1 parent 076846e commit eea4aec
Show file tree
Hide file tree
Showing 2 changed files with 152 additions and 70 deletions.
130 changes: 130 additions & 0 deletions src/Middleware/BuiltIns/BaseStaticFilesMiddlewareImpl.pas
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
{*!
* Fano Web Framework (https://fanoframework.github.io)
*
* @link https://github.com/fanoframework/fano
* @copyright Copyright (c) 2018 - 2022 Zamrony P. Juhara
* @license https://github.com/fanoframework/fano/blob/master/LICENSE (MIT)
*}

unit BaseStaticFilesMiddlewareImpl;

interface

{$MODE OBJFPC}
{$H+}

uses

RequestIntf,
ResponseIntf,
MiddlewareIntf,
RouteArgsReaderIntf,
RequestHandlerIntf,
ReadOnlyKeyValuePairIntf,
InjectableObjectImpl;

type

(*!------------------------------------------------
* base middleware class that serves static files from
* a base directory.
*-------------------------------------------------
* Content type of response will be determined
* using file extension that is stored in fMimeTypes
* if not set then 'application/octet-stream' is assumed
*-------------------------------------------------
* @author Zamrony P. Juhara <[email protected]>
*-------------------------------------------------*)
TBaseStaticFilesMiddleware = class(TInjectableObject, IMiddleware)
protected
fBaseDirectory : string;
fMimeTypes : IReadOnlyKeyValuePair;
function getContentTypeFromFilename(const filename : string) : string;

(*!-------------------------------------------
* clean filepath (if required)
*--------------------------------------------
* This method is provided so that descendant
* have opportunity to clean file path
*--------------------------------------------
* @param filePath original file path
* @return new cleaned file path
*--------------------------------------------*)
function clean(const filePath: string) : string; virtual;
public
constructor create(
const baseDir : string;
const mimeTypes : IReadOnlyKeyValuePair
);

function handleRequest(
const request : IRequest;
const response : IResponse;
const args : IRouteArgsReader;
const nextMdlwr : IRequestHandler
) : IResponse; virtual;
end;

implementation

uses

SysUtils,
FileResponseImpl;

constructor TBaseStaticFilesMiddleware.create(
const baseDir : string;
const mimeTypes : IReadOnlyKeyValuePair
);
begin
fBaseDirectory := baseDir;
fMimeTypes := mimeTypes;
end;

function TBaseStaticFilesMiddleware.getContentTypeFromFilename(
const filename : string
) : string;
var ext : string;
begin
ext := ExtractFileExt(filename);
//remove dot from ext
ext := copy(ext, 2, length(ext)-1);
if (fMimeTypes.has(ext)) then
begin
result := fMimeTypes.getValue(ext);
end else
begin
//set default
result := 'application/octet-stream';
end;
end;

function TBaseStaticFilesMiddleware.clean(const filePath: string) : string;
begin
result := filePath;
end;

function TBaseStaticFilesMiddleware.handleRequest(
const request : IRequest;
const response : IResponse;
const args : IRouteArgsReader;
const nextMdlwr : IRequestHandler
) : IResponse;
var filename : string;
begin
filename := fBaseDirectory + clean(request.uri().getPath());
if fileExists(filename) then
begin
//serve file
result := TFileResponse.create(
response.headers(),
getContentTypeFromFilename(filename),
filename
);
end else
begin
//file not found, just pass to next middleware
result := nextMdlwr.handleRequest(request, response, args);
end;
end;
end.
92 changes: 22 additions & 70 deletions src/Middleware/BuiltIns/StaticFilesMiddlewareImpl.pas
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,7 @@ interface

uses

RequestIntf,
ResponseIntf,
MiddlewareIntf,
RouteArgsReaderIntf,
RequestHandlerIntf,
ReadOnlyKeyValuePairIntf,
InjectableObjectImpl;
BaseStaticFilesMiddlewareImpl;

type

Expand All @@ -35,80 +29,38 @@ interface
*-------------------------------------------------
* @author Zamrony P. Juhara <[email protected]>
*-------------------------------------------------*)
TStaticFilesMiddleware = class(TInjectableObject, IMiddleware)
TStaticFilesMiddleware = class(TBaseStaticFilesMiddleware)
protected
fBaseDirectory : string;
fMimeTypes : IReadOnlyKeyValuePair;
function getContentTypeFromFilename(const filename : string) : string;
(*!-------------------------------------------
* clean filepath avoid serve hidden dot files in unix
*--------------------------------------------
* @param filePath original file path
* @return new cleaned file path
*--------------------------------------------*)
function clean(const filePath: string) : string; override;
public
constructor create(
const baseDir : string;
const mimeTypes : IReadOnlyKeyValuePair
);

function handleRequest(
const request : IRequest;
const response : IResponse;
const args : IRouteArgsReader;
const nextMdlwr : IRequestHandler
) : IResponse; virtual;
end;

implementation

uses

SysUtils,
FileResponseImpl;
SysUtils;

constructor TStaticFilesMiddleware.create(
const baseDir : string;
const mimeTypes : IReadOnlyKeyValuePair
);
begin
fBaseDirectory := baseDir;
fMimeTypes := mimeTypes;
end;

function TStaticFilesMiddleware.getContentTypeFromFilename(
const filename : string
) : string;
var ext : string;
(*!-------------------------------------------
* clean filepath avoid serve hidden dot files in unix
*--------------------------------------------
* @param filePath original file path
* @return new cleaned file path
*--------------------------------------------*)
function TStaticFilesMiddleware.clean(const filePath: string) : string;
begin
ext := ExtractFileExt(filename);
//remove dot from ext
ext := copy(ext, 2, length(ext)-1);
if (fMimeTypes.has(ext)) then
begin
result := fMimeTypes.getValue(ext);
end else
begin
//set default
result := 'application/octet-stream';
end;
// for example if filePath contain '/.htaccess' we replace it so
// filePath become '/htaccess'
result := stringReplace(filePath, '/.', '/', [rfReplaceAll]);
// just paranoia handle .. too
result := stringReplace(result, '..', '', [rfReplaceAll]);
end;

function TStaticFilesMiddleware.handleRequest(
const request : IRequest;
const response : IResponse;
const args : IRouteArgsReader;
const nextMdlwr : IRequestHandler
) : IResponse;
var filename : string;
begin
filename := fBaseDirectory + request.uri().getPath();
if fileExists(filename) then
begin
//serve file
result := TFileResponse.create(
response.headers(),
getContentTypeFromFilename(filename),
filename
);
end else
begin
//file not found, just pass to next middleware
result := nextMdlwr.handleRequest(request, response, args);
end;
end;
end.

0 comments on commit eea4aec

Please sign in to comment.