Skip to content

Commit eea4aec

Browse files
committed
fix bug which allow hidden dot files to be served by TStaticFilesMiddleware
1 parent 076846e commit eea4aec

File tree

2 files changed

+152
-70
lines changed

2 files changed

+152
-70
lines changed
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
{*!
2+
* Fano Web Framework (https://fanoframework.github.io)
3+
*
4+
* @link https://github.com/fanoframework/fano
5+
* @copyright Copyright (c) 2018 - 2022 Zamrony P. Juhara
6+
* @license https://github.com/fanoframework/fano/blob/master/LICENSE (MIT)
7+
*}
8+
9+
unit BaseStaticFilesMiddlewareImpl;
10+
11+
interface
12+
13+
{$MODE OBJFPC}
14+
{$H+}
15+
16+
uses
17+
18+
RequestIntf,
19+
ResponseIntf,
20+
MiddlewareIntf,
21+
RouteArgsReaderIntf,
22+
RequestHandlerIntf,
23+
ReadOnlyKeyValuePairIntf,
24+
InjectableObjectImpl;
25+
26+
type
27+
28+
(*!------------------------------------------------
29+
* base middleware class that serves static files from
30+
* a base directory.
31+
*-------------------------------------------------
32+
* Content type of response will be determined
33+
* using file extension that is stored in fMimeTypes
34+
* if not set then 'application/octet-stream' is assumed
35+
*-------------------------------------------------
36+
* @author Zamrony P. Juhara <zamronypj@yahoo.com>
37+
*-------------------------------------------------*)
38+
TBaseStaticFilesMiddleware = class(TInjectableObject, IMiddleware)
39+
protected
40+
fBaseDirectory : string;
41+
fMimeTypes : IReadOnlyKeyValuePair;
42+
function getContentTypeFromFilename(const filename : string) : string;
43+
44+
(*!-------------------------------------------
45+
* clean filepath (if required)
46+
*--------------------------------------------
47+
* This method is provided so that descendant
48+
* have opportunity to clean file path
49+
*--------------------------------------------
50+
* @param filePath original file path
51+
* @return new cleaned file path
52+
*--------------------------------------------*)
53+
function clean(const filePath: string) : string; virtual;
54+
public
55+
constructor create(
56+
const baseDir : string;
57+
const mimeTypes : IReadOnlyKeyValuePair
58+
);
59+
60+
function handleRequest(
61+
const request : IRequest;
62+
const response : IResponse;
63+
const args : IRouteArgsReader;
64+
const nextMdlwr : IRequestHandler
65+
) : IResponse; virtual;
66+
end;
67+
68+
implementation
69+
70+
uses
71+
72+
SysUtils,
73+
FileResponseImpl;
74+
75+
constructor TBaseStaticFilesMiddleware.create(
76+
const baseDir : string;
77+
const mimeTypes : IReadOnlyKeyValuePair
78+
);
79+
begin
80+
fBaseDirectory := baseDir;
81+
fMimeTypes := mimeTypes;
82+
end;
83+
84+
function TBaseStaticFilesMiddleware.getContentTypeFromFilename(
85+
const filename : string
86+
) : string;
87+
var ext : string;
88+
begin
89+
ext := ExtractFileExt(filename);
90+
//remove dot from ext
91+
ext := copy(ext, 2, length(ext)-1);
92+
if (fMimeTypes.has(ext)) then
93+
begin
94+
result := fMimeTypes.getValue(ext);
95+
end else
96+
begin
97+
//set default
98+
result := 'application/octet-stream';
99+
end;
100+
end;
101+
102+
function TBaseStaticFilesMiddleware.clean(const filePath: string) : string;
103+
begin
104+
result := filePath;
105+
end;
106+
107+
function TBaseStaticFilesMiddleware.handleRequest(
108+
const request : IRequest;
109+
const response : IResponse;
110+
const args : IRouteArgsReader;
111+
const nextMdlwr : IRequestHandler
112+
) : IResponse;
113+
var filename : string;
114+
begin
115+
filename := fBaseDirectory + clean(request.uri().getPath());
116+
if fileExists(filename) then
117+
begin
118+
//serve file
119+
result := TFileResponse.create(
120+
response.headers(),
121+
getContentTypeFromFilename(filename),
122+
filename
123+
);
124+
end else
125+
begin
126+
//file not found, just pass to next middleware
127+
result := nextMdlwr.handleRequest(request, response, args);
128+
end;
129+
end;
130+
end.

src/Middleware/BuiltIns/StaticFilesMiddlewareImpl.pas

Lines changed: 22 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,7 @@ interface
1515

1616
uses
1717

18-
RequestIntf,
19-
ResponseIntf,
20-
MiddlewareIntf,
21-
RouteArgsReaderIntf,
22-
RequestHandlerIntf,
23-
ReadOnlyKeyValuePairIntf,
24-
InjectableObjectImpl;
18+
BaseStaticFilesMiddlewareImpl;
2519

2620
type
2721

@@ -35,80 +29,38 @@ interface
3529
*-------------------------------------------------
3630
* @author Zamrony P. Juhara <zamronypj@yahoo.com>
3731
*-------------------------------------------------*)
38-
TStaticFilesMiddleware = class(TInjectableObject, IMiddleware)
32+
TStaticFilesMiddleware = class(TBaseStaticFilesMiddleware)
3933
protected
40-
fBaseDirectory : string;
41-
fMimeTypes : IReadOnlyKeyValuePair;
42-
function getContentTypeFromFilename(const filename : string) : string;
34+
(*!-------------------------------------------
35+
* clean filepath avoid serve hidden dot files in unix
36+
*--------------------------------------------
37+
* @param filePath original file path
38+
* @return new cleaned file path
39+
*--------------------------------------------*)
40+
function clean(const filePath: string) : string; override;
4341
public
44-
constructor create(
45-
const baseDir : string;
46-
const mimeTypes : IReadOnlyKeyValuePair
47-
);
48-
49-
function handleRequest(
50-
const request : IRequest;
51-
const response : IResponse;
52-
const args : IRouteArgsReader;
53-
const nextMdlwr : IRequestHandler
54-
) : IResponse; virtual;
5542
end;
5643

