net-scp 4.1.1.rc3 → 4.1.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: 140aba7e5a90f61684d2a1acf0dedd19834b903c6e0a1d09f8e396cea0fd002c
4
- data.tar.gz: 10ba5b3c7cc0172b435b44efbfc115be88caf60d804bfb11cb08b7e60bfbe00a
3
+ metadata.gz: b1413e9916cf7c4d82160a87e9da29044528f7c32dc746d12a873ea4d902a940
4
+ data.tar.gz: b0e33d71c6a78b8d263fd95408e23188c3c7e0bb274d52c1df6745bbc6793cc6
5
5
  SHA512:
6
- metadata.gz: eca631adb3b5cc64bca4fcf5ca05ae62e5043631f004dac7edc2341c86165da3546f3aeb249b7618c454046c1d8897ed98a1a453f927815c2c10395bc11bcbd5
7
- data.tar.gz: ee708ab42fa3b6d92cb79d695fe5a1f4b408cc4b4095cb74a8985c98f15af0002428d8f2c56c5f328a1fe0f7d709e33c49d0a60e8fb1db1d456ff9a016db17e7
6
+ metadata.gz: 46648015028eb41a2d7f269f4fe6baa430289da17f82c2945c5ef49ff4fa43085a966734d7e361a98b08cd91f7abf99f219eeebd954607bc74969f398e861ceb
7
+ data.tar.gz: e307dc9bb8e181c76acb226ffc8b35f5c2002adbef0f027f8954b9e695cc8436b780a7f4316dba7b685a48048184676c20dacceb2db81d235dcf51a83045ddf5
checksums.yaml.gz.sig CHANGED
Binary file
@@ -0,0 +1 @@
1
+ <!-- After opening the PR, add a changelog.d/<PR number>.<type>.txt fragment (none, change, security, breaking or breaking-security) instead of editing CHANGES.txt; see CONTRIBUTING.md#changelog. -->
@@ -0,0 +1,18 @@
1
+ name: Changelog
2
+
3
+ on:
4
+ pull_request:
5
+ types: [opened, synchronize, reopened, labeled, unlabeled]
6
+
7
+ permissions:
8
+ contents: read
9
+
10
+ jobs:
11
+ changelog:
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+ with:
16
+ # HEAD is the PR merged into the base branch; HEAD^1 is the base it was merged into.
17
+ fetch-depth: 2
18
+ - uses: net-ssh/changelogd@v1
@@ -10,7 +10,7 @@ jobs:
10
10
  strategy:
11
11
  fail-fast: false
12
12
  matrix:
13
- ruby-version: ['2.6', '2.7', '3.0', '3.1', '3.2', '3.3', '3.4', 'ruby-head']
13
+ ruby-version: ['2.6', '2.7', '3.0', '3.1', '3.2', '3.3', '3.4', '4.0', 'ruby-head']
14
14
  continue-on-error: ${{ matrix.ruby-version == 'ruby-head' }}
15
15
  steps:
16
16
  - uses: actions/checkout@v3
