resolv 0.7.1 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c4c4e60f6c23abc6f116dfbea5e531d20f99eb85025e4682d3d85f580e17d09d
4
- data.tar.gz: e609b5a2a67f67b5d7c616a2a3340d5ab3af305a29f66ab800785d62466ab883
3
+ metadata.gz: '017129811e9d2a2c49065d8f95e96f73acba713f6e1e2ee1cd65abb448bea1ce'
4
+ data.tar.gz: 9fe320e19d45e74055b648326efc8bc975b9cbc5aad2a4cd47e1b3660fb81f5e
5
5
  SHA512:
6
- metadata.gz: 3eab1f21df971bd56cd9e23429fdef5da84a502c6d441dcf4cb3ff06fbf2c5222c7cd72b6d17a466d46b956bdeeeecfde0bc0254d78c37d73d1fd1c915bc0912
7
- data.tar.gz: e81e097c7fc424ba72f2f84a07d933cb7bfb001f019ba2babb440c2a94e5aed2f38df688934cd0a35a5ed583ae73969b56ea7e6a92f1e8cdf4875a210d331003
6
+ metadata.gz: 9632f2e80d4ce1d581f3271b70913e2acef6af77a0289539d0954b191382234d6d53fa190f79bd27468724e917b4ba68fb27e2c8e247ce89f0cabccec1c956a5
7
+ data.tar.gz: 59623cf40d63ab667ce5a39301e2eff5186d8c98306cec5c41fd720aad5b74bca7fcfce877ea44b54de297809d41c4822d7bdd5603964c4e772544998a6a5ef7
@@ -0,0 +1,3 @@
1
+ # AUTOGENERATED DEPENDENCIES START
2
+ resolv.o: resolv.c
3
+ # AUTOGENERATED DEPENDENCIES END
@@ -6,8 +6,9 @@
6
6
 
7
7
  require 'win32/resolv.so'
8
8
 
9
+ # Generic namespace for Windows platform-specific features.
9
10
  module Win32
10
- module Resolv
11
+ module Resolv # :nodoc:
11
12
  # Error at Win32 API
12
13
  class Error < StandardError
13
14
  # +code+ Win32 Error code
@@ -53,6 +53,7 @@ wchar_to_utf8(const WCHAR *w, int n)
53
53
  return str;
54
54
  }
55
55
 
56
+ /* :nodoc: */
56
57
  static VALUE
57
58
  get_dns_server_list(VALUE self)
58
59
  {
@@ -81,7 +82,6 @@ get_dns_server_list(VALUE self)
81
82
  return nameservers;
82
83
  }
83
84
 
84
-
85
85
  static const WCHAR TCPIP_Params[] = L"SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters";
86
86
 
87
87
  static void
@@ -116,6 +116,7 @@ reg_open_key(VALUE klass, HKEY hkey, const WCHAR *wname)
116
116
  return rb_ensure(rb_yield, k, hkey_close, k);
117
117
  }
118
118
 
119
+ /* :nodoc: */
119
120
  static VALUE
120
121
  tcpip_params_open(VALUE klass)