5744
implementation
5845

5946
uses
6047

61-
SysUtils,
62-
FileResponseImpl;
48+
SysUtils;
6349

64-
constructor TStaticFilesMiddleware.create(
65-
const baseDir : string;
66-
const mimeTypes : IReadOnlyKeyValuePair
67-
);
68-
begin
69-
fBaseDirectory := baseDir;
70-
fMimeTypes := mimeTypes;
71-
end;
7250

73-
function TStaticFilesMiddleware.getContentTypeFromFilename(
74-
const filename : string
75-
) : string;
76-
var ext : string;
51+
(*!-------------------------------------------
52+
* clean filepath avoid serve hidden dot files in unix
53+
*--------------------------------------------
54+
* @param filePath original file path
55+
* @return new cleaned file path
56+
*--------------------------------------------*)
57+
function TStaticFilesMiddleware.clean(const filePath: string) : string;
7758
begin
78-
ext := ExtractFileExt(filename);
79-
//remove dot from ext
80-
ext := copy(ext, 2, length(ext)-1);
81-
if (fMimeTypes.has(ext)) then
82-
begin
83-
result := fMimeTypes.getValue(ext);
84-
end else
85-
begin
86-
//set default
87-
result := 'application/octet-stream';
88-
end;
59+
// for example if filePath contain '/.htaccess' we replace it so
60+
// filePath become '/htaccess'
61+
result := stringReplace(filePath, '/.', '/', [rfReplaceAll]);
62+
// just paranoia handle .. too
63+
result := stringReplace(result, '..', '', [rfReplaceAll]);
8964
end;
9065

91-
function TStaticFilesMiddleware.handleRequest(
92-
const request : IRequest;
93-
const response : IResponse;
94-
const args : IRouteArgsReader;
95-
const nextMdlwr : IRequestHandler
96-
) : IResponse;
97-
var filename : string;
98-
begin
99-
filename := fBaseDirectory + request.uri().getPath();
100-
if fileExists(filename) then
101-
begin
102-
//serve file
103-
result := TFileResponse.create(
104-
response.headers(),
105-
getContentTypeFromFilename(filename),
106-
filename
107-
);
108-
end else
109-
begin
110-
//file not found, just pass to next middleware
111-
result := nextMdlwr.handleRequest(request, response, args);
112-
end;
113-
end;
11466
end.

0 commit comments

Comments
 (0)