resolv 0.6.2 → 0.7.2

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: 9f3223ba332f64c3b4b3af1b7044b9fb1369f6da74e05d74d4704fa5d1235d3c
4
- data.tar.gz: 205750363ae9e6014d7cf7daa1e81010525543d7178fc3258ae4eaee4b007afb
3
+ metadata.gz: 38de4f14ddb183350d35593550caa34f9694644fb9d2fc79bd275d92d17ee338
4
+ data.tar.gz: 00d4977e89fb7cd8ec4a772c481d63e49fd4441b90c7ec5be42a79294ab3da19
5
5
  SHA512:
6
- metadata.gz: 7cb060b5cac5331a0c1a719e1c436588230636687f655487d01cbe6c028fb704e425c90b63480bd12eff8c6c798653ad593b8315a108305e91db61c69a9704e0
7
- data.tar.gz: 05d23a3d86214475e34406c614b611d5898ca66f49cebf9ff0507e0afa46acd85cab72d8b162f7a3d702bdb4c22577ee8a88bdbdefeb76bfca3757aed3b553b5
6
+ metadata.gz: 175ca63aa1f82ebabc499bbcd8eedf17c33ff220c439187a1c5778ca2e74926d15651a720c0bb5f615a698747252d8f9cd82c5db5fb25d28a4d1e63cb696acf1
7
+ data.tar.gz: 8789721febc5b7a96a8bcfdc51dd503afefa648a6d7057e6951b77d1fb514c613e35524ef9680e4ef6d4ad49e310a0fa2d1e92a972eae0e8f23969519c7db1d9
data/.document ADDED
@@ -0,0 +1,4 @@
1
+ BSDL
2
+ COPYING
3
+ README.md
4
+ lib/
@@ -1,5 +1,6 @@
1
1
  require 'mkmf'
2
- if RUBY_ENGINE == "ruby" and have_library('iphlpapi', 'GetNetworkParams')
2
+ if RUBY_ENGINE == "ruby" and have_library('iphlpapi', 'GetNetworkParams', ['windows.h', 'iphlpapi.h'])
3
+ have_library('advapi32', 'RegGetValueW', ['windows.h'])
3
4
  create_makefile('win32/resolv')
4
5
  else
5
6
  File.write('Makefile', "all clean install:\n\t@echo Done: $(@)\n")
@@ -4,8 +4,23 @@
4
4
 
5
5
  =end
6
6
 
7
+ require 'win32/resolv.so'
8
+
7
9
  module Win32
8
10
  module Resolv
11
+ # Error at Win32 API
12
+ class Error < StandardError
13
+ # +code+ Win32 Error code
14
+ # +message+ Formatted message for +code+
15
+ def initialize(code, message)
16
+ super(message)
17
+ @code = code
18
+ end
19
+
20
+ # Win32 error code
21
+ attr_reader :code
22
+ end
23
+
9
24
  def self.get_hosts_path
10
25
  path = get_hosts_dir
11
26
  path = File.expand_path('hosts', path)
@@ -29,115 +44,62 @@ module Win32
29
44
  end
30
45
  [ search, nameserver ]
31
46
  end
32
- end
33
- end
34
-
35
- begin
36
- require 'win32/resolv.so'
37
- rescue LoadError
38
- end
39
-
40
- module Win32
41
- #====================================================================
42
- # Windows NT
43
- #====================================================================
44
- module Resolv
45
- begin
46
- require 'win32/registry'
47
- module SZ
48
- refine Registry do
49
- # ad hoc workaround for broken registry
50
- def read_s(key)
51
- type, str = read(key)
52
- unless type == Registry::REG_SZ
53
- warn "Broken registry, #{name}\\#{key} was #{Registry.type2name(type)}, ignored"
54
- return String.new
55
- end
56
- str
57
- end
58
- end
59
- end
60
- using SZ
61
- rescue LoadError
62
- require "open3"
63
- end
64
-
65
- TCPIP_NT = 'SYSTEM\CurrentControlSet\Services\Tcpip\Parameters'
66
47
 
67
48
  class << self
68
49
  private
69
50
  def get_hosts_dir
70
- get_item_property(TCPIP_NT, 'DataBasePath', expand: true)
51
+ tcpip_params do |params|
52
+ params.value('DataBasePath')
53
+ end
71
54
  end
72
55
 
73
56
  def get_info
74
57
  search = nil
75
58
  nameserver = get_dns_server_list
76
59
 
77
- slist = get_item_property(TCPIP_NT, 'SearchList')
78
- search = slist.split(/,\s*/) unless slist.empty?
60
+ tcpip_params do |params|
61
+ slist = params.value('SearchList')
62
+ search = slist.split(/,\s*/) if slist and !slist.empty?
79
63
 
80
- if add_search = search.nil?
81
- search = []
82
- nvdom = get_item_property(TCPIP_NT, 'NV Domain')
64
+ if add_search = search.nil?
65
+ search = []
66
+ domain = params.value('Domain')
83
67
 
84
- unless nvdom.empty?
85
- @search = [ nvdom ]
86
- udmnd = get_item_property(TCPIP_NT, 'UseDomainNameDevolution').to_i
87
- if udmnd != 0
88
- if /^\w+\./ =~ nvdom
89
- devo = $'
68
+ if domain and !domain.empty?
69
+ search = [ domain ]
70
+ udmnd = params.value('UseDomainNameDevolution')
71
+ if udmnd&.nonzero?
72
+ if /^\w+\./ =~ domain
73
+ devo = $'
74
+ end
90
75
  end
91
76
  end
92
77
  end
93
- end
94
78
 
