diff --git a/yesod-core/ChangeLog.md b/yesod-core/ChangeLog.md index 3c508ef0..79718bf8 100644 --- a/yesod-core/ChangeLog.md +++ b/yesod-core/ChangeLog.md @@ -1,3 +1,9 @@ +## 1.6.7 + +* If no matches are found, `selectRep` chooses first representation regardless + of the presence or absence of a `Content-Type` header in the request + [#1540](https://github.com/yesodweb/yesod/pull/1540) + ## 1.6.6 * `defaultErrorHandler` handles text/plain requests [#1522](https://github.com/yesodweb/yesod/pull/1520) diff --git a/yesod-core/Yesod/Core/Handler.hs b/yesod-core/Yesod/Core/Handler.hs index b9a7b7d0..83d0af79 100644 --- a/yesod-core/Yesod/Core/Handler.hs +++ b/yesod-core/Yesod/Core/Handler.hs @@ -1289,15 +1289,9 @@ selectRep w = do [] -> case reps of [] -> sendResponseStatus H.status500 ("No reps provided to selectRep" :: Text) - rep:_ -> - if null cts - then returnRep rep - else sendResponseStatus H.status406 explainUnaccepted + rep:_ -> returnRep rep rep:_ -> returnRep rep where - explainUnaccepted :: Text - explainUnaccepted = "no match found for accept header" - returnRep (ProvidedRep ct mcontent) = fmap (TypedContent ct) mcontent reps = appEndo (Writer.execWriter w) [] diff --git a/yesod-core/test/YesodCoreTest/ErrorHandling.hs b/yesod-core/test/YesodCoreTest/ErrorHandling.hs index f6fb6bb4..048342ce 100644 --- a/yesod-core/test/YesodCoreTest/ErrorHandling.hs +++ b/yesod-core/test/YesodCoreTest/ErrorHandling.hs @@ -40,6 +40,11 @@ mkYesod "App" [parseRoutes| /file-bad-name FileBadNameR GET /good-builder GoodBuilderR GET + +/auth-not-accepted AuthNotAcceptedR GET +/auth-not-adequate AuthNotAdequateR GET +/args-not-valid ArgsNotValidR POST +/only-plain-text OnlyPlainTextR GET |] overrideStatus :: Status @@ -119,6 +124,18 @@ getErrorR 9 = setUltDest (undefined :: Text) getErrorR 10 = setMessage undefined getErrorR x = error $ "getErrorR: " ++ show x +getAuthNotAcceptedR :: Handler TypedContent +getAuthNotAcceptedR = notAuthenticated + +getAuthNotAdequateR :: Handler TypedContent +getAuthNotAdequateR = permissionDenied "That doesn't belong to you. " + +postArgsNotValidR :: Handler TypedContent +postArgsNotValidR = invalidArgs ["Doesn't matter.", "Don't want it."] + +getOnlyPlainTextR :: Handler TypedContent +getOnlyPlainTextR = selectRep $ provideRepType "text/plain" $ return ("Only plain text." :: Text) + errorHandlingTest :: Spec errorHandlingTest = describe "Test.ErrorHandling" $ do it "says not found" caseNotFound @@ -132,6 +149,11 @@ errorHandlingTest = describe "Test.ErrorHandling" $ do it "file with bad name" caseFileBadName it "builder includes content-length" caseGoodBuilder forM_ [1..10] $ \i -> it ("error case " ++ show i) (caseError i) + it "accept DVI file, invalid args -> 400" caseDviInvalidArgs + it "accept audio, not authenticated -> 401" caseAudioNotAuthenticated + it "accept CSS, permission denied -> 403" caseCssPermissionDenied + it "accept image, non-existent path -> 404" caseImageNotFound + it "accept video, bad method -> 405" caseVideoBadMethod runner :: Session a -> IO a runner f = toWaiApp App >>= runSession f @@ -222,3 +244,50 @@ caseError i = runner $ do ReaderT $ \r -> StateT $ \s -> runStateT (runReaderT (assertStatus 500 res) r) s `E.catch` \e -> do liftIO $ print res E.throwIO (e :: E.SomeException) + +caseDviInvalidArgs :: IO () +caseDviInvalidArgs = runner $ do + res <- request defaultRequest + { pathInfo = ["args-not-valid"] + , requestMethod = "POST" + , requestHeaders = + ("accept", "application/x-dvi") : requestHeaders defaultRequest + } + assertStatus 400 res + +caseAudioNotAuthenticated :: IO () +caseAudioNotAuthenticated = runner $ do + res <- request defaultRequest + { pathInfo = ["auth-not-accepted"] + , requestHeaders = + ("accept", "audio/mpeg") : requestHeaders defaultRequest + } + assertStatus 401 res + +caseCssPermissionDenied :: IO () +caseCssPermissionDenied = runner $ do + res <- request defaultRequest + { pathInfo = ["auth-not-adequate"] + , requestHeaders = + ("accept", "text/css") : requestHeaders defaultRequest + } + assertStatus 403 res + +caseImageNotFound :: IO () +caseImageNotFound = runner $ do + res <- request defaultRequest + { pathInfo = ["not_a_path"] + , requestHeaders = + ("accept", "image/jpeg") : requestHeaders defaultRequest + } + assertStatus 404 res + +caseVideoBadMethod :: IO () +caseVideoBadMethod = runner $ do + res <- request defaultRequest + { pathInfo = ["good-builder"] + , requestMethod = "DELETE" + , requestHeaders = + ("accept", "video/webm") : requestHeaders defaultRequest + } + assertStatus 405 res diff --git a/yesod-core/test/YesodCoreTest/Reps.hs b/yesod-core/test/YesodCoreTest/Reps.hs index 461a39b8..ae953718 100644 --- a/yesod-core/test/YesodCoreTest/Reps.hs +++ b/yesod-core/test/YesodCoreTest/Reps.hs @@ -85,7 +85,6 @@ specs = do test "text/html" "HTML" test specialHtml "HTMLSPECIAL" testRequest 200 (acceptRequest "application/json") { pathInfo = ["json"] } "{\"message\":\"Invalid Login\"}" - testRequest 406 (acceptRequest "text/foo") "no match found for accept header" test "text/*" "HTML" test "*/*" "HTML" describe "routeAttrs" $ do diff --git a/yesod-core/yesod-core.cabal b/yesod-core/yesod-core.cabal index e1862987..a679c01c 100644 --- a/yesod-core/yesod-core.cabal +++ b/yesod-core/yesod-core.cabal @@ -1,5 +1,5 @@ name: yesod-core -version: 1.6.6 +version: 1.6.7 license: MIT license-file: LICENSE author: Michael Snoyman