require 'socket' # Why doesn't socket work with autoload? autoload :Timeout, 'timeout' autoload :IPAddr, 'ipaddr' autoload :Whois, 'whois' autoload :PublicSuffixService, 'public_suffix_service' class Stella IMAGE_EXT = %w/.bmp .gif .jpg .jpeg .png .ico/ unless defined?(Stella::IMAGE_EXT) # A motley collection of methods that Stella loves to call! module Utils extend self include Socket::Constants ADDR_LOCAL = IPAddr.new("127.0.0.0/8") ADDR_CLASSA = IPAddr.new("10.0.0.0/8") ADDR_CLASSB = IPAddr.new("172.16.0.0/16") ADDR_CLASSC = IPAddr.new("192.168.0.0/24") # See: https://forums.aws.amazon.com/ann.jspa?annID=877 ADDR_EC2_US_EAST = %w{ 216.182.224.0/20 72.44.32.0/19 67.202.0.0/18 75.101.128.0/17 174.129.0.0/16 204.236.192.0/18 184.73.0.0/16 184.72.128.0/17 184.72.64.0/18 50.16.0.0/15 }.collect { |ipr| IPAddr.new(ipr.strip) } ADDR_EC2_US_WEST = %w{ 204.236.128.0/18 184.72.0.0/18 50.18.0.0/18 }.collect { |ipr| IPAddr.new(ipr.strip) } ADDR_EC2_EU_WEST = %w{ 79.125.0.0/17 46.51.128.0/18 46.51.192.0/20 46.137.0.0/17 }.collect { |ipr| IPAddr.new(ipr.strip) } ADDR_EC2_AP_EAST = %w{ 175.41.128.0/18 122.248.192.0/18 }.collect { |ipr| IPAddr.new(ipr.strip) } def image_ext?(name) IMAGE_EXT.include?(File.extname(name.downcase)) end def image?(s) return false if s.nil? (bmp?(s) || jpg?(s) || png?(s) || gif?(s) || ico?(s)) end # Checks if the file has more than 30% non-ASCII characters. # NOTE: how to determine the difference between non-latin and binary? def binary?(s) return false if s.nil? #puts "TODO: fix encoding issue in 1.9" s = s.to_s.split(//) rescue [] unless Array === s s.slice!(0, 4096) # limit to a typcial blksize ((s.size - s.grep(" ".."~").size) / s.size.to_f) > 0.30 end # Based on ptools by Daniel J. Berger # http://raa.ruby-lang.org/project/ptools/ def bmp?(a) possible = ['BM6', 'BM' << 226.chr] possible.member? a.slice(0, 3) end # Based on ptools by Daniel J. Berger # http://raa.ruby-lang.org/project/ptools/ def jpg?(a) a.slice(0, 10) == "\377\330\377\340\000\020JFIF" end # Based on ptools by Daniel J. Berger # http://raa.ruby-lang.org/project/ptools/ def png?(a) a.slice(0, 4) == "\211PNG" end def ico?(a) a.slice(0, 3) == [0.chr, 0.chr, 1.chr].join end # Based on ptools by Daniel J. Berger # http://raa.ruby-lang.org/project/ptools/ def gif?(a) ['GIF89a', 'GIF97a'].include?(a.slice(0, 6)) end def domain(host) begin PublicSuffixService.parse host rescue PublicSuffixService::DomainInvalid => ex Stella.ld ex.message nil rescue => ex Stella.li "Error determining domain for #{host}: #{ex.message} (#{ex.class})" Stella.ld ex.backtrace nil end end def whois(host_or_ip) begin raw = Whois.whois(host_or_ip) info = raw.content.split("\n").select { |line| line !~ /\A[\#\%]/ && !line.empty? } info.join("\n") rescue => ex Stella.ld "Error fetching whois for #{host_or_ip}: #{ex.message}" Stella.ld ex.backtrace end end # Returns an Array of ip addresses or nil def ipaddr(host) require 'resolv' host = host.host if host.kind_of?(URI) begin resolv = Resolv::DNS.new # { :nameserver => [] } resolv.getaddresses(host).collect { |addr| addr.to_s } rescue => ex Stella.ld "Error getting ip address for #{host}: #{ex.message} (#{ex.class})" Stella.ld ex.backtrace nil end end # http://www.opensource.apple.com/source/ruby/ruby-4/ruby/lib/resolv.rb # * Resolv::DNS::Resource::IN::ANY # * Resolv::DNS::Resource::IN::NS # * Resolv::DNS::Resource::IN::CNAME # * Resolv::DNS::Resource::IN::SOA # * Resolv::DNS::Resource::IN::HINFO # * Resolv::DNS::Resource::IN::MINFO # * Resolv::DNS::Resource::IN::MX # * Resolv::DNS::Resource::IN::TXT # * Resolv::DNS::Resource::IN::ANY # * Resolv::DNS::Resource::IN::A # * Resolv::DNS::Resource::IN::WKS # * Resolv::DNS::Resource::IN::PTR # * Resolv::DNS::Resource::IN::AAAA # Returns a cname or nil def cname(host) require 'resolv' host = host.host if host.kind_of?(URI) begin resolv = Resolv::DNS.new # { :nameserver => [] } resolv.getresources(host, Resolv::DNS::Resource::IN::CNAME).collect { |cname| cname.name.to_s }.first rescue => ex Stella.ld "Error getting CNAME for #{host}: #{ex.message} (#{ex.class})" Stella.ld ex.backtrace nil end end def local_ipaddr?(addr) addr = IPAddr.new(addr) if String === addr ADDR_LOCAL.include?(addr) end def private_ipaddr?(addr) addr = IPAddr.new(addr) if String === addr ADDR_CLASSA.include?(addr) || ADDR_CLASSB.include?(addr) || ADDR_CLASSC.include?(addr) end def ec2_cname_to_ipaddr(cname) return unless cname =~ /\Aec2-(\d+)-(\d+)-(\d+)-(\d+)\./ [$1, $2, $3, $4].join '.' end def ec2_ipaddr?(addr) ec2_us_east_ipaddr?(addr) || ec2_us_west_ipaddr?(addr) || ec2_eu_west_ipaddr?(addr) || ec2_ap_east_ipaddr?(addr) end def ec2_us_east_ipaddr?(addr) ADDR_EC2_US_EAST.each { |ipclass| return true if ipclass.include?(addr) } false end def ec2_us_west_ipaddr?(addr) ADDR_EC2_US_WEST.each { |ipclass| return true if ipclass.include?(addr) } false end def ec2_eu_west_ipaddr?(addr) ADDR_EC2_EU_WEST.each { |ipclass| return true if ipclass.include?(addr) } false end def ec2_ap_east_ipaddr?(addr) ADDR_EC2_AP_EAST.each { |ipclass| return true if ipclass.include?(addr) } false end def hosted_at_ec2?(hostname, region=nil) meth = region.nil? ? :ec2_ipaddr? : :"ec2_#{region}_ipaddr?" cname = Stella::Utils.cname(hostname) if !cname.nil? && cname.first addr = Stella::Utils.ec2_cname_to_ipaddr(cname.first) else addresses = Stella::Utils.ipaddr(hostname) || [] addr = addresses.first end addr.nil? ? false : Stella::Utils.send(meth, addr) end def valid_hostname?(uri) begin if String === uri uri = "http://#{uri}" unless uri.match(/^https?:\/\//) uri = URI.parse(uri) end hostname = Socket.gethostbyname(uri.host).first true rescue SocketError => ex Stella.ld "#{uri.host}: #{ex.message}" false end end # Return the external IP address (the one seen by the internet) def external_ip_address ip = nil begin %w{solutious.heroku.com/ip}.each do |sponge| ipstr = Net::HTTP.get(URI.parse("http://#{sponge}")) || '' ip = /([0-9]{1,3}\.){3}[0-9]{1,3}/.match(ipstr).to_s break if ip && !ip.empty? end rescue SocketError, Errno::ETIMEDOUT => ex Stella.le "Connection Error. Check your internets!" end ip end # Return the local IP address which receives external traffic # from: http://coderrr.wordpress.com/2008/05/28/get-your-local-ip-address/ # NOTE: This does not open a connection to the IP address. def internal_ip_address # turn off reverse DNS resolution temporarily orig, Socket.do_not_reverse_lookup = Socket.do_not_reverse_lookup, true ip = UDPSocket.open {|s| s.connect('75.101.137.7', 1); s.addr.last } # Solutious IP ip ensure Socket.do_not_reverse_lookup = orig end # require a glob of files. # * +path+ is a list of path elements which is sent to File.join # and then to Dir.glob. The list of files found are sent to require. # Nothing is returned but LoadError exceptions are caught. The message # is printed to STDERR and the program exits with 7. def require_glob(*path) begin Dir.glob(File.join(*path.flatten)).each do |path| require path end rescue LoadError => ex puts "Error: #{ex.message}" exit 7 end end # require a library from the vendor directory. # The vendor directory should be organized such # that +name+ and +version+ can be used to create # the path to the library. # # e.g. # # vendor/httpclient-2.1.5.2/httpclient # def require_vendor(name, version) $:.unshift File.join(STELLA_LIB_HOME, '..', 'vendor', "#{name}-#{version}") require name end # Same as require_vendor, but uses autoload instead. def autoload_vendor(mod, name, version) autoload mod, File.join(STELLA_LIB_HOME, '..', 'vendor', "#{name}-#{version}", name) end # Checks whether something is listening to a socket. # * +host+ A hostname # * +port+ The port to check # * +wait+ The number of seconds to wait for before timing out. # # Returns true if +host+ allows a socket connection on +port+. # Returns false if one of the following exceptions is raised: # Errno::EAFNOSUPPORT, Errno::ECONNREFUSED, SocketError, Timeout::Error # def service_available?(host, port, wait=3) if Stella.sysinfo.vm == :java begin iadd = Java::InetSocketAddress.new host, port socket = Java::Socket.new socket.connect iadd, wait * 1000 # milliseconds success = !socket.isClosed && socket.isConnected rescue NativeException => ex puts ex.message, ex.backtrace if Stella.debug? false end else begin status = Timeout::timeout(wait) do socket = Socket.new( AF_INET, SOCK_STREAM, 0 ) sockaddr = Socket.pack_sockaddr_in( port, host ) socket.connect( sockaddr ) end true rescue Errno::EAFNOSUPPORT, Errno::ECONNREFUSED, SocketError, Timeout::Error => ex puts ex.class, ex.message, ex.backtrace if Stella.debug? false end end end # A basic file writer def write_to_file(filename, content, mode, chmod=0600) mode = (mode == :append) ? 'a' : 'w' f = File.open(filename,mode) f.puts content f.close return unless Stella.sysinfo.os == :unix raise "Provided chmod is not a Fixnum (#{chmod})" unless chmod.is_a?(Fixnum) File.chmod(chmod, filename) end # # Generates a string of random alphanumeric characters. # * +len+ is the length, an Integer. Default: 8 # * +safe+ in safe-mode, ambiguous characters are removed (default: true): # i l o 1 0 def strand( len=8, safe=true ) chars = ("a".."z").to_a + ("0".."9").to_a chars.delete_if { |v| %w(i l o 1 0).member?(v) } if safe str = "" 1.upto(len) { |i| str << chars[rand(chars.size-1)] } str end # Returns +str+ with the leading indentation removed. # Stolen from http://github.com/mynyml/unindent/ because it was better. def noindent(str) indent = str.split($/).each {|line| !line.strip.empty? }.map {|line| line.index(/[^\s]/) }.compact.min str.gsub(/^[[:blank:]]{#{indent}}/, '') end IPAddr.new("127.0.0.0/8") end end