95
- ifs = if defined?(Win32::Registry)
96
- Registry::HKEY_LOCAL_MACHINE.open(TCPIP_NT + '\Interfaces') do |reg|
97
- reg.keys
98
- rescue Registry::Error
99
- []
100
- end
101
- else
102
- cmd = "Get-ChildItem 'HKLM:\\#{TCPIP_NT}\\Interfaces' | ForEach-Object { $_.PSChildName }"
103
- output, _ = Open3.capture2('powershell', '-Command', cmd)
104
- output.split(/\n+/)
79
+ params.open('Interfaces') do |reg|
80
+ reg.each_key do |iface|
81
+ next unless ns = %w[NameServer DhcpNameServer].find do |key|
82
+ ns = iface.value(key)
83
+ break ns.split(/[,\s]\s*/) if ns and !ns.empty?
105
84
  end
106
85
 
107
- ifs.each do |iface|
108
- next unless ns = %w[NameServer DhcpNameServer].find do |key|
109
- ns = get_item_property(TCPIP_NT + '\Interfaces' + "\\#{iface}", key)
110
- break ns.split(/[,\s]\s*/) unless ns.empty?
111
- end
86
+ next if (nameserver & ns).empty?
112
87
 
113
- next if (nameserver & ns).empty?
114
-
115
- if add_search
116
- [ 'Domain', 'DhcpDomain' ].each do |key|
117
- dom = get_item_property(TCPIP_NT + '\Interfaces' + "\\#{iface}", key)
118
- unless dom.empty?
119
- search.concat(dom.split(/,\s*/))
120
- break
88
+ if add_search
89
+ [ 'Domain', 'DhcpDomain' ].each do |key|
90
+ dom = iface.value(key)
91
+ if dom and !dom.empty?
92
+ search.concat(dom.split(/,\s*/))
93
+ break
94
+ end
95
+ end
121
96
  end
122
97
  end
123
98
  end
124
- end
125
- search << devo if add_search and devo
126
- [ search.uniq, nameserver.uniq ]
127
- end
128
99
 
129
- def get_item_property(path, name, expand: false)
130
- if defined?(Win32::Registry)
131
- Registry::HKEY_LOCAL_MACHINE.open(path) do |reg|
132
- expand ? reg.read_s_expand(name) : reg.read_s(name)
133
- rescue Registry::Error
134
- ""
135
- end
136
- else
137
- cmd = "Get-ItemProperty -Path 'HKLM:\\#{path}' -Name '#{name}' -ErrorAction SilentlyContinue | Select-Object -ExpandProperty '#{name}'"
138
- output, _ = Open3.capture2('powershell', '-Command', cmd)
139
- output.strip
100
+ search << devo if add_search and devo
140
101
  end
102
+ [ search.uniq, nameserver.uniq ]
141
103
  end
142
104
  end
143
105
  end
@@ -1,24 +1,56 @@
1
1
  #include <ruby.h>
2
2
  #include <ruby/encoding.h>
3
3
  #include <windows.h>
4
+ #include <windns.h>
4
5
  #ifndef NTDDI_VERSION
5
6
  #define NTDDI_VERSION 0x06000000
6
7
  #endif
7
8
  #include <iphlpapi.h>
8
9
 
10
+ #ifndef numberof
11
+ #define numberof(array) ((int)(sizeof(array) / sizeof((array)[0])))
12
+ #endif
13
+
9
14
  static VALUE
10
15
  w32error_make_error(DWORD e)
11
16
  {
12
- VALUE code = ULONG2NUM(e);
13
- return rb_class_new_instance(1, &code, rb_path2class("Win32::Resolv::Error"));
17
+ char buffer[512], *p;
18
+ DWORD source = 0;
19
+ VALUE args[2];
20
+ if (!FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM |
21
+ FORMAT_MESSAGE_IGNORE_INSERTS, &source, e,
22
+ MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),
23
+ buffer, sizeof(buffer), NULL)) {
24
+ snprintf(buffer, sizeof(buffer), "Unknown Error %lu", (unsigned long)e);
25
+ }
26
+ p = buffer;
27
+ while ((p = strpbrk(p, "\r\n")) != NULL) {
28
+ memmove(p, p + 1, strlen(p));
29
+ if (!p[1]) {
30
+ p[0] = '\0';
31
+ break;
32
+ }
33
+ }
34
+ args[0] = ULONG2NUM(e);
35
+ args[1] = rb_str_new_cstr(buffer);
36
+ return rb_class_new_instance(2, args, rb_path2class("Win32::Resolv::Error"));
14
37
  }
15
38
 
16
- NORETURN(static void w32error_raise(DWORD e));
17
-
18
39
  static void
19
- w32error_raise(DWORD e)
40
+ w32error_check(DWORD e)
41
+ {
42
+ if (e != NO_ERROR) {
43
+ rb_exc_raise(w32error_make_error(e));
44
+ }
45
+ }
46
+
47
+ static VALUE
48
+ wchar_to_utf8(const WCHAR *w, int n)
20
49
  {
21
- rb_exc_raise(w32error_make_error(e));
50
+ int clen = WideCharToMultiByte(CP_UTF8, 0, w, n, NULL, 0, NULL, NULL);
51
+ VALUE str = rb_enc_str_new(NULL, clen, rb_utf8_encoding());
52
+ WideCharToMultiByte(CP_UTF8, 0, w, n, RSTRING_PTR(str), clen, NULL, NULL);
53
+ return str;
22
54
  }
23
55
 
24
56
  static VALUE
@@ -30,9 +62,7 @@ get_dns_server_list(VALUE self)
30
62
  VALUE buf, nameservers = Qnil;
31
63
 
32
64
  ret = GetNetworkParams(NULL, &buflen);