data/CHANGES.txt CHANGED
@@ -1,5 +1,22 @@
1
+ === 4.1.2
2
+
3
+ * Security: Downloads no longer apply setuid, setgid or sticky bits sent by the server, check that a server-chosen local name matches the requested remote basename (as OpenSSH does since CVE-2019-6111), and refuse to follow an existing local symlink at a server-named path instead of writing through it. The local path passed by the caller may still be a symlink. O_NOFOLLOW suggested in net-sftp#162 by srosenhamer [#104]
4
+ * Security: Recursive downloads, and downloads into a directory, reject file names from the server that are not a single path component (such as `../x` or `a/b`) instead of writing outside the target directory. Reported in net-sftp#162 by srosenhamer [#103]
5
+ * Raise `Net::SCP::Error` when the server ends a transfer before the SCP protocol finishes, instead of reporting a truncated transfer as successful [#105, #96]
6
+ * Opening an `scp://` URI through `uri/open-scp` now percent-decodes the user, password and path (once, keeping a literal `+`) and passes an IPv6 address to SSH without its brackets, so `scp://user@[::1]/a%20b` downloads `/a b` from `::1`. Thanks @OskarEichler [#94]
7
+ * Opening an `scp://` URI through `uri/open-scp` now writes the download into the OpenURI buffer as it arrives, so large files spill to a temporary file instead of being held in memory twice. Thanks @OskarEichler [#93]
8
+ * Parsing an `scp://` URI with a valueless query entry (`?flag`) or an empty one (`?a=1&&b=2`) no longer raises `NoMethodError`, and `URI::SCP#options` reflects the current query after `query=`. Thanks @OskarEichler [#92]
9
+ * Opening an `scp://` URI through `uri/open-scp` now calls `:progress_proc` with the number of bytes received, as OpenURI documents, instead of with the SCP progress arguments (which raised `ArgumentError` for a lambda), and calls `:content_length_proc` once with the file size. Thanks @OskarEichler [#91]
10
+ * Opening an `scp://` URI through `uri/open-scp` now uses the port and password given in the URI; they were passed outside the `:ssh` options and ignored. Options given in `:ssh` still take precedence. Thanks @OskarEichler [#90]
11
+ * Close the local file net-scp opened for an upload or download when the transfer fails, so it is not left open (and locked on Windows) [#89, #20]
12
+ * With `preserve: true`, recursive downloads now keep directories' modification times, which were overwritten when their contents were created. Thanks @OskarEichler [#88]
13
+ * `upload`/`upload!` with a zero, negative or non-Integer `:chunk_size` now raises `ArgumentError` before opening a channel; previously `0` hung forever sending empty chunks, a negative value or a String failed only after the channel was open, and a Float was silently truncated. Thanks @OskarEichler [#87]
14
+ * Uploading from an IO that isn't at its start now announces only the remaining bytes, instead of the full size, which made the transfer hang or fail. Thanks @OskarEichler [#86]
15
+ * Accept a `Pathname` as the local path for uploads and downloads; previously uploads looped forever and downloads kept only the last chunk. Thanks @OskarEichler [#85, #29]
16
+
1
17
  === 4.1.1
2
18
 
19
+ * Allow net-ssh 8
3
20
  * Fix frozen string literal compatibility [npezza93, mfazekas]
4
21
  - Use mutable string for scp command construction
5
22
  - Use mutable string for error_string initialization
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,33 @@
1
+ # Contributing to net-scp
2
+
3
+ Run the tests with `bundle exec rake test`.
4
+
5
+ ## Changelog
6
+
7
+ Don't edit `CHANGES.txt`. Instead, add a fragment file to `changelog.d/` in your PR; the release's version bump (`rake vbump:*`) moves all fragments into a new `=== <version>` section at the top of `CHANGES.txt` and deletes them.
8
+
9
+ A fragment is named `<number>.<type>.txt`, where `<number>` is the PR number (so open the PR first, then add the fragment), and contains a single UTF-8 paragraph: the entry as it should read in `CHANGES.txt`, without the bullet or prefix. Lines are joined, so it can be wrapped. If it doesn't end with a reference list such as `[#95, #101]` or `[GHSA-xxxx-xxxx-xxxx]`, ` [#<number>]` is appended.
10
+
11
+ | Type | Written as | Use for |
12
+ | ------------------- | ------------------------------ | ------------------------------------------------------------ |
13
+ | `breaking-security` | `* BREAKING: Security: <text>` | an incompatible change made for security |
14
+ | `breaking` | `* BREAKING: <text>` | an incompatible change |
15
+ | `security` | `* Security: <text>` | a security fix or hardening |
16
+ | `change` | `* <text>` | any other user-visible fix or feature |
17
+ | `none` | nothing | PRs with no user-visible change (tests, CI, docs, refactors) |
18
+
19
+ Examples:
20
+
21
+ ```sh
22
+ rake "changelog:new[102,change,Fixed uploading files with spaces in their names]"
23
+ # changelog.d/102.change.txt -> * Fixed uploading files with spaces in their names [#102]
24
+
25
+ echo 'Refuse downloads that write outside the target directory [GHSA-xxxx-xxxx-xxxx]' > changelog.d/103.security.txt
26
+ touch changelog.d/104.none.txt
27
+ ```
28
+
29
+ For several entries from one PR add a suffix: `102.change.txt`, `102.change.2.txt` (`changelog:new` picks the next free one).
30
+
31
+ `rake changelog:check` validates the fragments and `rake changelog:preview` prints the section the next release would add. CI fails a PR that edits `CHANGES.txt` or doesn't add or modify a `changelog.d/<PR number>.<type>.txt` fragment, unless it only touches `changelog.d/` or a maintainer adds the `skip-changelog` label; Dependabot PRs are skipped.
32
+
33
+ The full rules, including how a release orders the entries, are in the [changelogd README](https://github.com/net-ssh/changelogd#readme).
data/Gemfile CHANGED
@@ -11,6 +11,9 @@ gem "bundler", ">= 1.11"
11
11
  gem "rake", ">= 12.0"
12
12
  gem "rdoc"
13
13
 
14
+ # git: rather than github:, which Bundler 1.x (the publish workflow uses 1.17.3) expands to an unsupported git:// URL.
15
+ gem "changelogd", git: "https://github.com/net-ssh/changelogd.git", tag: "v1", group: :development
16
+
14
17
  gem 'byebug', group: %i[development test] if !Gem.win_platform? && RUBY_ENGINE == "ruby"
15
18
 
16
19
  if ENV["CI"]
data/Rakefile CHANGED
@@ -22,6 +22,16 @@ name = "net-scp"
22
22
  require_relative "lib/net/scp/version"
23
23
  version = Net::SCP::Version::CURRENT
24
24
 
25
+ begin
26
+ require "changelogd/rake"
27
+ rescue LoadError
28
+ # A git gem isn't on the load path under plain `rake`, as the release workflow runs it; bundler/setup could clash with the running rake.
29
+ require "bundler"
30
+ $LOAD_PATH.unshift(*Bundler.definition.specs["changelogd"].flat_map(&:full_require_paths))
31
+ require "changelogd/rake"
32
+ end
33
+ Changelogd::RakeTasks.new(version_file: "lib/net/scp/version.rb")
34
+
25
35
  namespace :cert do
26
36
  desc "Update public cert from private - only run if public is expired"
27
37
  task :update_public_when_expired do
@@ -86,6 +96,7 @@ if defined?(RDoc::Task)
86
96
  end
87
97
 
88
98
  def change_version(&block)
99
+ Rake::Task["changelog:check"].invoke
89
100
  version_file = 'lib/net/scp/version.rb'
90
101
  require_relative version_file
91
102
  pre = Net::SCP::Version::PRE
@@ -135,6 +146,7 @@ def change_version(&block)
135
146
 
136
147
  FileUtils.mv version_file, "#{version_file}.old"
137
148
  FileUtils.mv "#{version_file}.new", version_file
149
+ Rake::Task["changelog:release"].invoke
138
150
  end
139
151
 
140
152
  namespace :vbump do
@@ -0,0 +1 @@
1
+ Changelog fragments: one `<PR number>.<type>.txt` file per user-visible change, assembled into `CHANGES.txt` when the version is bumped. See [CONTRIBUTING.md](../CONTRIBUTING.md#changelog).
@@ -50,8 +50,11 @@ module Net; class SCP
50
50
  when :file
51
51
  read_file(channel, directive)
52
52
  when :end
53
+ directory = channel[:stack].pop
54
+ if channel[:options][:preserve] && directory && directory[:times]
55
+ File.utime(directory[:times][:atime], directory[:times][:mtime], channel[:local])
56
+ end
53
57
  channel[:local] = File.dirname(channel[:local])
54
- channel[:stack].pop
55
58
  channel[:state] = :finish if channel[:stack].empty?
56
59
  end
57
60
 
@@ -73,7 +76,7 @@ module Net; class SCP
73
76
  # Finishes off the read, sets the times for the file (if any), and then
74
77
  # jumps to either #finish_state (for single-file downloads) or
75
78
  # #read_directive_state (for recursive downloads). A 0-byte is sent to the
76
- # server to indicate that the file was recieved successfully.
79
+ # server to indicate that the file was received successfully.
77
80
  def finish_read_state(channel)
78
81
  channel[:io].close unless channel[:io] == channel[:local]
79
82
 
@@ -126,19 +129,17 @@ module Net; class SCP
126
129
  raise Net::SCP::Error, ":recursive not specified for directory download"
127
130
  end
128
131
 
129
- channel[:local] = File.join(channel[:local], directive[:name])
132
+ channel[:local] = local_path_for(channel, directive[:name])
130
133
 
131
- if File.exist?(channel[:local]) && !File.directory?(channel[:local])
134
+ if File.symlink?(channel[:local])
135
+ raise Net::SCP::Error, "#{channel[:local]} is a symlink, refusing to follow it"
136
+ elsif File.exist?(channel[:local]) && !File.directory?(channel[:local])
132
137
  raise Net::SCP::Error, "#{channel[:local]} already exists and is not a directory"
133
138
  elsif !File.exist?(channel[:local])
134
- Dir.mkdir(channel[:local], directive[:mode] | 0700)
139
+ Dir.mkdir(channel[:local], (directive[:mode] & 0777) | 0700)
135
140
  end
136
141
 
137
- if channel[:options][:preserve] && channel[:times]
138
- File.utime(channel[:times][:atime], channel[:times][:mtime], channel[:local])
139
- end
140
-
141
- channel[:stack] << directive
142
+ channel[:stack] << directive.merge(:times => channel[:times])
142
143
  channel[:times] = nil
143
144
  end
144
145
 
@@ -146,20 +147,54 @@ module Net; class SCP
146
147
  # actual read.
147
148
  def read_file(channel, directive)
148
149
  if !channel[:local].respond_to?(:write)
149
- directive[:name] = (channel[:options][:recursive] || File.directory?(channel[:local])) ?
150
- File.join(channel[:local], directive[:name]) :
151
- channel[:local]
150
+ server_named = channel[:options][:recursive] || File.directory?(channel[:local])
151
+ directive[:name] = server_named ? local_path_for(channel, directive[:name]) : channel[:local]
152
152
  end
153
153
 
154
154
  channel[:file] = directive.merge(:times => channel[:times])
155
+ # Setuid/setgid/sticky bits are never taken from the server, even with :preserve.
155
156
  channel[:io] = channel[:local].respond_to?(:write) ? channel[:local] :
156
- File.new(directive[:name], "wb", directive[:mode] | 0600)
157
+ open_local_file(directive[:name], (directive[:mode] & 0777) | 0600, server_named)
157
158
  channel[:times] = nil
158
159
  channel[:remaining] = channel[:file][:size]
159
160
  channel[:state] = :read_data
160
161
 
161
162
  progress_callback(channel, channel[:file][:name], 0, channel[:file][:size])
162
163
  end
164
+
165
+ # The name comes from the server, so anything but a single path component
166
+ # would let a malicious server write outside the target (CVE-2019-6111).
167
+ def local_path_for(channel, name)
168
+ if name.empty? || name == "." || name == ".." || name.include?("/") || name.include?("\0") ||
169
+ (File::ALT_SEPARATOR && name.include?(File::ALT_SEPARATOR))
170
+ raise Net::SCP::Error, "invalid file name from server: #{name.inspect}"
171
+ end
172
+
173
+ if channel[:stack].empty? && !requested_name?(channel[:remote], name)
174
+ raise Net::SCP::Error, "file name from server does not match request: #{name.inspect}"
175
+ end
176
+
177
+ File.join(channel[:local], name)
178
+ end
179
+
180
+ # OpenSSH's CVE-2019-6111 check: the flags mirror its brace expansion plus fnmatch(3) with flags 0.
181
+ def requested_name?(remote, name)
182
+ pattern = File.basename(remote.to_s)
183
+ name == pattern || File.fnmatch(pattern, name, File::FNM_DOTMATCH | File::FNM_EXTGLOB)
184
+ end
185
+
186
+ # A symlink planted at a server-named path must not redirect the write;
187
+ # the caller's own target may be a symlink on purpose (net-sftp#162).
188
+ def open_local_file(path, perm, nofollow)
189
+ return File.new(path, "wb", perm) unless nofollow && defined?(File::NOFOLLOW)
190
+
191
+ begin
192
+ File.new(path, "wb", perm, :flags => File::NOFOLLOW)
193
+ rescue SystemCallError
194
+ raise unless File.symlink?(path)
195
+ raise Net::SCP::Error, "#{path} is a symlink, refusing to follow it"
196
+ end
197
+ end
163
198
  end
164
199
 
165
200
  end; end
@@ -91,6 +91,7 @@ module Net; class SCP
91
91
  # upload and moves to #upload_current_state.
92
92
  def next_item_state(channel)
93
93
  if channel[:stack].empty?
94
+ channel[:state] = :finish
94
95
  finish_state(channel)
95
96
  else
96
97
  next_item = channel[:stack].last.shift
@@ -118,6 +119,9 @@ module Net; class SCP
118
119
  end
119
120
 
120
121
  channel[:size] = channel[:stat] ? channel[:stat].size : channel[:current].size
122
+ if channel[:current].respond_to?(:read) && channel[:current].respond_to?(:pos)
123
+ channel[:size] = [channel[:size] - channel[:current].pos, 0].max
124
+ end
121
125
  end
122
126
 
123
127
  # If the :preserve option is set, send a 'T' directive and wait for the
@@ -52,11 +52,11 @@ module Net
52
52
  MINOR = 1
53
53
 
54
54
  # The tiny component of this version of the Net::SSH library
55
- TINY = 1
55
+ TINY = 2
56
56
 
57
57
  # The prerelease component of this version of the Net::SSH library
58
58
  # nil allowed
59
- PRE = "rc3"
59
+ PRE = nil
60
60
 
61
61
  # The current version of the Net::SSH library as a Version instance
62
62
  CURRENT = new(*[MAJOR, MINOR, TINY, PRE].compact)
data/lib/net/scp.rb CHANGED
@@ -1,3 +1,4 @@
1
+ require 'pathname'
1
2
  require 'stringio'
2
3
  require 'shellwords'
3
4
 
@@ -64,7 +65,7 @@ module Net
64
65
  # puts "#{name}: #{sent}/#{total}"
65
66
  # end
66
67
  #
67
- # Whenever a new chunk of data is recieved for or sent to a file, the callback
68
+ # Whenever a new chunk of data is received for or sent to a file, the callback
68
69
  # will be invoked, indicating the name of the file (local for downloads,
69
70
  # remote for uploads), the number of bytes that have been sent or received
70
71
  # so far for the file, and the size of the file.
@@ -274,6 +275,10 @@ module Net
274
275
  # channel = scp.upload("/local/path", "/remote/path")
275
276
  # channel.wait
276
277
  def upload(local, remote, options={}, &progress)
278
+ chunk_size = options[:chunk_size] || Upload::DEFAULT_CHUNK_SIZE
279
+ unless chunk_size.is_a?(Integer) && chunk_size > 0
280
+ raise ArgumentError, ":chunk_size must be a positive Integer"
281
+ end
277
282
  start_command(:upload, local, remote, options, &progress)
278
283
  end
279
284
 
@@ -342,6 +347,7 @@ module Net
342
347
  # sets up a state machine to use to process the upload or download.
343
348
  # (See Net::SCP::Upload and Net::SCP::Download).
344
349
  def start_command(mode, local, remote, options={}, &callback)
350
+ local = local.to_path if local.is_a?(Pathname)
345
351
  session.open_channel do |channel|
346
352
 
347
353
  if options[:shell]
@@ -363,19 +369,22 @@ module Net
363
369
  channel[:error_string] = +''
364
370
 
365
371
  channel.on_close do
372
+ # The close can be dispatched before on_process has consumed data that arrived with it.
373
+ send("#{channel[:state]}_state", channel)
366
374
  # If we got an exit-status and it is not 0, something went wrong
367
375
  if !channel[:exit].nil? && channel[:exit] != 0
368
376
  raise Net::SCP::Error, 'SCP did not finish successfully ' \
369
377
  "(#{channel[:exit]}): #{channel[:error_string]}"
370
378
  end
371
- # We may get no exit-status at all as returning a status is only RECOMENDED
372
- # in RFC4254. But if our state is not :finish, something went wrong
373
- if channel[:exit].nil? && channel[:state] != :finish
374
- raise Net::SCP::Error, 'SCP did not finish successfully ' \
375
- '(channel closed before end of transmission)'
379
+ # A zero or missing exit-status does not prove the transfer completed.
380
+ if channel[:state] != :finish
381
+ message = +'SCP did not finish successfully (channel closed before end of transmission)'
382
+ message << ": #{channel[:error_string]}" unless channel[:error_string].empty?
383
+ raise Net::SCP::Error, message
376
384
  end
377
- # At this point, :state can be :finish or :next_item
378
- send("#{channel[:state]}_state", channel)
385
+ ensure
386
+ io = channel[:io]
387
+ io.close if io && io != channel[:local] && !io.closed?
379
388
  end
380
389
  channel.on_data { |ch2, data| channel[:buffer].append(data) }
381
390
  channel.on_extended_data { |ch2, type, data| debug { data.chomp } }
data/lib/uri/open-scp.rb CHANGED
@@ -8,9 +8,21 @@ module URI
8
8
 
9
9
  class SCP
10
10
  def buffer_open(buf, proxy, open_options)
11
- options = open_options.merge(:port => port, :password => password)
11
+ options = open_options.dup
12
+ options[:ssh] = { :port => port }
13
+ options[:ssh][:password] = URI::DEFAULT_PARSER.unescape(password) if password
14
+ options[:ssh].merge!(open_options[:ssh] || {})
12
15
  progress = options.delete(:progress_proc)
13
- buf << Net::SCP.download!(host, user, path, nil, options, &progress)
16
+ content_length = options.delete(:content_length_proc)
17
+ output = Object.new
18
+ output.define_singleton_method(:write) { |data| buf << data }
19
+ Net::SCP.download!(hostname, user && URI::DEFAULT_PARSER.unescape(user), URI::DEFAULT_PARSER.unescape(path), output, options) do |_channel, _name, received, total|
20
+ if content_length
21
+ content_length.call(total)
22
+ content_length = nil
23
+ end
24
+ progress.call(received) if progress
25
+ end
14
26
  buf.io.rewind
15
27
  end
16
28
 
data/lib/uri/scp.rb CHANGED
@@ -11,8 +11,6 @@ module URI
11
11
  :query
12
12
  ].freeze
13
13
 
14
- attr_reader :options
15
-
16
14
  def self.new2(user, password, host, port, path, query)
17
15
  new('scp', [user, password], host, port, nil, path, nil, query)
18
16
  end
@@ -20,14 +18,23 @@ module URI
20
18
  def initialize(*args)
21
19
  super(*args)
22
20
 
21
+ options
22
+ end
23
+
24
+ def options
25
+ return @options if defined?(@options_query) && @options_query == query
26
+
27
+ @options_query = query && query.dup
23
28
  @options = Hash.new
24
29
  (query || "").split(/&/).each do |pair|
30
+ next if pair.empty?
25
31
  name, value = pair.split(/=/, 2)
26
32
  opt_name = name.to_sym
27
- values = value.split(/,/).map { |v| v.to_i.to_s == v ? v.to_i : v }
33
+ values = value.to_s.split(/,/).map { |v| v.to_i.to_s == v ? v.to_i : v }
28
34
  values = values.first if values.length == 1
29
- options[opt_name] = values
35
+ @options[opt_name] = values
30
36
  end
37
+ @options
31
38
  end
32
39
  end
33
40
 
data/net-scp.gemspec CHANGED
@@ -33,16 +33,16 @@ Gem::Specification.new do |spec|
33
33
  spec.specification_version = 3
34
34
 
35
35
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
36
- spec.add_runtime_dependency(%q<net-ssh>, [">= 2.6.5", "< 8.0.0"])
36
+ spec.add_runtime_dependency(%q<net-ssh>, [">= 2.6.5", "< 9.0.0"])
37
37
  spec.add_development_dependency(%q<test-unit>, [">= 0"])
38
38
  spec.add_development_dependency(%q<mocha>, [">= 0"])
39
39
  else
40
- spec.add_dependency(%q<net-ssh>, [">= 2.6.5", "< 8.0.0"])
40
+ spec.add_dependency(%q<net-ssh>, [">= 2.6.5", "< 9.0.0"])
41
41
  spec.add_dependency(%q<test-unit>, [">= 0"])
42
42
  spec.add_dependency(%q<mocha>, [">= 0"])
43
43
  end
44
44
  else
45
- spec.add_dependency(%q<net-ssh>, [">= 2.6.5", "< 8.0.0"])
45
+ spec.add_dependency(%q<net-ssh>, [">= 2.6.5", "< 9.0.0"])
46
46
  spec.add_dependency(%q<test-unit>, [">= 0"])
47
47
  spec.add_dependency(%q<mocha>, [">= 0"])
48
48
  end
data/setup.rb CHANGED
@@ -120,7 +120,7 @@ class ConfigTable
120
120
  ((major == 1) and
121
121
  ((minor >= 5) or
122
122
  ((minor == 4) and (teeny >= 4)))))
123
-
123
+
124
124
  subprefix = lambda {|path|
125
125
  path.sub(/\A#{Regexp.quote(c['prefix'])}/o, '$prefix')
126
126
  }
@@ -172,7 +172,7 @@ class ConfigTable
172
172
  'the directory for ruby scripts' ] ],
173
173
  [ 'so-dir', [ sodir,
174
174
  'path',
175
- 'the directory for ruby extentions' ] ],
175
+ 'the directory for ruby extensions' ] ],
176
176
  [ 'data-dir', [ '$prefix/share',
177
177
  'path',
178
178
  'the directory for shared data' ] ],
@@ -184,10 +184,10 @@ class ConfigTable
184
184
  'the ruby program using for installation' ] ],
185
185
  [ 'make-prog', [ makeprog,
186
186
  'name',
187
- 'the make program to compile ruby extentions' ] ],
187
+ 'the make program to compile ruby extensions' ] ],
188
188
  [ 'without-ext', [ 'no',
189
189
  'yes/no',
190
- 'does not compile/install ruby extentions' ] ]
190
+ 'does not compile/install ruby extensions' ] ]
191
191
  ]
