REST: Implement 405 responses
authorTim Starling <tstarling@wikimedia.org>
Fri, 31 May 2019 03:41:40 +0000 (13:41 +1000)
committerTim Starling <tstarling@wikimedia.org>
Wed, 12 Jun 2019 00:22:34 +0000 (10:22 +1000)
Change-Id: I2a4676569a9903d12b7f5f731c5fd47ceafc3c6c

includes/Rest/Router.php

index ab54439..83cd0f0 100644 (file)
@@ -181,6 +181,20 @@ class Router {
                return $this->matchers;
        }
 
+       /**
+        * Remove the path prefix $this->rootPath. Return the part of the path with the
+        * prefix removed, or false if the prefix did not match.
+        *
+        * @param string $path
+        * @return false|string
+        */
+       private function getRelativePath( $path ) {
+               if ( substr_compare( $path, $this->rootPath, 0, strlen( $this->rootPath ) ) !== 0 ) {
+                       return false;
+               }
+               return substr( $path, strlen( $this->rootPath ) );
+       }
+
        /**
         * Find the handler for a request and execute it
         *
@@ -188,20 +202,37 @@ class Router {
         * @return ResponseInterface
         */
        public function execute( RequestInterface $request ) {
-               $matchers = $this->getMatchers();
-               $matcher = $matchers[$request->getMethod()] ?? null;
-               if ( $matcher === null ) {
-                       return $this->responseFactory->createHttpError( 404 );
-               }
                $path = $request->getUri()->getPath();
-               if ( substr_compare( $path, $this->rootPath, 0, strlen( $this->rootPath ) ) !== 0 ) {
+               $relPath = $this->getRelativePath( $path );
+               if ( $relPath === false ) {
                        return $this->responseFactory->createHttpError( 404 );
                }
-               $relPath = substr( $path, strlen( $this->rootPath ) );
-               $match = $matcher->match( $relPath );
+
+               $matchers = $this->getMatchers();
+               $matcher = $matchers[$request->getMethod()] ?? null;
+               $match = $matcher ? $matcher->match( $relPath ) : null;
+
                if ( !$match ) {
-                       return $this->responseFactory->createHttpError( 404 );
+                       // Check for 405 wrong method
+                       $allowed = [];
+                       foreach ( $matchers as $allowedMethod => $allowedMatcher ) {
+                               if ( $allowedMethod === $request->getMethod() ) {
+                                       continue;
+                               }
+                               if ( $allowedMatcher->match( $relPath ) ) {
+                                       $allowed[] = $allowedMethod;
+                               }
+                       }
+                       if ( $allowed ) {
+                               $response = $this->responseFactory->createHttpError( 405 );
+                               $response->setHeader( 'Allow', $allowed );
+                               return $response;
+                       } else {
+                               // Did not match with any other method, must be 404
+                               return $this->responseFactory->createHttpError( 404 );
+                       }
                }
+
                $request->setAttributes( $match['params'] );
                $spec = $match['userData'];
                $objectFactorySpec = array_intersect_key( $spec,