121
122
  {
data/lib/resolv.rb CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  require 'socket'
4
4
  require 'timeout'
5
- require 'io/wait'
5
+ require 'io/wait' if RUBY_VERSION < '3.2'
6
6
  require 'securerandom'
7
7
  require 'rbconfig'
8
8
 
@@ -35,7 +35,7 @@ require 'rbconfig'
35
35
  class Resolv
36
36
 
37
37
  # The version string
38
- VERSION = "0.7.1"
38
+ VERSION = "0.8.0"
39
39
 
40
40
  ##
41
41
  # Looks up the first IP address for +name+.
@@ -562,7 +562,19 @@ class Resolv
562
562
  next if !sender
563
563
  senders[[candidate, requester, nameserver, port]] = sender
564
564
  end
565
- reply, reply_name = requester.request(sender, tout)
565
+ begin
566
+ reply, reply_name = requester.request(sender, tout)
567
+ rescue ResolvTimeout
568
+ # Giving up part way through a frame loses stream sync, and a peer
569
+ # seen going away leaves the socket dead. Either way the requester
570
+ # says so, and the next attempt has to open a fresh connection. A
571
+ # timeout with the stream still on a frame boundary keeps it.
572
+ unless requester.reusable?
573
+ requesters.delete([nameserver, port])
574
+ requester.close
575
+ end
576
+ raise
577
+ end
566
578
  case reply.rcode
567
579
  when RCode::NoError
568
580
  if reply.tc == 1 and not Requester::TCP === requester
@@ -698,13 +710,27 @@ class Resolv
698
710
  @socks = nil
699
711
  end
700
712
 
713
+ # Whether another request may be sent over the same transport. Only a
714
+ # stream transport can end up in a state that rules this out.
715
+ def reusable?
716
+ true
717
+ end
718
+
719
+ # A request could not be written. Only a stream transport can be left
720
+ # unusable by that; see #reusable?.
721
+ def send_failed
722
+ end
723
+
701
724
  def request(sender, tout)
702
725
  start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
703
726
  timelimit = start + tout
704
727
  begin
705
728
  sender.send
706
729
  rescue Errno::EHOSTUNREACH, # multi-homed IPv6 may generate this
707
- Errno::ENETUNREACH
730
+ Errno::ENETUNREACH,
731
+ Errno::EPIPE, # a peer that went away between requests
732
+ Errno::ECONNRESET # the same, as Windows reports it
733
+ send_failed
708
734
  raise ResolvTimeout
709
735
  end
710
736
  while true
@@ -724,7 +750,7 @@ class Resolv
724
750
  raise ResolvTimeout
725
751
  end
726
752
  begin
727
- reply, from = recv_reply(select_result[0])
753
+ reply, from = recv_reply(select_result[0], timelimit)
728
754
  rescue Errno::ECONNREFUSED, # GNU/Linux, FreeBSD
729
755
  Errno::ECONNRESET, # Windows
730
756
  EOFError
@@ -801,7 +827,7 @@ class Resolv
801
827
  self
802
828
  end
803
829
 
804
- def recv_reply(readable_socks)
830
+ def recv_reply(readable_socks, timelimit = nil)
805
831
  lazy_initialize
806
832
  reply, from = readable_socks[0].recvfrom(UDPSize)
807
833
  return reply, [from[3],from[1]]
@@ -860,20 +886,37 @@ class Resolv
860
886
  @mutex.synchronize {
861
887
  next if @initialized
862
888
  @initialized = true
863
- is_ipv6 = @host.index(':')
864
- sock = UDPSocket.new(is_ipv6 ? Socket::AF_INET6 : Socket::AF_INET)
865
- @socks = [sock]
866
- sock.do_not_reverse_lookup = true
867
- DNS.bind_random_port(sock, is_ipv6 ? "::" : "0.0.0.0")
868
- sock.connect(@host, @port)
889
+ connect_socket
869
890
  }
870
891
  self
871
892
  end
872
893
 
873
- def recv_reply(readable_socks)
894
+ # The socket to talk to the nameserver over, opening one if there is
895
+ # none yet. #recv_reply may have replaced it since a sender was
896
+ # created, so senders ask for it per request instead of holding on to
897
+ # one.
898
+ def sock
899
+ lazy_initialize
900
+ @socks[0]
901
+ end
902
+
903
+ def recv_reply(readable_socks, timelimit = nil)
874
904
  lazy_initialize
875
905
  reply = readable_socks[0].recv(UDPSize)
876
906
  return reply, nil
907
+ rescue Errno::ECONNREFUSED, Errno::ECONNRESET
908
+ # The kernel reports these from an ICMP message, and a second one for
909
+ # the same pair of endpoints is not always passed on: macOS 26.1 and
910
+ # later deliver every other one. A retry over this socket would then
911
+ # wait out its whole timeout rather than fail at once, so start over
912
+ # from a new source port.
913
+ @mutex.synchronize {
914
+ if @initialized
915
+ @socks&.each(&:close)
916
+ connect_socket
917
+ end
918
+ }
919
+ raise
877
920
  end
878
921
 
879
922
  def sender(msg, data, host=@host, port=@port)
@@ -884,7 +927,7 @@ class Resolv
884
927
  id = DNS.allocate_request_id(@host, @port)
885
928
  request = msg.encode
886
929
  request[0,2] = [id].pack('n')
887
- return @senders[[nil,id]] = Sender.new(request, data, @socks[0])
930
+ return @senders[[nil,id]] = Sender.new(request, data, self)
888
931
  end
889
932
 
890
933
  def close
@@ -899,10 +942,23 @@ class Resolv
899
942
  end
900
943
  end
901
944
 
945
+ private def connect_socket
946
+ is_ipv6 = @host.index(':')
947
+ sock = UDPSocket.new(is_ipv6 ? Socket::AF_INET6 : Socket::AF_INET)
948
+ @socks = [sock]
949
+ sock.do_not_reverse_lookup = true
950
+ DNS.bind_random_port(sock, is_ipv6 ? "::" : "0.0.0.0")
951
+ sock.connect(@host, @port)
952
+ end
953
+
902
954
  class Sender < Requester::Sender # :nodoc:
955
+ def initialize(msg, data, requester)
956
+ super(msg, data, nil)
957
+ @requester = requester
958
+ end
959
+
903
960
  def send
904
- raise "@sock is nil." if @sock.nil?
905
- @sock.send(@msg, 0)
961
+ @requester.sock.send(@msg, 0)
906
962
  end
907
963
  attr_reader :data
908
964
  end
@@ -933,15 +989,32 @@ class Resolv
933
989
  sock = TCPSocket.new(@host, @port)
934
990
  @socks = [sock]
935
991
  @senders = {}
992
+ @reusable = true
993
+ end
994
+
995
+ def reusable?
996
+ @reusable
997
+ end
998
+
999
+ def send_failed
1000
+ @reusable = false
936
1001
  end
937
1002
 
938
- def recv_reply(readable_socks)
939
- len_data = readable_socks[0].read(2)
1003
+ def recv_reply(readable_socks, timelimit = nil)
1004
+ sock = readable_socks[0]
1005
+ len_data = read_exactly(sock, 2, timelimit)
940
1006
  raise EOFError if len_data.nil? || len_data.bytesize != 2
941
1007
  len = len_data.unpack('n')[0]
942
- reply = @socks[0].read(len)
1008
+ reply = read_exactly(sock, len, timelimit)
943
1009
  raise EOFError if reply.nil? || reply.bytesize != len
1010
+ @reusable = true
944
1011
  return reply, nil
1012
+ rescue EOFError, SystemCallError
1013
+ # Whatever the kernel reported, this socket cannot be trusted for
1014
+ # another frame. In practice that is the peer closing or resetting;
1015
+ # the rest is rare enough that erring towards reconnecting is right.
1016
+ @reusable = false
1017
+ raise
945
1018
  end
946
1019
 
947
1020
  def sender(msg, data, host=@host, port=@port)
@@ -963,11 +1036,40 @@ class Resolv
963
1036
  end
964
1037
 
965
1038
  def close
1039
+ @reusable = false
966
1040
  super
967
1041
  @senders.each_key {|from,id|
968
1042
  DNS.free_request_id(@host, @port, id)
969
1043
  }
970
1044
  end
1045
+
1046
+ private
1047
+
1048
+ # Read +len+ bytes, giving up with ResolvTimeout once +timelimit+ (a
1049
+ # CLOCK_MONOTONIC value) has passed. A shorter result means the peer
1050
+ # closed the connection, which the caller turns into an EOFError.
1051
+ # Consuming any byte marks the requester unusable until the whole frame
1052
+ # has been read, since giving up in between loses frame sync. Without a
1053
+ # +timelimit+ the read blocks instead and keeps no such mark; that is
1054
+ # only for a caller still using the one argument form of #recv_reply.
1055
+ def read_exactly(sock, len, timelimit)
1056
+ return sock.read(len) unless timelimit
1057
+ buf = String.new
1058
+ while buf.bytesize < len
1059
+ case chunk = sock.read_nonblock(len - buf.bytesize, exception: false)
1060
+ when :wait_readable
1061
+ remaining = timelimit - Process.clock_gettime(Process::CLOCK_MONOTONIC)
1062
+ raise ResolvTimeout if remaining <= 0
1063
+ sock.wait_readable(remaining) or raise ResolvTimeout
1064
+ when nil
1065
+ break
1066
+ else
1067
+ @reusable = false
1068
+ buf << chunk
1069
+ end
1070
+ end
1071
+ buf
1072
+ end
971
1073
  end
972
1074
 
973
1075
  ##
@@ -1089,7 +1191,7 @@ class Resolv
1089
1191
  if /\./ =~ hostname
1090
1192
  @search = [Label.split($')]
1091
1193
  else
1092
- @search = [[]]
1194
+ @search = []
1093
1195
  end
1094
1196
  end
1095
1197
 
@@ -1136,23 +1238,18 @@ class Resolv
1136
1238
  end
1137
1239
 
1138
1240
  def generate_candidates(name)
1139
- candidates = nil
1140
1241
  name = Name.create(name)
1141
1242
  if name.absolute?
1142
- candidates = [name]
1243
+ [name]
1143
1244
  else
1245
+ abs = Name.new(name.to_a)
1246
+ search = @search.map {|domain| Name.new(name.to_a + domain)}
1144
1247
  if @ndots <= name.length - 1
1145
- candidates = [Name.new(name.to_a)]
1248
+ [abs, *search].uniq
1146
1249
  else
1147
- candidates = []
1148
- end
1149
- candidates.concat(@search.map {|domain| Name.new(name.to_a + domain)})
1150
- fname = Name.create("#{name}.")
1151
- if !candidates.include?(fname)
1152
- candidates << fname
1250
+ [*search, abs].uniq
1153
1251
  end
1154
1252
  end
1155
- return candidates
1156
1253
  end
1157
1254
 
1158
1255
  InitialTimeout = 5
@@ -1253,6 +1350,13 @@ class Resolv
1253
1350
 
1254
1351
  class Str # :nodoc:
1255
1352
  def initialize(string)
1353
+ # A label is limited to 63 octets. [RFC 1035 2.3.4] Checking it here
1354
+ # makes it an invariant of the object: every label, however it was
1355
+ # built, fits in its length octet and cannot wrap it. Callers turn
1356
+ # this into the error their own contract promises.
1357
+ if string.bytesize > 63
1358
+ raise ArgumentError, "DNS label is too long (#{string.bytesize} bytes, max 63): #{string.inspect}"
1359
+ end
1256
1360
  @string = string
1257
1361
  # case insensivity of DNS labels doesn't apply non-ASCII characters. [RFC 4343]
1258
1362
  # This assumes @string is given in ASCII compatible encoding.
@@ -1298,7 +1402,26 @@ class Resolv
1298
1402
  when Name
1299
1403
  return arg
1300
1404
  when String
1301
- return Name.new(Label.split(arg), /\.\z/ =~ arg ? true : false)
1405
+ # A hostname is runtime data rather than a programming mistake, so
1406
+ # both size limits surface as ResolvError to stay rescuable alongside
1407
+ # the rest of name resolution. The type check below is a caller
1408
+ # mistake and keeps raising ArgumentError.
1409
+ begin
1410
+ labels = Label.split(arg)
1411
+ rescue ArgumentError => e
1412
+ raise ResolvError.new(e.message)
1413
+ end
1414
+ # Label::Str enforces the per-label limit. Only the total is knowable
1415
+ # here, and it counts the encoded form, so size starts at 1 for the
1416
+ # root label's terminating zero octet. [RFC 1035 2.3.4, 3.1]
1417
+ size = 1
1418
+ labels.each do |label|
1419
+ size += 1 + label.string.bytesize
1420
+ if size > 255
1421
+ raise ResolvError.new("DNS name is too long (#{size} octets, max 255): #{arg.inspect}")
1422
+ end
1423
+ end
1424
+ return Name.new(labels, /\.\z/ =~ arg ? true : false)
1302
1425
  else
1303
1426
  raise ArgumentError.new("cannot interpret as DNS name: #{arg.inspect}")
1304
1427
  end
@@ -1420,12 +1543,24 @@ class Resolv
1420
1543
  @rd == other.rd &&
1421
1544
  @ra == other.ra &&
1422
1545
  @rcode == other.rcode &&
1423
- @question == other.question &&
1546
+ question_equal?(other.question) &&
1424
1547
  @answer == other.answer &&
1425
1548
  @authority == other.authority &&
1426
1549
  @additional == other.additional
1427
1550
  end
1428
1551
 
1552
+ # A question holds the resource class itself, and decoding creates a fresh
1553
+ # class for each unknown type, so the classes cannot be compared by
1554
+ # identity alone.
1555
+ private def question_equal?(other_question) # :nodoc:
1556
+ return false unless @question.length == other_question.length
1557
+ @question.zip(other_question) {|(name, typeclass), (o_name, o_typeclass)|
1558
+ return false unless name == o_name &&
1559
+ Resource::Generic.type_class_equal?(typeclass, o_typeclass)
1560
+ }
1561
+ return true
1562
+ end
1563
+
1429
1564
  def add_question(name, typeclass)
1430
1565
  @question << [Name.create(name), typeclass]
1431
1566
  end
@@ -1532,8 +1667,15 @@ class Resolv
1532
1667
  end
1533
1668
 
1534
1669
  def put_string(d)
1535
- self.put_pack("C", d.length)
1536
- @data << d
1670
+ s = d.to_s
1671
+ # A character-string is prefixed by a single length octet, so it can
1672
+ # hold at most 255 octets. [RFC 1035 3.3] Reject anything longer to
1673
+ # avoid silently truncating the length to its low 8 bits (mod 256).
1674
+ if s.bytesize > 255
1675
+ raise ArgumentError, "character-string is too long (#{s.bytesize} bytes, max 255): #{s.inspect}"
1676
+ end
1677
+ self.put_pack("C", s.bytesize)
1678
+ @data << s
1537
1679
  end
1538
1680
 
1539
1681
  def put_string_list(ds)
@@ -1563,7 +1705,17 @@ class Resolv
1563
1705
  end
1564
1706
 
1565
1707
  def put_label(d)
1566
- self.put_string(d.to_s)
1708
+ s = d.to_s
1709
+ # Label::Str applies this limit when a label is built, so what is left
1710
+ # for here is a raw string handed straight to put_labels. The two ways
1711
+ # an over-long label goes wrong differ: 64 to 255 octets write a length
1712
+ # octet in the reserved or compression pointer range, and 256 or more
1713
+ # wrap it mod 256. Either way the encoded name stops being the name the
1714
+ # caller asked for. [RFC 1035 2.3.4, 4.1.4]
1715
+ if s.bytesize > 63
1716
+ raise ArgumentError, "DNS label is too long (#{s.bytesize} bytes, max 63): #{s.inspect}"
1717
+ end
1718
+ self.put_string(s)
1567
1719
  end
1568
1720
  end
1569
1721
 
@@ -1689,7 +1841,9 @@ class Resolv
1689
1841
  prev_index = @index
1690
1842
  save_index = nil
1691
1843
  d = []
1692
- size = -1
1844
+ # size counts the encoded form, so it starts at 1 for the root
1845
+ # label's terminating zero octet. [RFC 1035 3.1]
1846
+ size = 1
1693
1847
  while true
1694
1848
  raise DecodeError.new("limit exceeded") if @limit <= @index
1695
1849
  case @data.getbyte(@index)
@@ -1720,6 +1874,11 @@ class Resolv
1720
1874
 
1721
1875
  def get_label
1722
1876
  return Label::Str.new(self.get_string)
1877
+ rescue ArgumentError => e
1878
+ # A length octet of 64..191 is reserved rather than a label length,
1879
+ # but this decoder used to read it as one. [RFC 1035 4.1.4] Report it
1880
+ # the way the rest of a malformed message is reported.
1881
+ raise DecodeError.new(e.message)
1723
1882
  end
1724
1883
 
1725
1884
  def get_question
@@ -1907,8 +2066,9 @@ class Resolv
1907
2066
  key_name = :"key#{key_number}"
1908
2067
  c.const_set(:KeyName, key_name)
1909
2068
  c.const_set(:KeyNumber, key_number)
1910
- self.const_set(:"Key#{key_number}", c)
1911
- ClassHash[key_name] = ClassHash[key_number] = c
2069
+ # Not registered in a constant or in ClassHash. ClassHash creates a
2070
+ # class for every unknown SvcParamKey, so registering them
2071
+ # permanently would let a malicious response exhaust memory.
1912
2072
  return c
1913
2073
  end
1914
2074
  end
@@ -2215,12 +2375,28 @@ class Resolv
2215
2375
  return self.new(msg.get_bytes)
2216
2376
  end
2217
2377
 
2378
+ # create makes a fresh class for each decoded resource, so the type and
2379
+ # class values have to be compared instead of the class itself.
2380
+ def self.type_class_equal?(klass, other) # :nodoc:
2381
+ return true if klass.equal?(other)
2382
+ Generic > klass && Generic > other &&
2383
+ klass::TypeValue == other::TypeValue &&
2384
+ klass::ClassValue == other::ClassValue
2385
+ end
2386
+
2387
+ def ==(other) # :nodoc:
2388
+ return other.is_a?(Generic) &&
2389
+ Generic.type_class_equal?(self.class, other.class) &&
2390
+ @data == other.data
2391
+ end
2392
+
2218
2393
  def self.create(type_value, class_value) # :nodoc:
2219
2394
  c = Class.new(Generic)
2220
2395
  c.const_set(:TypeValue, type_value)
2221
2396
  c.const_set(:ClassValue, class_value)
2222
- Generic.const_set("Type#{type_value}_Class#{class_value}", c)
2223
- ClassHash[[type_value, class_value]] = c
2397
+ # Not registered in a constant or in ClassHash. get_class creates a
2398
+ # class for every unknown (type, class) pair, so registering them
2399
+ # permanently would let a malicious response exhaust memory.
2224
2400
  return c
2225
2401
  end
2226
2402
  end
@@ -3268,7 +3444,7 @@ class Resolv
3268
3444
 
3269
3445
  # Regular expression LOC size must match.
3270
3446
 
3271
- Regex = /^(\d+\.*\d*)[m]$/
3447
+ Regex = /\A0*(\d{1,8}(?:\.\d+)?)m\z/
3272
3448
 
3273
3449
  ##
3274
3450
  # Creates a new LOC::Size from +arg+ which may be:
@@ -3281,13 +3457,14 @@ class Resolv
3281
3457
  when Size
3282
3458
  return arg
3283
3459
  when String
3284
- scalar = ''
3285
- if Regex =~ arg
3286
- scalar = [(($1.to_f*(1e2)).to_i.to_s[0].to_i*(2**4)+(($1.to_f*(1e2)).to_i.to_s.length-1))].pack("C")
3287
- else
3460
+ unless Regex =~ arg
3288
3461
  raise ArgumentError.new("not a properly formed Size string: " + arg)
3289
3462
  end
3290
- return Size.new(scalar)
3463
+ unless (0.0...1e8) === (scalar = $1.to_f)
3464
+ raise ArgumentError.new("out of range as Size: #{arg}")
3465
+ end
3466
+ str = (scalar * 100).to_i.to_s
3467
+ return new([(str[0].to_i << 4) + (str.bytesize-1)].pack("C"))
3291
3468
  else
3292
3469
  raise ArgumentError.new("cannot interpret as Size: #{arg.inspect}")
3293
3470
  end
@@ -3304,8 +3481,8 @@ class Resolv
3304
3481
  attr_reader :scalar
3305
3482
 
3306
3483
  def to_s # :nodoc:
3307
- s = @scalar.unpack("H2").join.to_s
3308
- return ((s[0].to_i)*(10**(s[1].to_i-2))).to_s << "m"
3484
+ s, = @scalar.unpack("C")
3485
+ return "#{(s >> 4) * (10.0 ** ((s & 0xf) - 2))}m"
3309
3486
  end
3310
3487
 
3311
3488
  def inspect # :nodoc:
@@ -3333,7 +3510,10 @@ class Resolv
3333
3510
 
3334
3511
  # Regular expression LOC Coord must match.
3335
3512
 
3336
- Regex = /^(\d+)\s(\d+)\s(\d+\.\d+)\s([NESW])$/
3513
+ Regex = /\A0*(\d{1,3})\s([0-5]?\d)\s([0-5]?\d(?:\.\d+)?)\s([NESW])\z/
3514
+
3515
+ # Bias for the equator/prime meridian, in thousandths of a second of arc.
3516
+ Bias = 1 << 31
3337
3517
 
3338
3518
  ##
3339
3519
  # Creates a new LOC::Coord from +arg+ which may be:
@@ -3346,17 +3526,19 @@ class Resolv
3346
3526
  when Coord
3347
3527
  return arg
3348
3528
  when String
3349
- coordinates = ''
3350
- if Regex =~ arg && $1.to_f < 180
3351
- m = $~
3352
- hemi = (m[4][/[NE]/]) || (m[4][/[SW]/]) ? 1 : -1
3353
- coordinates = [ ((m[1].to_i*(36e5)) + (m[2].to_i*(6e4)) +
3354
- (m[3].to_f*(1e3))) * hemi+(2**31) ].pack("N")
3355
- orientation = m[4][/[NS]/] ? 'lat' : 'lon'
3356
- else
3529
+ unless m = Regex.match(arg)
3357
3530
  raise ArgumentError.new("not a properly formed Coord string: " + arg)
3358
3531
  end
3359
- return Coord.new(coordinates,orientation)
3532
+
3533
+ arc = (m[1].to_i * 3_600_000) + (m[2].to_i * 60_000) + (m[3].to_f * 1_000).to_i
3534
+ dir = m[4]
3535
+ lat = dir[/[NS]/]
3536
+ unless arc <= (lat ? 324_000_000 : 648_000_000) # (lat ? 90 : 180) * 3_600_000
3537
+ raise ArgumentError.new("out of range as Coord: #{arg}")
3538
+ end
3539
+
3540
+ hemi = dir[/[NE]/] ? 1 : -1
3541
+ return new([arc * hemi + Bias].pack("N"), lat ? "lat" : "lon")
3360
3542
  else
3361
3543
  raise ArgumentError.new("cannot interpret as Coord: #{arg.inspect}")
3362
3544
  end
@@ -3364,10 +3546,10 @@ class Resolv
3364
3546
 
3365
3547
  # Internal use; use self.create.
3366
3548
  def initialize(coordinates,orientation)
3367
- unless coordinates.kind_of?(String)
3549
+ unless coordinates.kind_of?(String) and coordinates.bytesize == 4
3368
3550
  raise ArgumentError.new("Coord must be a 32bit unsigned integer in hex format: #{coordinates.inspect}")
3369
3551
  end
3370
- unless orientation.kind_of?(String) && orientation[/^lon$|^lat$/]
3552
+ unless orientation == "lon" || orientation == "lat"
3371
3553
  raise ArgumentError.new('Coord expects orientation to be a String argument of "lat" or "lon"')
3372
3554
  end
3373
3555
  @coordinates = coordinates
@@ -3384,22 +3566,17 @@ class Resolv
3384
3566
  attr_reader :orientation
3385
3567
 
3386
3568
  def to_s # :nodoc:
3387
- c = @coordinates.unpack("N").join.to_i
3388
- val = (c - (2**31)).abs
3389
- fracsecs = (val % 1e3).to_i.to_s
3390
- val = val / 1e3
3391
- secs = (val % 60).to_i.to_s
3392
- val = val / 60
3393
- mins = (val % 60).to_i.to_s
3394
- degs = (val / 60).to_i.to_s
3395
- posi = (c >= 2**31)
3396
- case posi
3397
- when true
3398
- hemi = @orientation[/^lat$/] ? "N" : "E"
3569
+ c, = @coordinates.unpack("N")
3570
+ val = (c -= Bias).abs
3571
+ val, fracsecs = val.divmod(1000)
3572
+ val, secs = val.divmod(60)
3573
+ degs, mins = val.divmod(60)
3574
+ hemi = if c.negative?
3575
+ @orientation == "lon" ? "W" : "S"
3399
3576
  else
3400
- hemi = @orientation[/^lon$/] ? "W" : "S"
3577
+ @orientation == "lat" ? "N" : "E"
3401
3578
  end
3402
- return degs << " " << mins << " " << secs << "." << fracsecs << " " << hemi
3579
+ format("%d %02d %02d.%03d %s", degs, mins, secs, fracsecs, hemi)
3403
3580
  end
3404
3581
 
3405
3582
  def inspect # :nodoc:
@@ -3427,7 +3604,10 @@ class Resolv
3427
3604
 
3428
3605
  # Regular expression LOC Alt must match.
3429
3606
 
3430
- Regex = /^([+-]*\d+\.*\d*)[m]$/
3607
+ Regex = /\A([+-]?0*\d{1,8}(?:\.\d+)?)m\z/
3608
+
3609
+ # Bias to a base of 100,000m below the WGS 84 reference spheroid.
3610
+ Bias = 100_000_00
3431
3611
 
3432
3612
  ##
3433
3613
  # Creates a new LOC::Alt from +arg+ which may be:
@@ -3440,13 +3620,14 @@ class Resolv
3440
3620
  when Alt
3441
3621
  return arg
3442
3622
  when String
3443
- altitude = ''
3444
- if Regex =~ arg
3445
- altitude = [($1.to_f*(1e2))+(1e7)].pack("N")
3446
- else
3623
+ unless Regex =~ arg
3447
3624
  raise ArgumentError.new("not a properly formed Alt string: " + arg)
3448
3625
  end
3449
- return Alt.new(altitude)
3626
+ altitude = ($1.to_f * 100).to_i + Bias
3627
+ unless (0...0x1_0000_0000) === altitude
3628
+ raise ArgumentError.new("out of raise as Alt: #{arg}")
3629
+ end
3630
+ return new([altitude].pack("N"))
3450
3631
  else
3451
3632
  raise ArgumentError.new("cannot interpret as Alt: #{arg.inspect}")
3452
3633
  end
@@ -3463,8 +3644,8 @@ class Resolv
3463
3644
  attr_reader :altitude
3464
3645
 
3465
3646
  def to_s # :nodoc:
3466
- a = @altitude.unpack("N").join.to_i
3467
- return ((a.to_f/1e2)-1e5).to_s + "m"
3647
+ a, = @altitude.unpack("N")
3648
+ return "#{(a - Bias).fdiv(100)}m"
3468
3649
  end
3469
3650
 
3470
3651
  def inspect # :nodoc:
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: resolv
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.1
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tanaka Akira
@@ -21,6 +21,7 @@ files:
21
21
  - BSDL
22
22
  - COPYING
23
23
  - README.md
24
+ - ext/win32/resolv/depend
24
25
  - ext/win32/resolv/extconf.rb
25
26
  - ext/win32/resolv/lib/resolv.rb
26
27
  - ext/win32/resolv/resolv.c