From 822e996e8f96ca8c7d070f7e8febcb323cc5782e Mon Sep 17 00:00:00 2001 From: Martin Jul Date: Thu, 6 Oct 2022 12:05:57 +0200 Subject: [PATCH] Add pre-conditions to reject invalid URL inputs --- src/ring/util/http_response.clj | 16 ++++ test/ring/util/http_response_test.clj | 112 +++++++++++++++----------- 2 files changed, 80 insertions(+), 48 deletions(-) diff --git a/src/ring/util/http_response.clj b/src/ring/util/http_response.clj index ea35d90..7db79ad 100644 --- a/src/ring/util/http_response.clj +++ b/src/ring/util/http_response.clj @@ -43,12 +43,21 @@ :headers {} :body body})) +(defn ^{:private true} + url-like? + "Is x a URL-like parameter?" + [x] + (or (string? x) + (instance? java.net.URI x) + (instance? java.net.URL x))) + (defn created "201 Created (Success) The request has been fulfilled and resulted in a new resource being created." ([] (created nil)) ([url] (created url nil)) ([url body] + {:pre [(or (nil? url) (url-like? url))]} {:status 201 :headers {"Location" url} :body body})) @@ -127,6 +136,7 @@ "300 Multiple Choices (Redirection) There are multiple options for the resource that the client may follow." ([url] + {:pre [(url-like? url)]} {:status 300 :headers {"Location" url} :body ""})) @@ -135,6 +145,7 @@ "301 Moved Permanently (Redirection) This and all future requests should be directed to the given URI." ([url] + {:pre [(url-like? url)]} {:status 301 :headers {"Location" url} :body ""})) @@ -143,6 +154,7 @@ "302 Found (Redirection) The resource was found but at a different URI." ([url] + {:pre [(url-like? url)]} {:status 302 :headers {"Location" url} :body ""})) @@ -151,6 +163,7 @@ "303 See Other (Redirection) The response to the request can be found under another URI using a GET method." ([url] + {:pre [(url-like? url)]} {:status 303 :headers {"Location" url} :body ""})) @@ -167,6 +180,7 @@ "305 Use Proxy (Redirection) This single request is to be repeated via the proxy given by the Location field." ([url] + {:pre [(url-like? url)]} {:status 305 :headers {"Location" url} :body ""})) @@ -175,6 +189,7 @@ "307 Temporary Redirect (Redirection) The request should be repeated with another URI but future requests can still use the original URI." ([url] + {:pre [(url-like? url)]} {:status 307 :headers {"Location" url} :body ""})) @@ -183,6 +198,7 @@ "308 Permanent Redirect (Redirection) The request and all future requests should be repeated using another URI." ([url] + {:pre [(url-like? url)]} {:status 308 :headers {"Location" url} :body ""})) diff --git a/test/ring/util/http_response_test.clj b/test/ring/util/http_response_test.clj index 80e80ca..8532619 100644 --- a/test/ring/util/http_response_test.clj +++ b/test/ring/util/http_response_test.clj @@ -20,7 +20,10 @@ (is (= {:status 206 :headers {} :body "body"} (partial-content "body"))) (is (= {:status 207 :headers {} :body "body"} (multi-status "body"))) (is (= {:status 208 :headers {} :body "body"} (already-reported "body"))) - (is (= {:status 226 :headers {} :body "body"} (im-used "body")))) + (is (= {:status 226 :headers {} :body "body"} (im-used "body"))) + (testing "Must reject invalid inputs" + (is (thrown? AssertionError (created 1) "Non-nil URL must be a URL-like type")) + (is (thrown? AssertionError (created {}) "Non-nil URL must be a URL-like type")))) (testing "Redirection" (is (= {:status 300 :headers {"Location" "/url"} :body ""} (multiple-choices "/url"))) @@ -30,55 +33,68 @@ (is (= {:status 304 :headers {} :body ""} (not-modified))) (is (= {:status 305 :headers {"Location" "/url"} :body ""} (use-proxy "/url"))) (is (= {:status 307 :headers {"Location" "/url"} :body ""} (temporary-redirect "/url"))) - (is (= {:status 308 :headers {"Location" "/url"} :body ""} (permanent-redirect "/url")))) + (is (= {:status 308 :headers {"Location" "/url"} :body ""} (permanent-redirect "/url"))) + (testing "Must reject invalid inputs" + (is (thrown? AssertionError (multiple-choices nil) "URL must be a non-nil URL-like type")) + (is (thrown? AssertionError (multiple-choices 1) "URL must be a non-nil URL-like type")) + (is (thrown? AssertionError (moved-permanently nil) "URL must be a non-nil URL-like type")) + (is (thrown? AssertionError (moved-permanently {}) "URL must be a non-nil URL-like type")) + (is (thrown? AssertionError (found nil) "URL must be a non-nil URL-like type")) + (is (thrown? AssertionError (found ::not-a-url) "URL must be a non-nil URL-like type")) + (is (thrown? AssertionError (use-proxy nil) "URL must be a non-nil URL-like type")) + (is (thrown? AssertionError (use-proxy ::not-a-url) "URL must be a non-nil URL-like type")) + (is (thrown? AssertionError (temporary-redirect nil) "URL must be a non-nil URL-like type")) + (is (thrown? AssertionError (temporary-redirect ::not-a-url) "URL must be a non-nil URL-like type")) + (is (thrown? AssertionError (permanent-redirect nil) "URL must be a non-nil URL-like type")) + (is (thrown? AssertionError (permanent-redirect ::not-a-url) "URL must be a non-nil URL-like type")))) - (testing "ClientError" - (is (= {:status 400 :headers {} :body "body"} (bad-request "body"))) - (is (= {:status 401 :headers {} :body "body"} (unauthorized "body"))) - (is (= {:status 402 :headers {} :body "body"} (payment-required "body"))) - (is (= {:status 403 :headers {} :body "body"} (forbidden "body"))) - (is (= {:status 404 :headers {} :body "body"} (not-found "body"))) - (is (= {:status 405 :headers {} :body "body"} (method-not-allowed "body"))) - (is (= {:status 406 :headers {} :body "body"} (not-acceptable "body"))) - (is (= {:status 407 :headers {} :body "body"} (proxy-authentication-required "body"))) - (is (= {:status 408 :headers {} :body "body"} (request-timeout "body"))) - (is (= {:status 409 :headers {} :body "body"} (conflict "body"))) - (is (= {:status 410 :headers {} :body "body"} (gone "body"))) - (is (= {:status 411 :headers {} :body "body"} (length-required "body"))) - (is (= {:status 412 :headers {} :body "body"} (precondition-failed "body"))) - (is (= {:status 413 :headers {} :body "body"} (request-entity-too-large "body"))) - (is (= {:status 414 :headers {} :body "body"} (request-uri-too-long "body"))) - (is (= {:status 415 :headers {} :body "body"} (unsupported-media-type "body"))) - (is (= {:status 416 :headers {} :body "body"} (requested-range-not-satisfiable "body"))) - (is (= {:status 417 :headers {} :body "body"} (expectation-failed "body"))) - (is (= {:status 420 :headers {} :body "body"} (enhance-your-calm "body"))) - (is (= {:status 422 :headers {} :body "body"} (unprocessable-entity "body"))) - (is (= {:status 423 :headers {} :body "body"} (locked "body"))) - (is (= {:status 424 :headers {} :body "body"} (failed-dependency "body"))) - (is (= {:status 425 :headers {} :body "body"} (unordered-collection "body"))) - (is (= {:status 426 :headers {} :body "body"} (upgrade-required "body"))) - (is (= {:status 428 :headers {} :body "body"} (precondition-required "body"))) - (is (= {:status 429 :headers {} :body "body"} (too-many-requests "body"))) - (is (= {:status 431 :headers {} :body "body"} (request-header-fields-too-large "body"))) - (is (= {:status 449 :headers {} :body "body"} (retry-with "body"))) - (is (= {:status 450 :headers {} :body "body"} (blocked-by-windows-parental-controls "body"))) - (is (= {:status 451 :headers {} :body "body"} (unavailable-for-legal-reasons "body")))) + (testing "ClientError" + (is (= {:status 400 :headers {} :body "body"} (bad-request "body"))) + (is (= {:status 401 :headers {} :body "body"} (unauthorized "body"))) + (is (= {:status 402 :headers {} :body "body"} (payment-required "body"))) + (is (= {:status 403 :headers {} :body "body"} (forbidden "body"))) + (is (= {:status 404 :headers {} :body "body"} (not-found "body"))) + (is (= {:status 405 :headers {} :body "body"} (method-not-allowed "body"))) + (is (= {:status 406 :headers {} :body "body"} (not-acceptable "body"))) + (is (= {:status 407 :headers {} :body "body"} (proxy-authentication-required "body"))) + (is (= {:status 408 :headers {} :body "body"} (request-timeout "body"))) + (is (= {:status 409 :headers {} :body "body"} (conflict "body"))) + (is (= {:status 410 :headers {} :body "body"} (gone "body"))) + (is (= {:status 411 :headers {} :body "body"} (length-required "body"))) + (is (= {:status 412 :headers {} :body "body"} (precondition-failed "body"))) + (is (= {:status 413 :headers {} :body "body"} (request-entity-too-large "body"))) + (is (= {:status 414 :headers {} :body "body"} (request-uri-too-long "body"))) + (is (= {:status 415 :headers {} :body "body"} (unsupported-media-type "body"))) + (is (= {:status 416 :headers {} :body "body"} (requested-range-not-satisfiable "body"))) + (is (= {:status 417 :headers {} :body "body"} (expectation-failed "body"))) + (is (= {:status 420 :headers {} :body "body"} (enhance-your-calm "body"))) + (is (= {:status 422 :headers {} :body "body"} (unprocessable-entity "body"))) + (is (= {:status 423 :headers {} :body "body"} (locked "body"))) + (is (= {:status 424 :headers {} :body "body"} (failed-dependency "body"))) + (is (= {:status 425 :headers {} :body "body"} (unordered-collection "body"))) + (is (= {:status 426 :headers {} :body "body"} (upgrade-required "body"))) + (is (= {:status 428 :headers {} :body "body"} (precondition-required "body"))) + (is (= {:status 429 :headers {} :body "body"} (too-many-requests "body"))) + (is (= {:status 431 :headers {} :body "body"} (request-header-fields-too-large "body"))) + (is (= {:status 449 :headers {} :body "body"} (retry-with "body"))) + (is (= {:status 450 :headers {} :body "body"} (blocked-by-windows-parental-controls "body"))) + (is (= {:status 451 :headers {} :body "body"} (unavailable-for-legal-reasons "body")))) - (testing "ServerError" - (is (= {:status 500 :headers {} :body "body"} (internal-server-error "body"))) - (is (= {:status 501 :headers {} :body "body"} (not-implemented "body"))) - (is (= {:status 502 :headers {} :body "body"} (bad-gateway "body"))) - (is (= {:status 503 :headers {} :body "body"} (service-unavailable "body"))) - (is (= {:status 504 :headers {} :body "body"} (gateway-timeout "body"))) - (is (= {:status 505 :headers {} :body "body"} (http-version-not-supported "body"))) - (is (= {:status 506 :headers {} :body "body"} (variant-also-negotiates "body"))) - (is (= {:status 507 :headers {} :body "body"} (insufficient-storage "body"))) - (is (= {:status 508 :headers {} :body "body"} (loop-detected "body"))) - (is (= {:status 509 :headers {} :body "body"} (bandwidth-limit-exceeded "body"))) - (is (= {:status 510 :headers {} :body "body"} (not-extended "body"))) - (is (= {:status 511 :headers {} :body "body"} (network-authentication-required "body"))) - (is (= {:status 598 :headers {} :body "body"} (network-read-timeout "body"))) - (is (= {:status 599 :headers {} :body "body"} (network-connect-timeout "body"))))) + (testing "ServerError" + (is (= {:status 500 :headers {} :body "body"} (internal-server-error "body"))) + (is (= {:status 501 :headers {} :body "body"} (not-implemented "body"))) + (is (= {:status 502 :headers {} :body "body"} (bad-gateway "body"))) + (is (= {:status 503 :headers {} :body "body"} (service-unavailable "body"))) + (is (= {:status 504 :headers {} :body "body"} (gateway-timeout "body"))) + (is (= {:status 505 :headers {} :body "body"} (http-version-not-supported "body"))) + (is (= {:status 506 :headers {} :body "body"} (variant-also-negotiates "body"))) + (is (= {:status 507 :headers {} :body "body"} (insufficient-storage "body"))) + (is (= {:status 508 :headers {} :body "body"} (loop-detected "body"))) + (is (= {:status 509 :headers {} :body "body"} (bandwidth-limit-exceeded "body"))) + (is (= {:status 510 :headers {} :body "body"} (not-extended "body"))) + (is (= {:status 511 :headers {} :body "body"} (network-authentication-required "body"))) + (is (= {:status 598 :headers {} :body "body"} (network-read-timeout "body"))) + (is (= {:status 599 :headers {} :body "body"} (network-connect-timeout "body"))))) (declare slingshots?) (defmethod assert-expr 'slingshots? [msg form]