33
- if (ret != NO_ERROR && ret != ERROR_BUFFER_OVERFLOW) {
34
- w32error_raise(ret);
35
- }
65
+ if (ret != ERROR_BUFFER_OVERFLOW) w32error_check(ret);
36
66
  fixedinfo = ALLOCV(buf, buflen);
37
67
  ret = GetNetworkParams(fixedinfo, &buflen);
38
68
  if (ret == NO_ERROR) {
@@ -46,18 +76,181 @@ get_dns_server_list(VALUE self)
46
76
  } while ((ipaddr = ipaddr->Next) != NULL);
47
77
  }
48
78
  ALLOCV_END(buf);
49
- if (ret != NO_ERROR) w32error_raise(ret);
79
+ w32error_check(ret);
50
80
 
51
81
  return nameservers;
52
82
  }
53
83
 
84
+
85
+ static const WCHAR TCPIP_Params[] = L"SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters";
86
+
87
+ static void
88
+ hkey_finalize(void *p)
89
+ {
90
+ RegCloseKey((HKEY)p);
91
+ }
92
+
93
+ static const rb_data_type_t hkey_type = {
94
+ "RegKey",
95
+ {0, hkey_finalize},
96
+ 0, 0, RUBY_TYPED_FREE_IMMEDIATELY,
97
+ };
98
+
99
+ static VALUE
100
+ hkey_close(VALUE self)
101
+ {
102
+ RegCloseKey((HKEY)DATA_PTR(self));
103
+ DATA_PTR(self) = 0;
104
+ return self;
105
+ }
106
+
107
+ static VALUE reg_key_class;
108
+
109
+ static VALUE
110
+ reg_open_key(VALUE klass, HKEY hkey, const WCHAR *wname)
111
+ {
112
+ VALUE k = TypedData_Wrap_Struct(klass, &hkey_type, NULL);
113
+ DWORD e = RegOpenKeyExW(hkey, wname, 0, KEY_READ, (HKEY *)&DATA_PTR(k));
114
+ if (e == ERROR_FILE_NOT_FOUND) return Qnil;
115
+ w32error_check(e);
116
+ return rb_ensure(rb_yield, k, hkey_close, k);
117
+ }
118
+
119
+ static VALUE
120
+ tcpip_params_open(VALUE klass)
121
+ {
122
+ return reg_open_key(reg_key_class, HKEY_LOCAL_MACHINE, TCPIP_Params);
123
+ }
124
+
125
+ static int
126
+ to_wname(VALUE *name, WCHAR *wname, int wlen)
127
+ {
128
+ const char *n = StringValueCStr(*name);
129
+ int nlen = RSTRING_LEN(*name);
130
+ int len = MultiByteToWideChar(CP_UTF8, 0, n, nlen, wname, wlen - 1);
131
+ if (len == 0) w32error_check(GetLastError());
132
+ if (len >= wlen) rb_raise(rb_eArgError, "too long name");
133
+ wname[len] = L'\0';
134
+ return len;
135
+ }
136
+
137
+ static VALUE
138
+ reg_open(VALUE self, VALUE name)
139
+ {
140
+ HKEY hkey = DATA_PTR(self);
141
+ WCHAR wname[256];
142
+ to_wname(&name, wname, numberof(wname));
143
+ return reg_open_key(CLASS_OF(self), hkey, wname);
144
+ }
145
+
146
+
147
+ static VALUE
148
+ reg_each_key(VALUE self)
149
+ {
150
+ WCHAR wname[256];
151
+ HKEY hkey = DATA_PTR(self);
152
+ VALUE k = TypedData_Wrap_Struct(CLASS_OF(self), &hkey_type, NULL);
153
+ DWORD i, e, n;
154
+ for (i = 0; n = numberof(wname), (e = RegEnumKeyExW(hkey, i, wname, &n, NULL, NULL, NULL, NULL)) == ERROR_SUCCESS; i++) {
155
+ e = RegOpenKeyExW(hkey, wname, 0, KEY_READ, (HKEY *)&DATA_PTR(k));
156
+ w32error_check(e);
157
+ rb_ensure(rb_yield, k, hkey_close, k);
158
+ }
159
+ if (e != ERROR_NO_MORE_ITEMS) w32error_check(e);
160
+ return self;
161
+ }
162
+
163
+ static inline DWORD
164
+ swap_dw(DWORD x)
165
+ {
166
+ #if defined(_MSC_VER)
167
+ return _byteswap_ulong(x);
168
+ #else
169
+ return __builtin_bswap32(x);
170
+ #endif
171
+ }
172
+
173
+ static VALUE
174
+ reg_value(VALUE self, VALUE name)
175
+ {
176
+ HKEY hkey = DATA_PTR(self);
177
+ DWORD type = 0, size = 0, e;
178
+ VALUE result, value_buffer;
179
+ void *buffer;
180
+ WCHAR wname[256];
181
+ to_wname(&name, wname, numberof(wname));
182
+ e = RegGetValueW(hkey, NULL, wname, RRF_RT_ANY, &type, NULL, &size);
183
+ if (e == ERROR_FILE_NOT_FOUND) return Qnil;
184
+ w32error_check(e);
185
+ # define get_value_2nd(data, dsize) do { \
186
+ DWORD type2 = type; \
187
+ w32error_check(RegGetValueW(hkey, NULL, wname, RRF_RT_ANY, &type2, data, dsize)); \
188
+ if (type != type2) { \
189
+ rb_raise(rb_eRuntimeError, "registry value type changed %lu -> %lu", \
190
+ (unsigned long)type, (unsigned long)type2); \
191
+ } \
192
+ } while (0)
193
+
194
+ switch (type) {
195
+ case REG_DWORD: case REG_DWORD_BIG_ENDIAN:
196
+ {
197
+ DWORD d;
198
+ if (size != sizeof(d)) rb_raise(rb_eRuntimeError, "invalid size returned: %lu", (unsigned long)size);
199
+ w32error_check(RegGetValueW(hkey, NULL, wname, RRF_RT_REG_DWORD, &type, &d, &size));
200
+ if (type == REG_DWORD_BIG_ENDIAN) d = swap_dw(d);
201
+ return ULONG2NUM(d);
202
+ }
203
+ case REG_QWORD:
204
+ {
205
+ QWORD q;
206
+ if (size != sizeof(q)) rb_raise(rb_eRuntimeError, "invalid size returned: %lu", (unsigned long)size);
207
+ w32error_check(RegGetValueW(hkey, NULL, wname, RRF_RT_REG_QWORD, &type, &q, &size));
208
+ return ULL2NUM(q);
209
+ }
210
+ case REG_SZ: case REG_MULTI_SZ: case REG_EXPAND_SZ:
211
+ if (size % sizeof(WCHAR)) rb_raise(rb_eRuntimeError, "invalid size returned: %lu", (unsigned long)size);
212
+ buffer = ALLOCV_N(char, value_buffer, size);
213
+ get_value_2nd(buffer, &size);
214
+ if (type == REG_MULTI_SZ) {
215
+ const WCHAR *w = (WCHAR *)buffer;
216
+ result = rb_ary_new();
217
+ size /= sizeof(WCHAR);
218
+ size -= 1;
219
+ for (size_t i = 0; i < size; ++i) {
220
+ int n = lstrlenW(w+i);
221
+ rb_ary_push(result, wchar_to_utf8(w+i, n));
222
+ i += n;
223
+ }
224
+ }
225
+ else {
226
+ result = wchar_to_utf8((WCHAR *)buffer, lstrlenW((WCHAR *)buffer));
227
+ }
228
+ ALLOCV_END(value_buffer);
229
+ break;
230
+ default:
231
+ result = rb_str_new(0, size);
232
+ get_value_2nd(RSTRING_PTR(result), &size);
233
+ rb_str_set_len(result, size);
234
+ break;
235
+ }
236
+ return result;
237
+ }
238
+
54
239
  void
