diff --git a/spec/std/http/client/client_spec.cr b/spec/std/http/client/client_spec.cr index fc38192c0c5e..74ad4ce0c133 100644 --- a/spec/std/http/client/client_spec.cr +++ b/spec/std/http/client/client_spec.cr @@ -60,6 +60,21 @@ module HTTP typeof(Client.post("http://www.example.com", body: Bytes[65])) typeof(Client.new("host").post("/", body: Bytes[65])) + describe "from String" do + it "raises when not a host" do + ["http://www.example.com", + "www.example.com:8080", + "example.com/path", + "example.com?query", + "http://example.com:bad_port", + "user:pass@domain"].each do |string| + expect_raises(ArgumentError, "The string passed to create an HTTP::Client must be just a host, not #{string.inspect}") do + Client.new(string) + end + end + end + end + describe "from URI" do it "has sane defaults" do cl = Client.new(URI.parse("http://example.com")) diff --git a/src/http/client.cr b/src/http/client.cr index a688e26da68d..5299f486ab70 100644 --- a/src/http/client.cr +++ b/src/http/client.cr @@ -125,6 +125,8 @@ class HTTP::Client # be used, else the given one. In any case the active context can be accessed through `tls`. {% if flag?(:without_openssl) %} def initialize(@host : String, port = nil, tls : Bool = false) + check_host_only(@host) + @tls = nil if tls raise "HTTP::Client TLS is disabled because `-D without_openssl` was passed at compile time" @@ -135,6 +137,8 @@ class HTTP::Client end {% else %} def initialize(@host : String, port = nil, tls : Bool | OpenSSL::SSL::Context::Client = false) + check_host_only(@host) + @tls = case tls when true OpenSSL::SSL::Context::Client.new @@ -149,6 +153,21 @@ class HTTP::Client end {% end %} + private def check_host_only(string : String) + # When parsing a URI with just a host + # we end up with a URI with just a path + uri = URI.parse(string) + if uri.scheme || uri.host || uri.port || uri.query || uri.user || uri.password || uri.path.includes?('/') + raise_invalid_host(string) + end + rescue URI::Error + raise_invalid_host(string) + end + + private def raise_invalid_host(string : String) + raise ArgumentError.new("The string passed to create an HTTP::Client must be just a host, not #{string.inspect}") + end + # Creates a new HTTP client from a URI. Parses the *host*, *port*, # and *tls* configuration from the URI provided. Port defaults to # 80 if not specified unless using the https protocol, which defaults