diff --git a/spec/std/http/server/server_spec.cr b/spec/std/http/server/server_spec.cr index 4dba0dff164a..731c01881eb6 100644 --- a/spec/std/http/server/server_spec.cr +++ b/spec/std/http/server/server_spec.cr @@ -298,6 +298,86 @@ module HTTP end server.close unless server.closed? end + + describe "with URI" do + it "accepts URI" do + server = Server.new { } + + begin + address = server.bind URI.parse("tcp://127.0.0.1:8081") + address.should eq Socket::IPAddress.new("127.0.0.1", 8081) + ensure + server.close + end + end + + it "accepts String" do + server = Server.new { } + + begin + address = server.bind "tcp://127.0.0.1:8081" + address.should eq Socket::IPAddress.new("127.0.0.1", 8081) + ensure + server.close + end + end + + it "parses TCP" do + server = Server.new { } + + begin + address = server.bind "tcp://127.0.0.1:8081" + address.should eq Socket::IPAddress.new("127.0.0.1", 8081) + ensure + server.close + end + end + + it "parses SSL" do + server = Server.new { } + + private_key = datapath("openssl", "openssl.key") + certificate = datapath("openssl", "openssl.crt") + + begin + expect_raises(ArgumentError, "missing CA certificate") do + server.bind "ssl://127.0.0.1:8081?key=#{private_key}&cert=#{certificate}&verify_mode=force-peer" + end + + address = server.bind "ssl://127.0.0.1:8081?key=#{private_key}&cert=#{certificate}&ca=#{certificate}" + address.should eq Socket::IPAddress.new("127.0.0.1", 8081) + ensure + server.close + end + end + + it "fails SSL with invalid params" do + server = Server.new { } + + private_key = datapath("openssl", "openssl.key") + certificate = datapath("openssl", "openssl.crt") + + begin + expect_raises(ArgumentError, "missing private key") { server.bind "ssl://127.0.0.1:8081" } + expect_raises(OpenSSL::Error, "No such file or directory") { server.bind "ssl://127.0.0.1:8081?key=foo.key" } + expect_raises(ArgumentError, "missing certificate") { server.bind "ssl://127.0.0.1:8081?key=#{private_key}" } + ensure + server.close + end + end + + it "fails with unknown scheme" do + server = Server.new { } + + begin + expect_raises(ArgumentError, "Unsupported socket type: udp") do + server.bind "udp://127.0.0.1:8081" + end + ensure + server.close + end + end + end end describe "#bind_ssl" do diff --git a/spec/std/openssl/ssl/context_spec.cr b/spec/std/openssl/ssl/context_spec.cr index e62742a18924..7506e2899af4 100644 --- a/spec/std/openssl/ssl/context_spec.cr +++ b/spec/std/openssl/ssl/context_spec.cr @@ -168,4 +168,53 @@ describe OpenSSL::SSL::Context do it "calls #finalize on insecure server context" do assert_finalizes(:insecure_server_ctx) { OpenSSL::SSL::Context::Server.insecure } end + + describe ".from_hash" do + it "builds" do + private_key = datapath("openssl", "openssl.key") + certificate = datapath("openssl", "openssl.crt") + + context = OpenSSL::SSL::Context::Client.from_hash({"key" => private_key, "cert" => certificate, "verify_mode" => "none"}) + context.verify_mode.should eq OpenSSL::SSL::VerifyMode::NONE + + context = OpenSSL::SSL::Context::Server.from_hash({"key" => private_key, "cert" => certificate}) + context.verify_mode.should eq OpenSSL::SSL::VerifyMode::NONE + + context = OpenSSL::SSL::Context::Client.from_hash({"key" => private_key, "cert" => certificate, "ca" => certificate}) + context.verify_mode.should eq OpenSSL::SSL::VerifyMode::PEER + + context = OpenSSL::SSL::Context::Client.from_hash({"key" => private_key, "cert" => certificate, "ca" => File.dirname(certificate)}) + context.verify_mode.should eq OpenSSL::SSL::VerifyMode::PEER + end + + it "errors" do + private_key = datapath("openssl", "openssl.key") + certificate = datapath("openssl", "openssl.crt") + + expect_raises(ArgumentError, "missing private key") do + OpenSSL::SSL::Context::Client.from_hash({} of String => String) + end + expect_raises(OpenSSL::Error, "SSL_CTX_use_PrivateKey_file: error:02001002:system library:fopen:No such file or directory") do + OpenSSL::SSL::Context::Client.from_hash({"key" => "foo"}) + end + expect_raises(ArgumentError, "missing certificate") do + OpenSSL::SSL::Context::Client.from_hash({"key" => private_key}) + end + expect_raises(OpenSSL::Error, "SSL_CTX_use_certificate_chain_file: error:02001002:system library:fopen:No such file or directory") do + OpenSSL::SSL::Context::Client.from_hash({"key" => private_key, "cert" => "foo"}) + end + expect_raises(ArgumentError, "Invalid SSL context: missing CA certificate") do + OpenSSL::SSL::Context::Client.from_hash({"key" => private_key, "cert" => certificate}) + end + expect_raises(ArgumentError, %(Invalid SSL context: unknown verify mode "foo")) do + OpenSSL::SSL::Context::Client.from_hash({"key" => private_key, "cert" => certificate, "verify_mode" => "foo"}) + end + expect_raises(ArgumentError, "Invalid SSL context: missing CA certificate") do + OpenSSL::SSL::Context::Client.from_hash({"key" => private_key, "cert" => certificate, "verify_mode" => "peer"}) + end + expect_raises(OpenSSL::Error, "SSL_CTX_load_verify_locations: error:02001002:system library:fopen:No such file or directory") do + OpenSSL::SSL::Context::Client.from_hash({"key" => private_key, "cert" => certificate, "ca" => "foo"}) + end + end + end end diff --git a/spec/std/socket/address_spec.cr b/spec/std/socket/address_spec.cr new file mode 100644 index 000000000000..c7bd57486526 --- /dev/null +++ b/spec/std/socket/address_spec.cr @@ -0,0 +1,163 @@ +require "spec" +require "socket/address" + +describe Socket::Address do + describe ".parse" do + it "accepts URI" do + address = Socket::Address.parse URI.parse("tcp://192.168.0.1:8081") + address.should eq Socket::IPAddress.new("192.168.0.1", 8081) + end + + it "parses TCP" do + address = Socket::Address.parse "tcp://192.168.0.1:8081" + address.should eq Socket::IPAddress.new("192.168.0.1", 8081) + end + + it "parses UDP" do + address = Socket::Address.parse "udp://192.168.0.1:8081" + address.should eq Socket::IPAddress.new("192.168.0.1", 8081) + end + + it "parses UNIX" do + address = Socket::Address.parse "unix://socket.sock" + address.should eq Socket::UNIXAddress.new("socket.sock") + end + + it "fails with unknown scheme" do + expect_raises(Socket::Error, "Unsupported address type: ssl") do + Socket::Address.parse "ssl://192.168.0.1:8081" + end + end + end +end + +describe Socket::IPAddress do + it "transforms an IPv4 address into a C struct and back" do + addr1 = Socket::IPAddress.new("127.0.0.1", 8080) + addr2 = Socket::IPAddress.from(addr1.to_unsafe, addr1.size) + + addr2.family.should eq(addr1.family) + addr2.port.should eq(addr1.port) + typeof(addr2.address).should eq(String) + addr2.address.should eq(addr1.address) + end + + it "transforms an IPv6 address into a C struct and back" do + addr1 = Socket::IPAddress.new("2001:db8:8714:3a90::12", 8080) + addr2 = Socket::IPAddress.from(addr1.to_unsafe, addr1.size) + + addr2.family.should eq(addr1.family) + addr2.port.should eq(addr1.port) + typeof(addr2.address).should eq(String) + addr2.address.should eq(addr1.address) + end + + it "won't resolve domains" do + expect_raises(Socket::Error, /Invalid IP address/) do + Socket::IPAddress.new("localhost", 1234) + end + end + + it "to_s" do + Socket::IPAddress.new("127.0.0.1", 80).to_s.should eq("127.0.0.1:80") + Socket::IPAddress.new("2001:db8:8714:3a90::12", 443).to_s.should eq("[2001:db8:8714:3a90::12]:443") + end + + describe ".parse" do + it "parses IPv4" do + address = Socket::IPAddress.parse "ip://192.168.0.1:8081" + address.should eq Socket::IPAddress.new("192.168.0.1", 8081) + end + + it "parses IPv6" do + address = Socket::IPAddress.parse "ip://[::1]:8081" + address.should eq Socket::IPAddress.new("::1", 8081) + end + + it "fails host name" do + expect_raises(Socket::Error, "Invalid IP address: example.com") do + Socket::IPAddress.parse "ip://example.com:8081" + end + end + + it "ignores path and params" do + address = Socket::IPAddress.parse "ip://192.168.0.1:8081/foo?bar=baz" + address.should eq Socket::IPAddress.new("192.168.0.1", 8081) + end + + it "fails with missing host" do + expect_raises(Socket::Error, "Invalid IP address: missing host") do + Socket::IPAddress.parse "ip:///path" + end + end + + it "fails with missing port" do + expect_raises(Socket::Error, "Invalid IP address: missing port") do + Socket::IPAddress.parse "ip://127.0.0.1" + end + end + end +end + +describe Socket::UNIXAddress do + it "transforms into a C struct and back" do + path = "unix_address.sock" + + addr1 = Socket::UNIXAddress.new(path) + addr2 = Socket::UNIXAddress.from(addr1.to_unsafe, addr1.size) + + addr2.family.should eq(addr1.family) + addr2.path.should eq(addr1.path) + addr2.to_s.should eq(path) + end + + it "raises when path is too long" do + path = "unix_address-too-long-#{("a" * 2048)}.sock" + + expect_raises(ArgumentError, "Path size exceeds the maximum size") do + Socket::UNIXAddress.new(path) + end + end + + it "to_s" do + Socket::UNIXAddress.new("some_path").to_s.should eq("some_path") + end + + describe ".parse" do + it "parses relative" do + address = Socket::UNIXAddress.parse "unix://foo.sock" + address.should eq Socket::UNIXAddress.new("foo.sock") + end + + it "parses relative subpath" do + address = Socket::UNIXAddress.parse "unix://foo/bar.sock" + address.should eq Socket::UNIXAddress.new("foo/bar.sock") + end + + it "parses relative dot" do + address = Socket::UNIXAddress.parse "unix://./bar.sock" + address.should eq Socket::UNIXAddress.new("./bar.sock") + end + + it "relative with" do + address = Socket::UNIXAddress.parse "unix://foo:21/bar.sock" + address.should eq Socket::UNIXAddress.new("foo:21/bar.sock") + end + + it "parses absolute" do + address = Socket::UNIXAddress.parse "unix:///foo.sock" + address.should eq Socket::UNIXAddress.new("/foo.sock") + end + + it "ignores params" do + address = Socket::UNIXAddress.parse "unix:///foo.sock?bar=baz" + address.should eq Socket::UNIXAddress.new("/foo.sock") + end + + it "fails with missing path" do + expect_raises(Socket::Error, "Invalid UNIX address: missing path") do + Socket::UNIXAddress.parse "unix://?foo=bar" + end + end + end +end diff --git a/spec/std/socket_spec.cr b/spec/std/socket_spec.cr index 0172a5c57188..87bdcc98a10a 100644 --- a/spec/std/socket_spec.cr +++ b/spec/std/socket_spec.cr @@ -148,64 +148,6 @@ describe Socket::Addrinfo do end end -describe Socket::IPAddress do - it "transforms an IPv4 address into a C struct and back" do - addr1 = Socket::IPAddress.new("127.0.0.1", 8080) - addr2 = Socket::IPAddress.from(addr1.to_unsafe, addr1.size) - - addr2.family.should eq(addr1.family) - addr2.port.should eq(addr1.port) - typeof(addr2.address).should eq(String) - addr2.address.should eq(addr1.address) - end - - it "transforms an IPv6 address into a C struct and back" do - addr1 = Socket::IPAddress.new("2001:db8:8714:3a90::12", 8080) - addr2 = Socket::IPAddress.from(addr1.to_unsafe, addr1.size) - - addr2.family.should eq(addr1.family) - addr2.port.should eq(addr1.port) - typeof(addr2.address).should eq(String) - addr2.address.should eq(addr1.address) - end - - it "won't resolve domains" do - expect_raises(Socket::Error, /Invalid IP address/) do - Socket::IPAddress.new("localhost", 1234) - end - end - - it "to_s" do - Socket::IPAddress.new("127.0.0.1", 80).to_s.should eq("127.0.0.1:80") - Socket::IPAddress.new("2001:db8:8714:3a90::12", 443).to_s.should eq("[2001:db8:8714:3a90::12]:443") - end -end - -describe Socket::UNIXAddress do - it "transforms into a C struct and back" do - path = "unix_address.sock" - - addr1 = Socket::UNIXAddress.new(path) - addr2 = Socket::UNIXAddress.from(addr1.to_unsafe, addr1.size) - - addr2.family.should eq(addr1.family) - addr2.path.should eq(addr1.path) - addr2.to_s.should eq(path) - end - - it "raises when path is too long" do - path = "unix_address-too-long-#{("a" * 2048)}.sock" - - expect_raises(ArgumentError, "Path size exceeds the maximum size") do - Socket::UNIXAddress.new(path) - end - end - - it "to_s" do - Socket::UNIXAddress.new("some_path").to_s.should eq("some_path") - end -end - describe UNIXServer do it "raises when path is too long" do with_tempfile("unix_server-too_long-#{("a" * 2048)}.sock") do |path| diff --git a/src/http/server.cr b/src/http/server.cr index df775b43f5f6..d12194d3645b 100644 --- a/src/http/server.cr +++ b/src/http/server.cr @@ -1,4 +1,5 @@ require "socket" +require "uri" require "./server/context" require "./server/handler" require "./server/response" @@ -156,6 +157,21 @@ class HTTP::Server bind_tcp "127.0.0.1", port, reuse_port end + # Creates a `TCPServer` listenting on *address* and adds it as a socket, returning the local address + # and port the server listens on. + # + # ``` + # server = HTTP::Server.new { } + # server.bind_tcp(Socket::IPAddress.new("127.0.0.100", 8080)) # => Socket::IPAddress.new("127.0.0.100", 8080) + # server.bind_tcp(Socket::IPAddress.new("127.0.0.100", 0)) # => Socket::IPAddress.new("127.0.0.100", 35487) + # ``` + # + # If *reuse_port* is `true`, it enables the `SO_REUSEPORT` socket option, + # which allows multiple processes to bind to the same port. + def bind_tcp(address : Socket::IPAddress, reuse_port : Bool = false) : Socket::IPAddress + bind_tcp(address.address, address.port, reuse_port: reuse_port) + end + # Creates a `TCPServer` listening on an unused port and adds it as a socket. # # Returns the `Socket::IPAddress` with the determined port number. @@ -182,8 +198,18 @@ class HTTP::Server server.local_address end + # Creates a `UNIXServer` bound to *address* and adds it as a socket. + # + # ``` + # server = HTTP::Server.new { } + # server.bind_unix(Socket::UNIXAddress.new("/tmp/my-socket.sock")) + # ``` + def bind_unix(address : Socket::UNIXAddress) : Socket::UNIXAddress + bind_unix(address.path) + end + {% unless flag?(:without_openssl) %} - # Creates a `OpenSSL::SSL::Server` and adds it as a socket. + # Creates an `OpenSSL::SSL::Server` and adds it as a socket. # # The SSL server wraps a `TCPServer` listenting on `host:port`. # @@ -203,7 +229,7 @@ class HTTP::Server tcp_server.local_address end - # Creates a `OpenSSL::SSL::Server` and adds it as a socket. + # Creates an `OpenSSL::SSL::Server` and adds it as a socket. # # The SSL server wraps a `TCPServer` listenting on an unused port on *host*. # @@ -217,8 +243,54 @@ class HTTP::Server def bind_ssl(host : String, context : OpenSSL::SSL::Context::Server) : Socket::IPAddress bind_ssl(host, 0, context) end + + # Creates an `OpenSSL::SSL::Server` and adds it as a socket. + # + # The SSL server wraps a `TCPServer` listenting on an unused port on *host*. + # + # ``` + # server = HTTP::Server.new { } + # context = OpenSSL::SSL::Context::Server.new + # context.certificate_chain = "openssl.crt" + # context.private_key = "openssl.key" + # address = server.bind_ssl Socket::IPAddress.new("127.0.0.1", 8000), context + # ``` + def bind_ssl(address : Socket::IPAddress, context : OpenSSL::SSL::Context::Server) : Socket::IPAddress + bind_ssl(address.address, address.port, context) + end {% end %} + # Parses a socket configuration from *uri* and adds it to this server. + # Returns the effective address it is bound to. + # + # ``` + # server = HTTP::Server.new { } + # server.bind("tcp://localhost:8080") # => Socket::IPAddress.new("localhost, 8080") + def bind(uri : String) : Socket::Address + bind(URI.parse(uri)) + end + + # :ditto: + def bind(uri : URI) : Socket::Address + case uri.scheme + when "tcp" + bind_tcp(Socket::IPAddress.parse(uri)) + when "unix" + bind_unix(Socket::UNIXAddress.parse(uri)) + when "ssl" + address = Socket::IPAddress.parse(uri) + {% unless flag?(:without_openssl) %} + context = OpenSSL::SSL::Context::Server.from_hash(HTTP::Params.parse(uri.query || "")) + + bind_ssl(address, context) + {% else %} + raise ArgumentError.new "Unsupported socket type: ssl (program was compiled without openssl support)" + {% end %} + else + raise ArgumentError.new "Unsupported socket type: #{uri.scheme}" + end + end + # Adds a `Socket::Server` *socket* to this server. def bind(socket : Socket::Server) : Nil raise "Can't add socket to running server" if listening? diff --git a/src/openssl/ssl/context.cr b/src/openssl/ssl/context.cr index c10b13620891..294c9c717a7f 100644 --- a/src/openssl/ssl/context.cr +++ b/src/openssl/ssl/context.cr @@ -90,10 +90,30 @@ abstract class OpenSSL::SSL::Context # # For everything else this uses the defaults of your OpenSSL. # Use this only if undoing the defaults that `new` sets is too much hassle. - def self.insecure(method : LibSSL::SSLMethod = Context.default_method) + def self.insecure(method : LibSSL::SSLMethod = Context.default_method) : self super(method) end + # Configures a client context from a hash-like interface. + # + # ``` + # require "openssl" + # + # context = OpenSSL::SSL::Context::Client.from_hash({"key" => "private.key", "cert" => "certificate.crt", "ca" => "ca.pem"}) + # ``` + # + # Params: + # + # * `key` *(required)*: Path to private key file. See `#private_key=`. + # * `cert` *(required)*: Path to the file containing the public certificate chain. See `#certificate_chain=`. + # * `verify_mode`: Either `peer`, `force-peer`, `none` or empty (default: `peer`). See `verify_mode=`. + # * `ca`: Path to a file containing the CA certificate chain or a directory containing all CA certificates. + # See `#ca_certificates=` and `#ca_certificates_path=`, respectively. + # Required if `verify_mode` is `peer`, `force-peer` or empty. + def self.from_hash(params) : self + super(params) + end + # Wraps the original certificate verification to also validate the # hostname against the certificate configured Subject Alternate # Names or Common Name. @@ -148,9 +168,29 @@ abstract class OpenSSL::SSL::Context # # For everything else this uses the defaults of your OpenSSL. # Use this only if undoing the defaults that `new` sets is too much hassle. - def self.insecure(method : LibSSL::SSLMethod = Context.default_method) + def self.insecure(method : LibSSL::SSLMethod = Context.default_method) : self super(method) end + + # Configures a server from a hash-like interface. + # + # ``` + # require "openssl" + # + # context = OpenSSL::SSL::Context::Client.from_hash({"key" => "private.key", "cert" => "certificate.crt", "ca" => "ca.pem"}) + # ``` + # + # Params: + # + # * `key` *(required)*: Path to private key file. See `#private_key=`. + # * `cert` *(required)*: Path to the file containing the public certificate chain. See `#certificate_chain=`. + # * `verify_mode`: Either `peer`, `force-peer`, `none` or empty (default: `none`). See `verify_mode=`. + # * `ca`: Path to a file containing the CA certificate chain or a directory containing all CA certificates. + # See `#ca_certificates=` and `#ca_certificates_path=`, respectively. + # Required if `verify_mode` is `peer` or `force-peer`. + def self.from_hash(params) : self + super(params) + end end protected def initialize(method : LibSSL::SSLMethod) @@ -370,4 +410,44 @@ abstract class OpenSSL::SSL::Context def to_unsafe @handle end + + private def self.from_hash(params) + context = new + if key = params["key"]? + context.private_key = key + else + raise ArgumentError.new("Invalid SSL context: missing private key ('key=')") + end + + if cert = params["cert"]? + context.certificate_chain = cert + else + raise ArgumentError.new("Invalid SSL context: missing certificate ('cert=')") + end + + case verify_mode = params["verify_mode"]? + when "peer" + context.verify_mode = OpenSSL::SSL::VerifyMode::PEER + when "force-peer" + context.verify_mode = OpenSSL::SSL::VerifyMode::FAIL_IF_NO_PEER_CERT + when "none" + context.verify_mode = OpenSSL::SSL::VerifyMode::NONE + when nil + # use default + else + raise ArgumentError.new("Invalid SSL context: unknown verify mode #{verify_mode.inspect}") + end + + if ca = params["ca"]? + if File.directory?(ca) + context.ca_certificates_path = ca + else + context.ca_certificates = ca + end + elsif context.verify_mode.peer? || context.verify_mode.fail_if_no_peer_cert? + raise ArgumentError.new("Invalid SSL context: missing CA certificate ('ca=')") + end + + context + end end diff --git a/src/socket/address.cr b/src/socket/address.cr index 94e7b644a6f7..aa77c4bda28b 100644 --- a/src/socket/address.cr +++ b/src/socket/address.cr @@ -1,3 +1,6 @@ +require "socket" +require "uri" + class Socket abstract struct Address getter family : Family @@ -18,6 +21,31 @@ class Socket end end + # Parses a `Socket::Address` from an URI. + # + # Supported formats: + # * `ip://:` + # * `tcp://:` + # * `udp://:` + # * `unix://` + # + # See `IPAddress.parse` and `UNIXAddress.parse` for details. + def self.parse(uri : URI) + case uri.scheme + when "ip", "tcp", "udp" + IPAddress.parse uri + when "unix" + UNIXAddress.parse uri + else + raise Socket::Error.new "Unsupported address type: #{uri.scheme}" + end + end + + # :ditto: + def self.parse(uri : String) + parse URI.parse(uri) + end + def initialize(@family : Family, @size : Int32) end @@ -75,6 +103,36 @@ class Socket end end + # Parses a `Socket::IPAddress` from an URI. + # + # It expects the URI to include `://:` where `scheme` as + # well as any additional URI components (such as `path` or `query`) are ignored. + # + # `host` must be an IP address (v4 or v6), otherwise `Socket::Error` will be + # raised. Domain names will not be resolved. + # + # ``` + # Socket::IPAddress.parse("tcp://127.0.0.1:8080") # => Socket::IPAddress.new("127.0.0.1", 8080) + # Socket::IPAddress.parse("udp://[::1]:8080") # => Socket::IPAddress.new("::1", 8080) + # ``` + def self.parse(uri : URI) : IPAddress + host = uri.host || raise Socket::Error.new("Invalid IP address: missing host") + + port = uri.port || raise Socket::Error.new("Invalid IP address: missing port") + + # remove ipv6 brackets + if host.starts_with?('[') && host.ends_with?(']') + host = host.byte_slice(1, host.bytesize - 2) + end + + new(host, port) + end + + # :ditto: + def self.parse(uri : String) + parse URI.parse(uri) + end + protected def initialize(sockaddr : LibC::SockaddrIn6*, @size) @family = Family::INET6 @addr6 = sockaddr.value.sin6_addr @@ -202,6 +260,43 @@ class Socket new(sockaddr.as(LibC::SockaddrUn*), addrlen.to_i) end + # Parses a `Socket::UNIXAddress` from an URI. + # + # It expects the URI to include `://` where `scheme` as well + # as any additional URI components (such as `fragment` or `query`) are ignored. + # + # If `host` is not empty, it will be prepended to `path` to form a relative + # path. + # + # ``` + # Socket::UNIXAddress.parse("unix:///foo.sock") # => Socket::UNIXAddress.new("/foo.sock") + # Socket::UNIXAddress.parse("unix://foo.sock") # => Socket::UNIXAddress.new("foo.sock") + # ``` + def self.parse(uri : URI) : UNIXAddress + unix_path = String.build do |io| + io << uri.host + if port = uri.port + io << ':' << port + end + if (path = uri.path) && !path.empty? + io << path + end + end + + raise Socket::Error.new("Invalid UNIX address: missing path") if unix_path.empty? + + {% if flag?(:unix) %} + UNIXAddress.new(unix_path) + {% else %} + raise NotImplementedError.new("UNIX address not available") + {% end %} + end + + # :ditto: + def self.parse(uri : String) + parse URI.parse(uri) + end + protected def initialize(sockaddr : LibC::SockaddrUn*, size) @family = Family::UNIX @path = String.new(sockaddr.value.sun_path.to_unsafe)