55
240
  InitVM_resolv(void)
56
241
  {
57
242
  VALUE mWin32 = rb_define_module("Win32");
58
243
  VALUE resolv = rb_define_module_under(mWin32, "Resolv");
59
244
  VALUE singl = rb_singleton_class(resolv);
245
+ VALUE regkey = rb_define_class_under(resolv, "registry key", rb_cObject);
246
+
247
+ reg_key_class = regkey;
248
+ rb_undef_alloc_func(regkey);
60
249
  rb_define_private_method(singl, "get_dns_server_list", get_dns_server_list, 0);
250
+ rb_define_private_method(singl, "tcpip_params", tcpip_params_open, 0);
251
+ rb_define_method(regkey, "open", reg_open, 1);
252
+ rb_define_method(regkey, "each_key", reg_each_key, 0);
253
+ rb_define_method(regkey, "value", reg_value, 1);
61
254
  }
62
255
 
63
256
  void
data/lib/resolv.rb CHANGED
@@ -4,6 +4,7 @@ require 'socket'
4
4
  require 'timeout'
5
5
  require 'io/wait'
6
6
  require 'securerandom'
7
+ require 'rbconfig'
7
8
 
8
9
  # Resolv is a thread-aware DNS resolver library written in Ruby. Resolv can
9
10
  # handle multiple DNS requests concurrently without blocking the entire Ruby
@@ -33,7 +34,8 @@ require 'securerandom'
33
34
 
34
35
  class Resolv
35
36
 
36
- VERSION = "0.6.2"
37
+ # The version string
38
+ VERSION = "0.7.2"
37
39
 
38
40
  ##
39
41
  # Looks up the first IP address for +name+.
@@ -173,21 +175,19 @@ class Resolv
173
175
 
174
176
  class ResolvTimeout < Timeout::Error; end
175
177
 
176
- WINDOWS = /mswin|cygwin|mingw|bccwin/ =~ RUBY_PLATFORM || ::RbConfig::CONFIG['host_os'] =~ /mswin/
177
- private_constant :WINDOWS
178
-
179
178
  ##
180
179
  # Resolv::Hosts is a hostname resolver that uses the system hosts file.
181
180
 
182
181
  class Hosts
183
- if WINDOWS
182
+ if /mswin|cygwin|mingw|bccwin/ =~ RUBY_PLATFORM || ::RbConfig::CONFIG['host_os'] =~ /mswin/
184
183
  begin
185
184
  require 'win32/resolv' unless defined?(Win32::Resolv)
186
- DefaultFileName = Win32::Resolv.get_hosts_path || IO::NULL
185
+ hosts = Win32::Resolv.get_hosts_path || IO::NULL
187
186
  rescue LoadError
188
187
  end
189
188
  end
190
- DefaultFileName ||= '/etc/hosts'
189
+ # The default file name for host names
190
+ DefaultFileName = hosts || '/etc/hosts'
191
191
 
192
192
  ##
193
193
  # Creates a new Resolv::Hosts, using +filename+ for its data source.
@@ -487,13 +487,18 @@ class Resolv
487
487
  # * Resolv::DNS::Resource::IN::A
488
488
  # * Resolv::DNS::Resource::IN::AAAA
489
489
  # * Resolv::DNS::Resource::IN::ANY
490
+ # * Resolv::DNS::Resource::IN::CAA
490
491
  # * Resolv::DNS::Resource::IN::CNAME
491
492
  # * Resolv::DNS::Resource::IN::HINFO
493
+ # * Resolv::DNS::Resource::IN::HTTPS
494
+ # * Resolv::DNS::Resource::IN::LOC
492
495
  # * Resolv::DNS::Resource::IN::MINFO
