Send 405 (Method Not Allowed) – for requests that are not `HEAD`/`GET`
Fixes https://github.com/dart-lang/shelf_static/issues/53
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 @@
} 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 @@
}
if (fileFound == null) {
- return Response.notFound('Not Found');
+ return notFound();
}
final file = fileFound;
@@ -91,7 +94,7 @@
// 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 @@
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 @@
/// [getContentType] and uses it to populate the Content-Type header.
Future<Response> _handleFile(Request request, File file,
FutureOr<String?> Function() getContentType) async {
+ if (!_getMethods.contains(request.method)) {
+ return _methodNotAllowed();
+ }
+
final stat = file.statSync();
final ifModifiedSince = request.ifModifiedSince;
@@ -201,3 +208,13 @@
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 '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 @@
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<void> _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 @@
return (Request request) {
if (!_ctx.isWithin('/$path', request.requestedUri.path)) {
- return Response.notFound('not found');
+ return notFound();
}
assert(request.handlerPath == '/');