diff --git a/CHANGELOG.md b/CHANGELOG.md index 60be005..2dd106f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ -## 1.1.0-dev +## 1.1.0 +* Correctly send 405 (Method Not Allowed) – for requests that are not `HEAD` or + `GET` method. * Correctly handle `HEAD` requests. ## 1.0.0 diff --git a/lib/src/static_handler.dart b/lib/src/static_handler.dart index 11220aa..86e60f9 100644 --- a/lib/src/static_handler.dart +++ b/lib/src/static_handler.dart @@ -75,6 +75,9 @@ Handler createStaticHandler(String fileSystemPath, } else if (entityType == FileSystemEntityType.directory) { fileFound = _tryDefaultFile(fsPath, defaultDocument); if (fileFound == null && listDirectories) { + if (!_getMethods.contains(request.method)) { + return _methodNotAllowed(); + } final uri = request.requestedUri; if (!uri.path.endsWith('/')) return _redirectToAddTrailingSlash(uri); return listDirectory(fileSystemPath, fsPath); @@ -82,7 +85,7 @@ Handler createStaticHandler(String fileSystemPath, } if (fileFound == null) { - return Response.notFound('Not Found'); + return notFound(); } final file = fileFound; @@ -91,7 +94,7 @@ Handler createStaticHandler(String fileSystemPath, // Do not serve a file outside of the original fileSystemPath if (!p.isWithin(fileSystemPath, resolvedPath)) { - return Response.notFound('Not Found'); + return notFound(); } } @@ -166,7 +169,7 @@ Handler createFileHandler(String path, {String? url, String? contentType}) { url ??= p.toUri(p.basename(path)).toString(); return (request) { - if (request.url.path != url) return Response.notFound('Not Found'); + if (request.url.path != url) return notFound(); return _handleFile(request, file, () => mimeType); }; } @@ -178,6 +181,10 @@ Handler createFileHandler(String path, {String? url, String? contentType}) { /// [getContentType] and uses it to populate the Content-Type header. Future _handleFile(Request request, File file, FutureOr Function() getContentType) async { + if (!_getMethods.contains(request.method)) { + return _methodNotAllowed(); + } + final stat = file.statSync(); final ifModifiedSince = request.ifModifiedSince; @@ -201,3 +208,13 @@ Future _handleFile(Request request, File file, headers: headers, ); } + +/// HTTP methods which return OK (200) responses with static file servers. +const _getMethods = {'GET', 'HEAD'}; + +/// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/405 and +/// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Allow +Response _methodNotAllowed() => Response( + 405, + headers: {'Allow': _getMethods.join(', ')}, + ); diff --git a/lib/src/util.dart b/lib/src/util.dart index 2d42379..772185c 100644 --- a/lib/src/util.dart +++ b/lib/src/util.dart @@ -2,7 +2,11 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. +import 'package:shelf/shelf.dart'; + DateTime toSecondResolution(DateTime dt) { if (dt.millisecond == 0) return dt; return dt.subtract(Duration(milliseconds: dt.millisecond)); } + +Response notFound() => Response.notFound('Not Found'); diff --git a/pubspec.yaml b/pubspec.yaml index a8fdc56..824b2d6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: shelf_static -version: 1.1.0-dev +version: 1.1.0 description: Static file server support for the shelf package and ecosystem repository: https://github.com/dart-lang/shelf_static diff --git a/test/basic_file_test.dart b/test/basic_file_test.dart index a1425da..6f83980 100644 --- a/test/basic_file_test.dart +++ b/test/basic_file_test.dart @@ -8,6 +8,7 @@ import 'dart:io'; import 'package:http_parser/http_parser.dart'; import 'package:mime/mime.dart' as mime; import 'package:path/path.dart' as p; +import 'package:shelf/src/handler.dart'; import 'package:shelf_static/shelf_static.dart'; import 'package:test/test.dart'; import 'package:test_descriptor/test_descriptor.dart' as d; @@ -233,4 +234,29 @@ void main() { expect(response.mimeType, 'image/webp'); }); }); + + group('HTTP method', () { + for (var method in _httpMethods) { + test('$method - file', () async { + final handler = createStaticHandler(d.sandbox); + + await _testBadMethod(handler, method, '/root.txt'); + }); + + test('$method - directory', () async { + final handler = createStaticHandler(d.sandbox, listDirectories: true); + + await _testBadMethod(handler, method, '/'); + }); + } + }); } + +Future _testBadMethod(Handler handler, String method, String path) async { + final response = await makeRequest(handler, path, method: method); + + expect(response.statusCode, HttpStatus.methodNotAllowed); + expect(response.headers['allow'], 'GET, HEAD'); +} + +const _httpMethods = {'PUT', 'POST', 'DELETE', 'PATCH'}; diff --git a/test/test_util.dart b/test/test_util.dart index 5c628d8..dada6c1 100644 --- a/test/test_util.dart +++ b/test/test_util.dart @@ -35,7 +35,7 @@ Handler _rootHandler(String? path, Handler handler) { return (Request request) { if (!_ctx.isWithin('/$path', request.requestedUri.path)) { - return Response.notFound('not found'); + return notFound(); } assert(request.handlerPath == '/');