493
496
  # * Resolv::DNS::Resource::IN::MX
494
497
  # * Resolv::DNS::Resource::IN::NS
495
498
  # * Resolv::DNS::Resource::IN::PTR
496
499
  # * Resolv::DNS::Resource::IN::SOA
500
+ # * Resolv::DNS::Resource::IN::SRV
501
+ # * Resolv::DNS::Resource::IN::SVCB
497
502
  # * Resolv::DNS::Resource::IN::TXT
498
503
  # * Resolv::DNS::Resource::IN::WKS
499
504
  #
@@ -525,6 +530,8 @@ class Resolv
525
530
  }
526
531
  end
527
532
 
533
+ # :stopdoc:
534
+
528
535
  def fetch_resource(name, typeclass)
529
536
  lazy_initialize
530
537
  truncated = {}
@@ -719,7 +726,8 @@ class Resolv
719
726
  begin
720
727
  reply, from = recv_reply(select_result[0])
721
728
  rescue Errno::ECONNREFUSED, # GNU/Linux, FreeBSD
722
- Errno::ECONNRESET # Windows
729
+ Errno::ECONNRESET, # Windows
730
+ EOFError
723
731
  # No name server running on the server?
724
732
  # Don't wait anymore.
725
733
  raise ResolvTimeout
@@ -928,8 +936,11 @@ class Resolv
928
936
  end
929
937
 
930
938
  def recv_reply(readable_socks)
931
- len = readable_socks[0].read(2).unpack('n')[0]
939
+ len_data = readable_socks[0].read(2)
940
+ raise EOFError if len_data.nil? || len_data.bytesize != 2
941
+ len = len_data.unpack('n')[0]
932
942
  reply = @socks[0].read(len)
943
+ raise EOFError if reply.nil? || reply.bytesize != len
933
944
  return reply, nil
934
945
  end
935
946
 
@@ -1021,8 +1032,7 @@ class Resolv
1021
1032
  def Config.default_config_hash(filename="/etc/resolv.conf")
1022
1033
  if File.exist? filename
1023
1034
  Config.parse_resolv_conf(filename)
1024
- elsif WINDOWS
1025
- require 'win32/resolv' unless defined?(Win32::Resolv)
1035
+ elsif defined?(Win32::Resolv)
1026
1036
  search, nameserver = Win32::Resolv.get_resolv_info
1027
1037
  config_hash = {}
1028
1038
  config_hash[:nameserver] = nameserver if nameserver
@@ -1243,6 +1253,13 @@ class Resolv
1243
1253
 
1244
1254
  class Str # :nodoc:
1245
1255
  def initialize(string)
1256
+ # A label is limited to 63 octets. [RFC 1035 2.3.4] Checking it here
1257
+ # makes it an invariant of the object: every label, however it was
1258
+ # built, fits in its length octet and cannot wrap it. Callers turn
1259
+ # this into the error their own contract promises.
1260
+ if string.bytesize > 63
1261
+ raise ArgumentError, "DNS label is too long (#{string.bytesize} bytes, max 63): #{string.inspect}"
1262
+ end
1246
1263
  @string = string
1247
1264
  # case insensivity of DNS labels doesn't apply non-ASCII characters. [RFC 4343]
1248
1265
  # This assumes @string is given in ASCII compatible encoding.
@@ -1288,7 +1305,26 @@ class Resolv
1288
1305
  when Name
1289
1306
  return arg
1290
1307
  when String
1291
- return Name.new(Label.split(arg), /\.\z/ =~ arg ? true : false)
1308
+ # A hostname is runtime data rather than a programming mistake, so
1309
+ # both size limits surface as ResolvError to stay rescuable alongside
1310
+ # the rest of name resolution. The type check below is a caller
1311
+ # mistake and keeps raising ArgumentError.
1312
+ begin
1313
+ labels = Label.split(arg)
1314
+ rescue ArgumentError => e
1315
+ raise ResolvError.new(e.message)
1316
+ end
1317
+ # Label::Str enforces the per-label limit. Only the total is knowable
1318
+ # here, and it counts the encoded form, so size starts at 1 for the
1319
+ # root label's terminating zero octet. [RFC 1035 2.3.4, 3.1]
1320
+ size = 1
1321
+ labels.each do |label|
1322
+ size += 1 + label.string.bytesize
1323
+ if size > 255
1324
+ raise ResolvError.new("DNS name is too long (#{size} octets, max 255): #{arg.inspect}")
1325
+ end
1326
+ end
1327
+ return Name.new(labels, /\.\z/ =~ arg ? true : false)
1292
1328
  else
1293
1329
  raise ArgumentError.new("cannot interpret as DNS name: #{arg.inspect}")
1294
1330
  end
@@ -1410,12 +1446,24 @@ class Resolv
1410
1446
  @rd == other.rd &&
1411
1447
  @ra == other.ra &&
1412
1448
  @rcode == other.rcode &&
1413
- @question == other.question &&
1449
+ question_equal?(other.question) &&
1414
1450
  @answer == other.answer &&
1415
1451
  @authority == other.authority &&
1416
1452
  @additional == other.additional
1417
1453
  end
1418
1454
 
1455
+ # A question holds the resource class itself, and decoding creates a fresh
1456
+ # class for each unknown type, so the classes cannot be compared by
1457
+ # identity alone.
1458
+ private def question_equal?(other_question) # :nodoc:
1459
+ return false unless @question.length == other_question.length
1460
+ @question.zip(other_question) {|(name, typeclass), (o_name, o_typeclass)|
1461
+ return false unless name == o_name &&
1462
+ Resource::Generic.type_class_equal?(typeclass, o_typeclass)
1463
+ }
1464
+ return true
1465
+ end
1466
+
1419
1467
  def add_question(name, typeclass)