192
192
  multipackage_descripters = [
193
193
  [ 'with', [ '',
@@ -303,7 +303,7 @@ class ConfigTable
303
303
  unless ConfigTable.config_key?(k)
304
304
  @table[k] = v
305
305
  end
306
-
306
+
307
307
  def [](key)
308
308
  return nil unless @table[key]
309
309
  @table[key].gsub(%r<\$([^/]+)>) { self[$1] }
@@ -479,7 +479,7 @@ module FileOperations
479
479
  def ruby(str)
480
480
  command config('ruby-prog') + ' ' + str
481
481
  end
482
-
482
+
483
483
  def make(task = '')
484
484
  command config('make-prog') + ' ' + task
485
485
  end
@@ -572,7 +572,7 @@ module HookScriptAPI
572
572
  def srcdirectory?(path)
573
573
  File.dir?(srcfile(path))
574
574
  end
575
-
575
+
576
576
  def srcfile?(path)
577
577
  File.file? srcfile(path)
578
578
  end
@@ -606,10 +606,10 @@ class ToplevelInstaller
606
606
  TASKS = [
607
607
  [ 'config', 'saves your configurations' ],
608
608
  [ 'show', 'shows current configuration' ],
609
- [ 'setup', 'compiles ruby extentions and others' ],
609
+ [ 'setup', 'compiles ruby extensions and others' ],
610
610
  [ 'install', 'installs files' ],
611
- [ 'clean', "does `make clean' for each extention" ],
612
- [ 'distclean',"does `make distclean' for each extention" ]
611
+ [ 'clean', "does `make clean' for each extension" ],
612
+ [ 'distclean',"does `make distclean' for each extension" ]
613
613
  ]
614
614
 
615
615
  def ToplevelInstaller.invoke
@@ -708,7 +708,7 @@ class ToplevelInstaller
708
708
  when '-v', '--version'
709
709
  puts "#{File.basename($0)} version #{Version}"
710
710
  exit 0
711
-
711
+
712
712
  when '--copyright'
713
713
  puts Copyright
714
714
  exit 0
@@ -911,7 +911,7 @@ class ToplevelInstallerMulti < ToplevelInstaller
911
911
 
912
912
  def print_usage(f)
913
913
  super
914
- f.puts 'Inluded packages:'
914
+ f.puts 'Included packages:'
915
915
  f.puts ' ' + @packages.sort.join(' ')
916
916
  f.puts
917
917
  end
@@ -1146,7 +1146,7 @@ class Installer
1146
1146
 
1147
1147
  def install_dir_ext(rel)
1148
1148
  return unless extdir?(curr_srcdir())
1149
- install_files ruby_extentions('.'),
1149
+ install_files ruby_extensions('.'),
1150
1150
  "#{config('so-dir')}/#{File.dirname(rel)}",
1151
1151
  0555
1152
1152
  end
@@ -1165,9 +1165,9 @@ class Installer
1165
1165
  def ruby_scripts
1166
1166
  collect_filenames_auto().select {|n| /\.rb\z/ =~ n || "module.yml" == n }
1167
1167
  end
1168
-
1168
+
1169
1169
  # picked up many entries from cvs-1.11.1/src/ignore.c
1170
- reject_patterns = %w(
1170
+ reject_patterns = %w(
1171
1171
  core RCSLOG tags TAGS .make.state
1172
1172
  .nse_depinfo #* .#* cvslog.* ,* .del-* *.olb
1173
1173
  *~ *.old *.bak *.BAK *.orig *.rej _$* *$
@@ -1212,14 +1212,14 @@ class Installer
1212
1212
  }
1213
1213
  end
1214
1214
 
1215
- def ruby_extentions(dir)
1216
- _ruby_extentions(dir) or
1217
- raise InstallError, "no ruby extention exists: 'ruby #{$0} setup' first"
1215
+ def ruby_extensions(dir)
1216
+ _ruby_extensions(dir) or
1217
+ raise InstallError, "no ruby extension exists: 'ruby #{$0} setup' first"
1218
1218
  end
1219
1219
 
1220
1220
  DLEXT = /\.#{ ::Config::CONFIG['DLEXT'] }\z/
1221
1221
 
1222
- def _ruby_extentions(dir)
1222
+ def _ruby_extensions(dir)
1223
1223
  Dir.open(dir) {|d|
1224
1224
  return d.select {|fname| DLEXT =~ fname }
1225
1225
  }
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: net-scp
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.1.1.rc3
4
+ version: 4.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jamis Buck
@@ -32,7 +32,7 @@ cert_chain:
32
32
  vNiMFH5yLu/tlav4xh+HlYscWVdFQIvd7f/Uxa+Vip1Pi16wmhnrE15z/vDX921v
33
33
  hukBRIoCEAuUgNVBh+L/C8aETzbf/77WU3Si/w==
34
34
  -----END CERTIFICATE-----
35
- date: 2026-05-16 00:00:00.000000000 Z
35
+ date: 2026-10-25 00:00:00.000000000 Z
36
36
  dependencies:
37
37
  - !ruby/object:Gem::Dependency
38
38
  name: net-ssh
@@ -43,7 +43,7 @@ dependencies:
43
43
  version: 2.6.5
44
44
  - - "<"
45
45
  - !ruby/object:Gem::Version
46
- version: 8.0.0
46
+ version: 9.0.0
47
47
  type: :runtime
48
48
  prerelease: false
49
49
  version_requirements: !ruby/object:Gem::Requirement
@@ -53,7 +53,7 @@ dependencies:
53
53
  version: 2.6.5
54
54
  - - "<"
55
55
  - !ruby/object:Gem::Version
56
- version: 8.0.0
56
+ version: 9.0.0
57
57
  - !ruby/object:Gem::Dependency
58
58
  name: test-unit
59
59
  requirement: !ruby/object:Gem::Requirement
@@ -91,15 +91,19 @@ extra_rdoc_files:
91
91
  - LICENSE.txt
92
92
  - README.md
93
93
  files:
94
+ - ".github/pull_request_template.md"
95
+ - ".github/workflows/changelog.yml"
94
96
  - ".github/workflows/ci.yml"
95
97
  - ".gitignore"
96
98
  - CHANGES.txt
99
+ - CONTRIBUTING.md
97
100
  - Gemfile
98
101
  - LICENSE.txt
99
102
  - Manifest
100
103
  - README.md
101
104
  - Rakefile
102
105
  - SECURITY.md
106
+ - changelog.d/README.md
103
107
  - lib/net/scp.rb
104
108
  - lib/net/scp/download.rb
105
109
  - lib/net/scp/errors.rb
metadata.gz.sig CHANGED
Binary file