PageRenderTime 63ms CodeModel.GetById 35ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/geolocal/provider/base.rb

https://gitlab.com/billhibadb/geolocal
Ruby | 193 lines | 151 code | 37 blank | 5 comment | 18 complexity | 4262551d4d2a756204c3846419273c1c MD5 | raw file
  1. require 'ipaddr'
  2. require 'fileutils'
  3. module Geolocal
  4. module Provider
  5. class Base
  6. def initialize params={}
  7. @config = params.merge(Geolocal.configuration.to_hash)
  8. end
  9. def config
  10. @config
  11. end
  12. def download
  13. download_files
  14. end
  15. def add_to_results results, name, lostr, histr
  16. loaddr = IPAddr.new(lostr)
  17. hiaddr = IPAddr.new(histr)
  18. lofam = loaddr.family
  19. hifam = hiaddr.family
  20. if lofam != hifam
  21. raise "#{lostr} and #{histr} must be in the same address family"
  22. end
  23. loval = loaddr.to_i
  24. hival = hiaddr.to_i
  25. if loval > hival
  26. raise "range supplied in the wrong order: #{lostr}..#{histr}"
  27. end
  28. if lofam == Socket::AF_INET
  29. namefam = name.upcase + 'v4' if config[:ipv4]
  30. elsif lofam == Socket::AF_INET6
  31. namefam = name.upcase + 'v6' if config[:ipv6]
  32. else
  33. raise "unknown address family #{lofam} for #{lostr}"
  34. end
  35. if namefam
  36. results[namefam] << (loaddr.to_i..hiaddr.to_i)
  37. end
  38. namefam
  39. end
  40. def countries
  41. @countries ||= config[:countries].sort_by { |k, v| k.to_s }.reduce({}) { |a, (k, v)|
  42. k = k.to_s.gsub(/[ -]/, '_')
  43. raise "invalid identifier: '#{k}'" if k =~ /^[^A-Za-z_]|[^A-Za-z0-9_]|^\s*$/
  44. a.merge! k.to_s.downcase => Array(v).map { |c| c.to_s.upcase }.sort.to_set
  45. }
  46. end
  47. def update
  48. results = countries.keys.reduce({}) { |a, k|
  49. a.merge! k.upcase+'v4' => [] if config[:ipv4]
  50. a.merge! k.upcase+'v6' => [] if config[:ipv6]
  51. a
  52. }
  53. read_ranges(countries) { |*args| add_to_results(results, *args) }
  54. FileUtils.mkdir_p File.dirname(config[:file])
  55. File.open(config[:file], 'w') do |file|
  56. output(file, results)
  57. end
  58. status "done, result in #{config[:file]}\n"
  59. end
  60. def up_to_date?(file, expiry)
  61. return false unless File.exist?(file)
  62. diff = Time.now - File.mtime(file)
  63. if diff < expiry
  64. status "using #{file} since it's #{diff.round} seconds old\n"
  65. return true
  66. end
  67. end
  68. def output file, results
  69. modname = config[:module]
  70. write_header file, modname
  71. countries.keys.each do |name|
  72. v4mod = config[:ipv4] ? name.to_s.upcase + 'v4' : 'nil'
  73. v6mod = config[:ipv6] ? name.to_s.upcase + 'v6' : 'nil'
  74. write_method file, name, v4mod, v6mod
  75. end
  76. file.write "end\n\n"
  77. status " writing "
  78. results.each do |name, ranges|
  79. coalesced = coalesce_ranges(ranges)
  80. status "#{name}-#{ranges.length - coalesced.length} "
  81. write_ranges file, modname, name, coalesced
  82. end
  83. status "\n"
  84. end
  85. def coalesce_ranges ranges
  86. ranges = ranges.sort_by { |r| r.min }
  87. uniques = []
  88. lastr = ranges.shift
  89. uniques << lastr if lastr
  90. ranges.each do |thisr|
  91. if lastr.last >= thisr.first - 1
  92. lastr = lastr.first..[thisr.last, lastr.last].max
  93. uniques[-1] = lastr
  94. else
  95. lastr = thisr
  96. uniques << lastr
  97. end
  98. end
  99. uniques
  100. end
  101. def write_header file, modname
  102. file.write <<EOL
  103. # This file is autogenerated by the Geolocal gem
  104. require 'ipaddr'
  105. module #{modname}
  106. def self.search address, family, v4module, v6module
  107. address = IPAddr.new(address) if address.is_a?(String)
  108. family = address.family unless family
  109. num = address.to_i
  110. case family
  111. when Socket::AF_INET then mod = v4module
  112. when Socket::AF_INET6 then mod = v6module
  113. else raise "Unknown family \#{family} for address \#{address}"
  114. end
  115. return false unless mod # if we didn't compile in ipv6, assume nothing's local
  116. true if mod.bsearch { |range| num > range.max ? 1 : num < range.min ? -1 : 0 }
  117. end
  118. EOL
  119. end
  120. def write_method file, name, v4mod, v6mod
  121. file.write <<EOL
  122. def self.in_#{name}? address, family=nil
  123. search address, family, #{v4mod}, #{v6mod}
  124. end
  125. EOL
  126. end
  127. def write_ranges file, modname, name, ranges
  128. file.write <<EOL
  129. #{modname}::#{name} = [
  130. #{ranges.join(",\n")}
  131. ]
  132. EOL
  133. end
  134. end
  135. end
  136. end
  137. # random utilities
  138. module Geolocal
  139. module Provider
  140. class Base
  141. # returns elapsed time of block in seconds
  142. def time_block
  143. start = Time.now
  144. yield
  145. stop = Time.now
  146. stop - start + 0.0000001 # fudge to prevent division by zero
  147. end
  148. def status *args
  149. unless config[:quiet]
  150. Kernel.print(*args)
  151. $stdout.flush unless args.last.end_with?("\n")
  152. end
  153. end
  154. end
  155. end
  156. end