1420
1468
  @question << [Name.create(name), typeclass]
1421
1469
  end
@@ -1522,8 +1570,15 @@ class Resolv
1522
1570
  end
1523
1571
 
1524
1572
  def put_string(d)
1525
- self.put_pack("C", d.length)
1526
- @data << d
1573
+ s = d.to_s
1574
+ # A character-string is prefixed by a single length octet, so it can
1575
+ # hold at most 255 octets. [RFC 1035 3.3] Reject anything longer to
1576
+ # avoid silently truncating the length to its low 8 bits (mod 256).
1577
+ if s.bytesize > 255
1578
+ raise ArgumentError, "character-string is too long (#{s.bytesize} bytes, max 255): #{s.inspect}"
1579
+ end
1580
+ self.put_pack("C", s.bytesize)
1581
+ @data << s
1527
1582
  end
1528
1583
 
1529
1584
  def put_string_list(ds)
@@ -1553,7 +1608,17 @@ class Resolv
1553
1608
  end
1554
1609
 
1555
1610
  def put_label(d)
1556
- self.put_string(d.to_s)
1611
+ s = d.to_s
1612
+ # Label::Str applies this limit when a label is built, so what is left
1613
+ # for here is a raw string handed straight to put_labels. The two ways
1614
+ # an over-long label goes wrong differ: 64 to 255 octets write a length
1615
+ # octet in the reserved or compression pointer range, and 256 or more
1616
+ # wrap it mod 256. Either way the encoded name stops being the name the
1617
+ # caller asked for. [RFC 1035 2.3.4, 4.1.4]
1618
+ if s.bytesize > 63
1619
+ raise ArgumentError, "DNS label is too long (#{s.bytesize} bytes, max 63): #{s.inspect}"
1620
+ end
1621
+ self.put_string(s)
1557
1622
  end
1558
1623
  end
1559
1624
 
@@ -1679,7 +1744,9 @@ class Resolv
1679
1744
  prev_index = @index
1680
1745
  save_index = nil
1681
1746
  d = []
1682
- size = -1
1747
+ # size counts the encoded form, so it starts at 1 for the root
1748
+ # label's terminating zero octet. [RFC 1035 3.1]
1749
+ size = 1
1683
1750
  while true
1684
1751
  raise DecodeError.new("limit exceeded") if @limit <= @index
1685
1752
  case @data.getbyte(@index)
@@ -1710,6 +1777,11 @@ class Resolv
1710
1777
 
1711
1778
  def get_label
1712
1779
  return Label::Str.new(self.get_string)
1780
+ rescue ArgumentError => e
1781
+ # A length octet of 64..191 is reserved rather than a label length,
1782
+ # but this decoder used to read it as one. [RFC 1035 4.1.4] Report it
1783
+ # the way the rest of a malformed message is reported.
1784
+ raise DecodeError.new(e.message)
1713
1785
  end
1714
1786
 
1715
1787
  def get_question
@@ -1897,8 +1969,9 @@ class Resolv
1897
1969
  key_name = :"key#{key_number}"
1898
1970
  c.const_set(:KeyName, key_name)
1899
1971
  c.const_set(:KeyNumber, key_number)
1900
- self.const_set(:"Key#{key_number}", c)
1901
- ClassHash[key_name] = ClassHash[key_number] = c
1972
+ # Not registered in a constant or in ClassHash. ClassHash creates a
1973
+ # class for every unknown SvcParamKey, so registering them
1974
+ # permanently would let a malicious response exhaust memory.
1902
1975
  return c
1903
1976
  end
1904
1977
  end
@@ -2205,12 +2278,28 @@ class Resolv
2205
2278
  return self.new(msg.get_bytes)
2206
2279
  end
2207
2280
 
2281
+ # create makes a fresh class for each decoded resource, so the type and
2282
+ # class values have to be compared instead of the class itself.
2283
+ def self.type_class_equal?(klass, other) # :nodoc:
2284
+ return true if klass.equal?(other)
2285
+ Generic > klass && Generic > other &&
2286
+ klass::TypeValue == other::TypeValue &&
2287
+ klass::ClassValue == other::ClassValue
2288
+ end
2289
+
2290
+ def ==(other) # :nodoc:
2291
+ return other.is_a?(Generic) &&
2292
+ Generic.type_class_equal?(self.class, other.class) &&
2293
+ @data == other.data
2294
+ end
2295
+
2208
2296
  def self.create(type_value, class_value) # :nodoc:
2209
2297
  c = Class.new(Generic)
2210
2298
  c.const_set(:TypeValue, type_value)
2211
2299
  c.const_set(:ClassValue, class_value)
2212
- Generic.const_set("Type#{type_value}_Class#{class_value}", c)
2213
- ClassHash[[type_value, class_value]] = c
2300
+ # Not registered in a constant or in ClassHash. get_class creates a
2301
+ # class for every unknown (type, class) pair, so registering them
2302
+ # permanently would let a malicious response exhaust memory.
2214
2303
  return c
2215
2304
  end
2216
2305
  end
@@ -2926,15 +3015,21 @@ class Resolv
2926
3015
 
2927
3016
  class IPv4
2928
3017
 
2929
- ##
2930
- # Regular expression IPv4 addresses must match.
2931
-
2932
3018
  Regex256 = /0
2933
3019
  |1(?:[0-9][0-9]?)?
2934
3020
  |2(?:[0-4][0-9]?|5[0-5]?|[6-9])?
