# frozen_string_literal: truerequire"socket"moduleBundlerclassSettings# Class used to build the mirror set and then find a mirror for a given URI## @param prober [Prober object, nil] by default a TCPSocketProbe, this object# will be used to probe the mirror address to validate that the mirror replies.classMirrorsdefinitialize(prober=nil)@all=Mirror.new@prober=prober||TCPSocketProbe.new@mirrors={}end# Returns a mirror for the given uri.## Depending on the uri having a valid mirror or not, it may be a# mirror that points to the provided urideffor(uri)if@all.validate!(@prober).valid?@allelsefetch_valid_mirror_for(Settings.normalize_uri(uri))endenddefeach@mirrors.eachdo|k,v|yieldk,v.uri.to_sendenddefparse(key,value)config=MirrorConfig.new(key,value)mirror=ifconfig.all?@allelse@mirrors[config.uri]||=Mirror.newendconfig.update_mirror(mirror)endprivatedeffetch_valid_mirror_for(uri)downcased=uri.to_s.downcasemirror=@mirrors[downcased]||@mirrors[Gem::URI(downcased).host]||Mirror.new(uri)mirror.validate!(@prober)mirror=Mirror.new(uri)unlessmirror.valid?mirrorendend# A mirror## Contains both the uri that should be used as a mirror and the# fallback timeout which will be used for probing if the mirror# replies on time or not.classMirrorDEFAULT_FALLBACK_TIMEOUT=0.1attr_reader:uri,:fallback_timeoutdefinitialize(uri=nil,fallback_timeout=0)self.uri=uriself.fallback_timeout=fallback_timeout@valid=nilenddefuri=(uri)@uri=ifuri.nil?nilelseGem::URI(uri.to_s)end@valid=nilenddeffallback_timeout=(timeout)casetimeoutwhentrue,"true"@fallback_timeout=DEFAULT_FALLBACK_TIMEOUTwhenfalse,"false"@fallback_timeout=0else@fallback_timeout=timeout.to_iend@valid=nilenddef==(other)!other.nil?&&uri==other.uri&&fallback_timeout==other.fallback_timeoutenddefvalid?returnfalseif@uri.nil?return@validunless@valid.nil?falseenddefvalidate!(probe=nil)@valid=falseifuri.nil?if@valid.nil?@valid=fallback_timeout==0||(probe||TCPSocketProbe.new).replies?(self)endselfendend# Class used to parse one configuration line## Gets the configuration line and the value.# This object provides a `update_mirror` method# used to setup the given mirror value.classMirrorConfigattr_accessor:uri,:valuedefinitialize(config_line,value)uri,fallback=config_line.match(%r{\Amirror\.(all|.+?)(\.fallback_timeout)?\/?\z}).captures@fallback=!fallback.nil?@all=falseifuri=="all"@all=trueelse@uri=Gem::URI(uri).absolute??Settings.normalize_uri(uri):uriend@value=valueenddefall?@allenddefupdate_mirror(mirror)if@fallbackmirror.fallback_timeout=@valueelsemirror.uri=Settings.normalize_uri(@value)endendend# Class used for probing TCP availability for a given mirror.classTCPSocketProbedefreplies?(mirror)MirrorSockets.new(mirror).any?do|socket,address,timeout|socket.connect_nonblock(address)rescueErrno::EINPROGRESSwait_for_writtable_socket(socket,address,timeout)rescueRuntimeError# Connection failed somehow, againfalseendendprivatedefwait_for_writtable_socket(socket,address,timeout)ifIO.select(nil,[socket],nil,timeout)probe_writtable_socket(socket,address)else# TCP Handshake timed out, or there is something dropping packetsfalseendenddefprobe_writtable_socket(socket,address)socket.connect_nonblock(address)rescueErrno::EISCONNtruerescueStandardError# Connection failedfalseendendend# Class used to build the list of sockets that correspond to# a given mirror.## One mirror may correspond to many different addresses, both# because of it having many dns entries or because# the network interface is both ipv4 and ipv5classMirrorSocketsdefinitialize(mirror)@timeout=mirror.fallback_timeout@addresses=Socket.getaddrinfo(mirror.uri.host,mirror.uri.port).mapdo|address|SocketAddress.new(address[0],address[3],address[1])endenddefany?@addresses.any?do|address|socket=Socket.new(Socket.const_get(address.type),Socket::SOCK_STREAM,0)socket.setsockopt(Socket::IPPROTO_TCP,Socket::TCP_NODELAY,1)value=yieldsocket,address.to_socket_address,@timeoutsocket.closeunlesssocket.closed?valueendendend# Socket address builder.## Given a socket type, a host and a port,# provides a method to build sockaddr stringclassSocketAddressattr_reader:type,:host,:portdefinitialize(type,host,port)@type=type@host=host@port=portenddefto_socket_addressSocket.pack_sockaddr_in(@port,@host)endendend