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 == '/');