2935
- |[3-9][0-9]?/x
3021
+ |[3-9][0-9]?/x # :nodoc:
3022
+
3023
+ ##
3024
+ # Regular expression IPv4 addresses must match.
2936
3025
  Regex = /\A(#{Regex256})\.(#{Regex256})\.(#{Regex256})\.(#{Regex256})\z/
2937
3026
 
3027
+ ##
3028
+ # Creates a new IPv4 address from +arg+ which may be:
3029
+ #
3030
+ # IPv4:: returns +arg+.
3031
+ # String:: +arg+ must match the IPv4::Regex constant
3032
+
2938
3033
  def self.create(arg)
2939
3034
  case arg
2940
3035
  when IPv4
@@ -3243,13 +3338,15 @@ class Resolv
3243
3338
 
3244
3339
  end
3245
3340
 
3246
- module LOC
3341
+ module LOC # :nodoc:
3247
3342
 
3248
3343
  ##
3249
3344
  # A Resolv::LOC::Size
3250
3345
 
3251
3346
  class Size
3252
3347
 
3348
+ # Regular expression LOC size must match.
3349
+
3253
3350
  Regex = /^(\d+\.*\d*)[m]$/
3254
3351
 
3255
3352
  ##
@@ -3275,6 +3372,7 @@ class Resolv
3275
3372
  end
3276
3373
  end
3277
3374
 
3375
+ # Internal use; use self.create.
3278
3376
  def initialize(scalar)
3279
3377
  @scalar = scalar
3280
3378
  end
@@ -3312,6 +3410,8 @@ class Resolv
3312
3410
 
3313
3411
  class Coord
3314
3412
 
3413
+ # Regular expression LOC Coord must match.
3414
+
3315
3415
  Regex = /^(\d+)\s(\d+)\s(\d+\.\d+)\s([NESW])$/
3316
3416
 
3317
3417
  ##
@@ -3341,6 +3441,7 @@ class Resolv
3341
3441
  end
3342
3442
  end
3343
3443
 
3444
+ # Internal use; use self.create.
3344
3445
  def initialize(coordinates,orientation)
3345
3446
  unless coordinates.kind_of?(String)
3346
3447
  raise ArgumentError.new("Coord must be a 32bit unsigned integer in hex format: #{coordinates.inspect}")
@@ -3403,6 +3504,8 @@ class Resolv
3403
3504
 
3404
3505
  class Alt
3405
3506
 
3507
+ # Regular expression LOC Alt must match.
3508
+
3406
3509
  Regex = /^([+-]*\d+\.*\d*)[m]$/
3407
3510
 
3408
3511
  ##
@@ -3428,6 +3531,7 @@ class Resolv
3428
3531
  end
3429
3532
  end
3430
3533
 
3534
+ # Internal use; use self.create.
3431
3535
  def initialize(altitude)
3432
3536
  @altitude = altitude
3433
3537
  end
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.6.2
4
+ version: 0.7.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tanaka Akira
@@ -17,23 +17,14 @@ extensions:
17
17
  - ext/win32/resolv/extconf.rb
18
18
  extra_rdoc_files: []
19
19
  files:
20
- - ".git-blame-ignore-revs"
21
- - ".github/dependabot.yml"
22
- - ".github/workflows/push_gem.yml"
23
- - ".github/workflows/test.yml"
24
- - ".gitignore"
20
+ - ".document"
25
21
  - BSDL
26
22
  - COPYING
27
- - Gemfile
28
23
  - README.md
29
- - Rakefile
30
- - bin/console
31
- - bin/setup
32
24
  - ext/win32/resolv/extconf.rb
33
25
  - ext/win32/resolv/lib/resolv.rb
34
26
  - ext/win32/resolv/resolv.c
35
27
  - lib/resolv.rb
36
- - resolv.gemspec
37
28
  homepage: https://github.com/ruby/resolv
38
29
  licenses:
39
30
  - Ruby
@@ -55,7 +46,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
55
46
  - !ruby/object:Gem::Version
56
47
  version: '0'
57
48
  requirements: []
58
- rubygems_version: 3.6.7
49
+ rubygems_version: 3.6.9
59
50
  specification_version: 4
60
51
  summary: Thread-aware DNS resolver library in Ruby.
61
52
  test_files: []
@@ -1,7 +0,0 @@
1
- # This is a file used by GitHub to ignore the following commits on `git blame`.
2
- #
3
- # You can also do the same thing in your local repository with:
4
- # $ git config --local blame.ignoreRevsFile .git-blame-ignore-revs
5
-
6
- # Expand tabs
7
- 9ae210665a8524554e7c868d3ec67af54b0958bb
@@ -1,6 +0,0 @@
1
- version: 2
2
- updates:
3
- - package-ecosystem: 'github-actions'
4
- directory: '/'
5
- schedule:
6
- interval: 'weekly'
@@ -1,46 +0,0 @@
1
- name: Publish gem to rubygems.org
2
-
3
- on:
4
- push:
5
- tags:
6
- - 'v*'
7
-
8
- permissions:
9
- contents: read
10
-
11
- jobs:
12
- push:
13
- if: github.repository == 'ruby/resolv'
14
- runs-on: ubuntu-latest
15
-
16
- environment:
17
- name: rubygems.org
18
- url: https://rubygems.org/gems/resolv
19
-
20
- permissions:
21
- contents: write
22
- id-token: write
23
-
24
- steps:
25
- - name: Harden Runner
26
- uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
27
- with:
28
- egress-policy: audit
29
-
30
- - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
31
-
32
- - name: Set up Ruby
33
- uses: ruby/setup-ruby@a4effe49ee8ee5b8b5091268c473a4628afb5651 # v1.245.0
34
- with:
35
- bundler-cache: true
36
- ruby-version: ruby
37
-
38
- - name: Publish to RubyGems
39
- uses: rubygems/release-gem@a25424ba2ba8b387abc8ef40807c2c85b96cbe32 # v1.1.1
40
-
41
- - name: Create GitHub release
42
- run: |
43
- tag_name="$(git describe --tags --abbrev=0)"
44
- gh release create "${tag_name}" --verify-tag --generate-notes
45
- env:
46
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -1,52 +0,0 @@
1
- name: test
2
-
3
- on: [push, pull_request]
4
-
5
- jobs:
6
- ruby-versions:
7
- uses: ruby/actions/.github/workflows/ruby_versions.yml@master
8
- with:
9
- engine: cruby-jruby
10
- min_version: 2.5
11
-
12
- build:
13
- needs: ruby-versions
14
- name: build (${{ matrix.ruby }} / ${{ matrix.os }})
15
- strategy:
16
- matrix:
17
- ruby: ${{ fromJson(needs.ruby-versions.outputs.versions) }}
18
- os: [ ubuntu-latest, macos-latest, windows-latest ]
19
- include:
20
- - ruby: mswin
21
- os: windows-latest
22
- exclude:
23
- - ruby: 2.5
24
- os: macos-latest
25
- runs-on: ${{ matrix.os }}
26
- steps:
27
- - uses: actions/checkout@v4
28
- - name: Set up Ruby
29
- uses: ruby/setup-ruby@v1
30
- with:
31
- ruby-version: ${{ matrix.ruby }}
32
- bundler-cache: true
33
- - name: Run test
34
- run: bundle exec rake test
35
- timeout-minutes: 3
36
- continue-on-error: ${{ startsWith(matrix.ruby, 'jruby') }}
37
- - name: Build package
38
- id: build
39
- shell: bash
40
- run: |
41
- if ruby -e 'exit RUBY_VERSION>="3.0."'; then
42
- bundle exec rake build
43
- set pkg/*.gem
44
- echo pkg=$1 >> $GITHUB_OUTPUT
45
- fi
46
- - name: Install gem
47
- run: |
48
- gem install ${{ steps.build.outputs.pkg }}
49
- ruby -rresolv -e 'puts $LOADED_FEATURES.grep(/resolv/)'
50
- ruby -rresolv -e 'puts Resolv::VERSION'
51
- if: ${{ steps.build.outputs.pkg }}
52
- continue-on-error: ${{ startsWith(matrix.ruby, 'jruby') }}
data/.gitignore DELETED
@@ -1,11 +0,0 @@
1
- /.build/
2
- /.bundle/
3
- /.yardoc
4
- /_yardoc/
5
- /coverage/
6
- /doc/
7
- /pkg/
8
- /spec/reports/
9
- /tmp/
10
-
11
- Gemfile.lock
data/Gemfile DELETED
@@ -1,8 +0,0 @@
1
- source "https://rubygems.org"
2
-
3
- gemspec
4
-
5
- gem "rake"
6
- gem "test-unit"
7
- gem "test-unit-ruby-core"
8
- gem "ruby-core-tasks", github: "ruby/ruby-core-tasks"
data/Rakefile DELETED
@@ -1,18 +0,0 @@
1
- require "bundler/gem_tasks"
2
- require "rake/testtask"
3
-
4
- if RUBY_ENGINE == "ruby"
5
- require "ruby-core/extensiontask"
6
- helper = Bundler::GemHelper.instance
7
- extask = RubyCore::ExtensionTask.new(helper.gemspec)
8
- task :test => :compile
9
- end
10
-
11
- Rake::TestTask.new(:test) do |t|
12
- t.libs.unshift(*extask.libs) if extask
13
- t.libs << "test/lib"
14
- t.ruby_opts << "-rhelper"
15
- t.test_files = FileList["test/**/test_*.rb"]
16
- end
17
-
18
- task :default => :test
data/bin/console DELETED
@@ -1,14 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require "bundler/setup"
4
- require "resolv"
5
-
6
- # You can add fixtures and/or initialization code here to make experimenting
7
- # with your gem easier. You can also use a different console, if you like.
8
-
9
- # (If you use this, don't forget to add pry to your Gemfile!)
10
- # require "pry"
11
- # Pry.start
12
-
13
- require "irb"
14
- IRB.start(__FILE__)
data/bin/setup DELETED
@@ -1,8 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
- IFS=$'\n\t'
4
- set -vx
5
-
6
- bundle install
7
-
8
- # Do any other automated setup that you need to do here
data/resolv.gemspec DELETED
@@ -1,30 +0,0 @@
1
- name = File.basename(__FILE__, ".gemspec")
2
- version = ["lib", Array.new(name.count("-")+1).join("/")].find do |dir|
3
- break File.foreach(File.join(__dir__, dir, "#{name.tr('-', '/')}.rb")) do |line|
4
- /^\s*VERSION\s*=\s*"(.*)"/ =~ line and break $1
5
- end rescue nil
6
- end
7
-
8
- Gem::Specification.new do |spec|
9
- spec.name = name
10
- spec.version = version
11
- spec.authors = ["Tanaka Akira"]
12
- spec.email = ["akr@fsij.org"]
13
-
14
- spec.summary = %q{Thread-aware DNS resolver library in Ruby.}
15
- spec.description = %q{Thread-aware DNS resolver library in Ruby.}
16
- spec.homepage = "https://github.com/ruby/resolv"
17
- spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
18
- spec.licenses = ["Ruby", "BSD-2-Clause"]
19
- spec.extensions << "ext/win32/resolv/extconf.rb"
20
-
21
- spec.metadata["homepage_uri"] = spec.homepage
22
- spec.metadata["source_code_uri"] = spec.homepage
23
-
24
- spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
25
- `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
26
- end
27
- spec.bindir = "exe"
28
- spec.executables = []
29
- spec.require_paths = ["lib"]
30
- end