From 4b46313e19f06f2739ee522eb4fbfe8dbcbee630 Mon Sep 17 00:00:00 2001 From: Samantaz Fox Date: Tue, 3 Aug 2021 23:44:47 +0200 Subject: [PATCH] Fix crystal overrides (#2295) * Move Crystal stdlib classes overrides to a separate file * Document known crystal overrides * Update crystal overrides for HTTP::Client socket * Update shard.yml to restrict crystal versions * Fix compilation error in Crystal 1.1.x (See https://github.com/crystal-lang/crystal/issues/10965 for more details about this issue). --- shard.yml | 2 +- .../helpers/crystal_class_overrides.cr | 70 +++++++++++++++++++ src/invidious/helpers/helpers.cr | 50 ------------- 3 files changed, 71 insertions(+), 51 deletions(-) create mode 100644 src/invidious/helpers/crystal_class_overrides.cr diff --git a/shard.yml b/shard.yml index fca1ce028..2df4909c6 100644 --- a/shard.yml +++ b/shard.yml @@ -26,6 +26,6 @@ dependencies: github: iv-org/lsquic.cr version: ~> 2.18.1-2 -crystal: 1.0.0 +crystal: ">= 1.0.0, < 2.0.0" license: AGPLv3 diff --git a/src/invidious/helpers/crystal_class_overrides.cr b/src/invidious/helpers/crystal_class_overrides.cr new file mode 100644 index 000000000..28afd5b5a --- /dev/null +++ b/src/invidious/helpers/crystal_class_overrides.cr @@ -0,0 +1,70 @@ +# Override of the TCPSocket and HTTP::Client classes in oder to allow an +# IP family to be selected for domains that resolve to both IPv4 and +# IPv6 addresses. +# +class TCPSocket + def initialize(host : String, port, dns_timeout = nil, connect_timeout = nil, family = Socket::Family::UNSPEC) + Addrinfo.tcp(host, port, timeout: dns_timeout, family: family) do |addrinfo| + super(addrinfo.family, addrinfo.type, addrinfo.protocol) + connect(addrinfo, timeout: connect_timeout) do |error| + close + error + end + end + end +end + +# :ditto: +class HTTP::Client + property family : Socket::Family = Socket::Family::UNSPEC + + private def io + io = @io + return io if io + unless @reconnect + raise "This HTTP::Client cannot be reconnected" + end + + hostname = @host.starts_with?('[') && @host.ends_with?(']') ? @host[1..-2] : @host + io = TCPSocket.new hostname, @port, @dns_timeout, @connect_timeout, @family + io.read_timeout = @read_timeout if @read_timeout + io.write_timeout = @write_timeout if @write_timeout + io.sync = false + + {% if !flag?(:without_openssl) %} + if tls = @tls + tcp_socket = io + begin + io = OpenSSL::SSL::Socket::Client.new(tcp_socket, context: tls, sync_close: true, hostname: @host) + rescue exc + # don't leak the TCP socket when the SSL connection failed + tcp_socket.close + raise exc + end + end + {% end %} + + @io = io + end +end + +# Mute the ClientError exception raised when a connection is flushed. +# This happends when the connection is unexpectedly closed by the client. +# +class HTTP::Server::Response + class Output + private def unbuffered_flush + @io.flush + rescue ex : IO::Error + unbuffered_close + end + end +end + +# TODO: Document this override +# +class PG::ResultSet + def field(index = @column_index) + @fields.not_nil![index] + end +end diff --git a/src/invidious/helpers/helpers.cr b/src/invidious/helpers/helpers.cr index fb7b19e61..99d7f4401 100644 --- a/src/invidious/helpers/helpers.cr +++ b/src/invidious/helpers/helpers.cr @@ -509,12 +509,6 @@ def check_table(db, table_name, struct_type = nil) end end -class PG::ResultSet - def field(index = @column_index) - @fields.not_nil![index] - end -end - def get_column_array(db, table_name) column_array = [] of String db.query("SELECT * FROM #{table_name} LIMIT 0") do |rs| @@ -699,47 +693,3 @@ def proxy_file(response, env) IO.copy response.body_io, env.response end end - -class HTTP::Server::Response - class Output - private def unbuffered_flush - @io.flush - rescue ex : IO::Error - unbuffered_close - end - end -end - -class HTTP::Client - property family : Socket::Family = Socket::Family::UNSPEC - - private def socket - socket = @socket - return socket if socket - - hostname = @host.starts_with?('[') && @host.ends_with?(']') ? @host[1..-2] : @host - socket = TCPSocket.new hostname, @port, @dns_timeout, @connect_timeout, @family - socket.read_timeout = @read_timeout if @read_timeout - socket.sync = false - - {% if !flag?(:without_openssl) %} - if tls = @tls - socket = OpenSSL::SSL::Socket::Client.new(socket, context: tls, sync_close: true, hostname: @host) - end - {% end %} - - @socket = socket - end -end - -class TCPSocket - def initialize(host, port, dns_timeout = nil, connect_timeout = nil, family = Socket::Family::UNSPEC) - Addrinfo.tcp(host, port, timeout: dns_timeout, family: family) do |addrinfo| - super(addrinfo.family, addrinfo.type, addrinfo.protocol) - connect(addrinfo, timeout: connect_timeout) do |error| - close - error - end - end - end -end