net-scp 4.1.2 → 4.2.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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data/CHANGES.txt +12 -0
- data/README.md +18 -0
- data/lib/net/scp/upload.rb +25 -1
- data/lib/net/scp/version.rb +2 -2
- data/lib/net/scp.rb +82 -25
- data.tar.gz.sig +1 -2
- metadata +3 -16
- metadata.gz.sig +0 -0
- data/.github/pull_request_template.md +0 -1
- data/.github/workflows/changelog.yml +0 -18
- data/.github/workflows/ci.yml +0 -25
- data/.gitignore +0 -13
- data/CONTRIBUTING.md +0 -33
- data/Gemfile +0 -22
- data/Manifest +0 -17
- data/Rakefile +0 -185
- data/SECURITY.md +0 -4
- data/changelog.d/README.md +0 -1
- data/net-scp-public_cert.pem +0 -21
- data/net-scp.gemspec +0 -49
- data/setup.rb +0 -1331
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b8d6b4ea9428a9cc3df98e388eb80ce0a6dd45021bad757992537f24dc954d38
|
|
4
|
+
data.tar.gz: 72ada8e9dc44cf7d7ffad414ccf8dfa90f1f3f875e83400acc6293e9523d8b03
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 76879f624878414b25e51abe5cfc88d078a99db6750b3b7c816454cae23c4d1d988201dd56af3ac3df21ac7b9c575a160c9eca44ccb731bfa332a6c3fe43651a
|
|
7
|
+
data.tar.gz: 41880eab49fcce9ae5e1f251f2360843941598b96496d26b33770449d53bf1b2ea15ef706af37d5af83370c57e0c878815d523eca2b974274dce43288953e318
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
data/CHANGES.txt
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
=== 4.2.0
|
|
2
|
+
|
|
3
|
+
* BREAKING: Require Ruby 2.6 or later. Bundler on Ruby 2.3-2.5 resolves to 4.1.3. [#114]
|
|
4
|
+
* Package only `lib`, `CHANGES.txt`, `LICENSE.txt` and `README.md` in the gem, about 40% smaller. Thanks @yuri-zubov [#118]
|
|
5
|
+
* An exception raised during a transfer, such as `upload!` of a missing local file or an error from a progress callback, no longer leaves the channel registered and re-raises on later operations on the same session; the original error is raised once, library-opened files are closed and caller-passed IO is left open. Thanks @ThomasSevestre and @OskarEichler [#107, #26, #97]
|
|
6
|
+
|
|
7
|
+
=== 4.1.3
|
|
8
|
+
|
|
9
|
+
* Load again on Ruby 2.3-2.5 (4.1.2 needed 2.5), and declare `required_ruby_version >= 2.3` [#112, #110]
|
|
10
|
+
* Add a `quote:` option to `upload`/`download` for the remote path: `:posix` (default), `:windows` for Win32-OpenSSH under cmd.exe or PowerShell (double-quoted, `\` sent as `/`, paths containing `"`, `$`, `` ` ``, `%`, `!`, curly quotes, CR, LF or NUL rejected), `:none` to send the path unchanged to servers without a shell, or a callable. By default a leading `~` or `~/` is now left for the remote shell to expand to the home directory; such paths previously referred to a literal `~` entry in the login directory and usually failed. Thanks @ArtsiomMusin for the Windows patch in #34 [#109, #34, #9]
|
|
11
|
+
* Recursive uploads no longer abort on a broken symlink: it is skipped with a warning, the rest is uploaded, and a Net::SCP::Error listing the skipped paths is raised at the end, like OpenSSH scp. Based on #23 by @maage. [#108, #23]
|
|
12
|
+
|
|
1
13
|
=== 4.1.2
|
|
2
14
|
|
|
3
15
|
* 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]
|
data/README.md
CHANGED
|
@@ -69,6 +69,24 @@ require 'uri/open-scp'
|
|
|
69
69
|
data = open("scp://user@host/path/to/file.txt").read
|
|
70
70
|
```
|
|
71
71
|
|
|
72
|
+
The remote path is escaped for a POSIX shell, except that a leading `~` or
|
|
73
|
+
`~/` is left for the remote shell to expand to the home directory (`~user/` is
|
|
74
|
+
not expanded). Use the `quote:` option of `upload`/`download` for other
|
|
75
|
+
servers:
|
|
76
|
+
|
|
77
|
+
```ruby
|
|
78
|
+
# Win32-OpenSSH (cmd.exe or PowerShell): the path is double-quoted and "\" becomes "/".
|
|
79
|
+
# Paths containing ", $, `, %, !, curly quotes, CR, LF or NUL raise ArgumentError.
|
|
80
|
+
scp.upload! "/local/path", "C:\\Program Files\\App\\file.txt", quote: :windows
|
|
81
|
+
|
|
82
|
+
# Servers that don't run a shell (MikroTik and other appliances): the path is
|
|
83
|
+
# sent unchanged. Never use this with untrusted paths.
|
|
84
|
+
scp.download! "flash/backup.rsc", "/local/backup.rsc", quote: :none
|
|
85
|
+
|
|
86
|
+
# Or quote it yourself
|
|
87
|
+
scp.upload! "/local/path", remote, quote: ->(path) { "'#{path}'" }
|
|
88
|
+
```
|
|
89
|
+
|
|
72
90
|
For more information, see Net::SCP.
|
|
73
91
|
|
|
74
92
|
## REQUIREMENTS:
|
data/lib/net/scp/upload.rb
CHANGED
|
@@ -49,7 +49,8 @@ module Net; class SCP
|
|
|
49
49
|
directive = "D%04o %d %s\n" % [mode, 0, File.basename(channel[:current])]
|
|
50
50
|
channel.send_data(directive)
|
|
51
51
|
channel[:cwd] = channel[:current]
|
|
52
|
-
|
|
52
|
+
entries = Dir.entries(channel[:current]).reject { |i| i == "." || i == ".." }
|
|
53
|
+
channel[:stack] << entries.reject { |i| skip_broken_symlink(channel, File.join(channel[:current], i)) }
|
|
53
54
|
await_response(channel, :next_item)
|
|
54
55
|
end
|
|
55
56
|
end
|
|
@@ -124,6 +125,29 @@ module Net; class SCP
|
|
|
124
125
|
end
|
|
125
126
|
end
|
|
126
127
|
|
|
128
|
+
# Returns true if +path+ is a symlink whose target doesn't exist, after
|
|
129
|
+
# logging a warning and recording it for #raise_if_entries_skipped.
|
|
130
|
+
def skip_broken_symlink(channel, path)
|
|
131
|
+
return false unless File.symlink?(path) && !File.exist?(path)
|
|
132
|
+
|
|
133
|
+
lwarn { "#{path}: broken symlink, skipping" }
|
|
134
|
+
(channel[:skipped] ||= []) << path
|
|
135
|
+
true
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Once the remote has closed the channel, raises a Net::SCP::Error listing
|
|
139
|
+
# the entries skipped by #skip_broken_symlink, if any.
|
|
140
|
+
def raise_if_entries_skipped(channel)
|
|
141
|
+
return unless channel.closing? && channel[:skipped]
|
|
142
|
+
|
|
143
|
+
# Cleared so it raises once, as on_process keeps calling this until the close is flushed.
|
|
144
|
+
skipped, channel[:skipped] = channel[:skipped], nil
|
|
145
|
+
# A failed remote scp is reported by start_command's exit-status check instead.
|
|
146
|
+
return unless channel[:exit].to_i.zero?
|
|
147
|
+
|
|
148
|
+
raise Net::SCP::Error, "upload incomplete, skipped broken symlinks: #{skipped.map(&:inspect).join(', ')}"
|
|
149
|
+
end
|
|
150
|
+
|
|
127
151
|
# If the :preserve option is set, send a 'T' directive and wait for the
|
|
128
152
|
# server to respond before proceeding to either #upload_file_state or
|
|
129
153
|
# #upload_directory_state, depending on what is being uploaded.
|
data/lib/net/scp/version.rb
CHANGED
|
@@ -49,10 +49,10 @@ module Net
|
|
|
49
49
|
MAJOR = 4
|
|
50
50
|
|
|
51
51
|
# The minor component of this version of the Net::SSH library
|
|
52
|
-
MINOR =
|
|
52
|
+
MINOR = 2
|
|
53
53
|
|
|
54
54
|
# The tiny component of this version of the Net::SSH library
|
|
55
|
-
TINY =
|
|
55
|
+
TINY = 0
|
|
56
56
|
|
|
57
57
|
# The prerelease component of this version of the Net::SSH library
|
|
58
58
|
# nil allowed
|
data/lib/net/scp.rb
CHANGED
|
@@ -259,13 +259,24 @@ module Net
|
|
|
259
259
|
#
|
|
260
260
|
# * :recursive - the +local+ parameter refers to a local directory, which
|
|
261
261
|
# should be uploaded to a new directory named +remote+ on the remote
|
|
262
|
-
# server.
|
|
262
|
+
# server. Broken symlinks in it are skipped with a warning, and once
|
|
263
|
+
# everything else is uploaded a Net::SCP::Error listing them is raised.
|
|
263
264
|
# * :preserve - the atime and mtime of the file should be preserved.
|
|
264
265
|
# * :verbose - the process should result in verbose output on the server
|
|
265
266
|
# end (useful for debugging).
|
|
266
267
|
# * :chunk_size - the size of each "chunk" that should be sent. Defaults
|
|
267
268
|
# to 2048. Changing this value may improve throughput at the expense
|
|
268
269
|
# of decreasing interactivity.
|
|
270
|
+
# * :quote - how +remote+ is quoted in the scp command run by the remote
|
|
271
|
+
# shell. :posix (the default) escapes it for a POSIX shell, leaving a
|
|
272
|
+
# leading "~" or "~/" unescaped so it expands to the remote home
|
|
273
|
+
# directory ("~user/" is not expanded). :windows is for Win32-OpenSSH,
|
|
274
|
+
# whose shell may be cmd.exe or PowerShell: backslashes become "/", the
|
|
275
|
+
# path is wrapped in double quotes, and paths containing a double or curly
|
|
276
|
+
# quote, $, `, %, !, CR, LF or NUL raise ArgumentError. :none sends the
|
|
277
|
+
# path unchanged, for servers that don't run a shell (some routers and
|
|
278
|
+
# appliances); never use it with untrusted input. A callable is given
|
|
279
|
+
# +remote+ and returns the string to send.
|
|
269
280
|
#
|
|
270
281
|
# This method will return immediately, returning the Net::SSH::Connection::Channel
|
|
271
282
|
# object that will support the upload. To wait for the upload to finish,
|
|
@@ -298,6 +309,7 @@ module Net
|
|
|
298
309
|
# * :preserve - the atime and mtime of the file should be preserved.
|
|
299
310
|
# * :verbose - the process should result in verbose output on the server
|
|
300
311
|
# end (useful for debugging).
|
|
312
|
+
# * :quote - how +remote+ is quoted for the remote shell (see #upload).
|
|
301
313
|
#
|
|
302
314
|
# This method will return immediately, returning the Net::SSH::Connection::Channel
|
|
303
315
|
# object that will support the download. To wait for the download to finish,
|
|
@@ -348,15 +360,14 @@ module Net
|
|
|
348
360
|
# (See Net::SCP::Upload and Net::SCP::Download).
|
|
349
361
|
def start_command(mode, local, remote, options={}, &callback)
|
|
350
362
|
local = local.to_path if local.is_a?(Pathname)
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
command = "#{scp_command(mode, options)} #{shellescape remote}"
|
|
358
|
-
end
|
|
363
|
+
# Kept in channel[:remote] too, so a download's basename check sees "C:/a/b.txt" as b.txt.
|
|
364
|
+
remote = remote.to_s.tr("\\", "/") if options[:quote] == :windows
|
|
365
|
+
command = "#{scp_command(mode, options)} #{quote_remote(remote, options[:quote] || :posix)}"
|
|
366
|
+
if options[:shell]
|
|
367
|
+
command = "#{options[:shell]} -c '#{command.gsub("'") { "'\\''" }}'"
|
|
368
|
+
end
|
|
359
369
|
|
|
370
|
+
session.open_channel do |channel|
|
|
360
371
|
channel.exec(command) do |ch, success|
|
|
361
372
|
if success
|
|
362
373
|
channel[:local ] = local
|
|
@@ -369,27 +380,30 @@ module Net
|
|
|
369
380
|
channel[:error_string] = +''
|
|
370
381
|
|
|
371
382
|
channel.on_close do
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
383
|
+
begin
|
|
384
|
+
next if channel[:aborted]
|
|
385
|
+
|
|
386
|
+
# The close can be dispatched before on_process has consumed data that arrived with it.
|
|
387
|
+
process_state(channel)
|
|
388
|
+
# If we got an exit-status and it is not 0, something went wrong
|
|
389
|
+
if !channel[:exit].nil? && channel[:exit] != 0
|
|
390
|
+
raise Net::SCP::Error, 'SCP did not finish successfully ' \
|
|
391
|
+
"(#{channel[:exit]}): #{channel[:error_string]}"
|
|
392
|
+
end
|
|
393
|
+
# A zero or missing exit-status does not prove the transfer completed.
|
|
394
|
+
if channel[:state] != :finish
|
|
395
|
+
message = +'SCP did not finish successfully (channel closed before end of transmission)'
|
|
396
|
+
message << ": #{channel[:error_string]}" unless channel[:error_string].empty?
|
|
397
|
+
raise Net::SCP::Error, message
|
|
398
|
+
end
|
|
399
|
+
ensure
|
|
400
|
+
close_owned_io(channel)
|
|
384
401
|
end
|
|
385
|
-
ensure
|
|
386
|
-
io = channel[:io]
|
|
387
|
-
io.close if io && io != channel[:local] && !io.closed?
|
|
388
402
|
end
|
|
389
403
|
channel.on_data { |ch2, data| channel[:buffer].append(data) }
|
|
390
404
|
channel.on_extended_data { |ch2, type, data| debug { data.chomp } }
|
|
391
405
|
channel.on_request("exit-status") { |ch2, data| channel[:exit] = data.read_long }
|
|
392
|
-
channel.on_process {
|
|
406
|
+
channel.on_process { process_state(channel) unless channel[:aborted] }
|
|
393
407
|
else
|
|
394
408
|
channel.close
|
|
395
409
|
raise Net::SCP::Error, "could not exec scp on the remote host"
|
|
@@ -398,6 +412,25 @@ module Net
|
|
|
398
412
|
end
|
|
399
413
|
end
|
|
400
414
|
|
|
415
|
+
# Runs the action for the current state. If it raises, the transfer is
|
|
416
|
+
# aborted and the channel closed, so the error surfaces once and the
|
|
417
|
+
# session stays usable.
|
|
418
|
+
def process_state(channel)
|
|
419
|
+
send("#{channel[:state]}_state", channel)
|
|
420
|
+
rescue Exception
|
|
421
|
+
channel[:aborted] = true
|
|
422
|
+
close_owned_io(channel)
|
|
423
|
+
channel.close
|
|
424
|
+
raise
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
# Closes the IO opened by the library; an IO passed in by the caller is
|
|
428
|
+
# left open.
|
|
429
|
+
def close_owned_io(channel)
|
|
430
|
+
io = channel[:io]
|
|
431
|
+
io.close if io && io != channel[:local] && !io.closed?
|
|
432
|
+
end
|
|
433
|
+
|
|
401
434
|
# Causes the state machine to enter the "await response" state, where
|
|
402
435
|
# things just pause until the server replies with a 0 (see
|
|
403
436
|
# #await_response_state), at which point the state machine will pick up
|
|
@@ -428,6 +461,7 @@ module Net
|
|
|
428
461
|
# of the pipe, and allows the pipe to drain until the server closes it.
|
|
429
462
|
def finish_state(channel)
|
|
430
463
|
channel.eof!
|
|
464
|
+
raise_if_entries_skipped(channel)
|
|
431
465
|
end
|
|
432
466
|
|
|
433
467
|
# Invoked to report progress back to the client. If a callback was not
|
|
@@ -436,6 +470,29 @@ module Net
|
|
|
436
470
|
channel[:callback].call(channel, name, sent, total) if channel[:callback]
|
|
437
471
|
end
|
|
438
472
|
|
|
473
|
+
# Inside double quotes cmd.exe still expands % and !, PowerShell expands $ and `, and PowerShell also ends a string at U+2018..U+201E.
|
|
474
|
+
WINDOWS_UNSAFE = /["$`%!\r\n\0\u2018-\u201E]/
|
|
475
|
+
|
|
476
|
+
def quote_remote(remote, quote)
|
|
477
|
+
case quote
|
|
478
|
+
when :posix
|
|
479
|
+
remote = remote.to_s
|
|
480
|
+
return remote if remote == "~" || remote == "~/"
|
|
481
|
+
return "~/#{shellescape(remote[2..-1])}" if remote.start_with?("~/")
|
|
482
|
+
shellescape(remote)
|
|
483
|
+
when :windows
|
|
484
|
+
if remote.dup.force_encoding(Encoding::UTF_8) =~ WINDOWS_UNSAFE
|
|
485
|
+
raise ArgumentError, "remote path #{remote.inspect} contains a character that can't be quoted safely for a Windows shell"
|
|
486
|
+
end
|
|
487
|
+
"\"#{remote}\""
|
|
488
|
+
when :none
|
|
489
|
+
remote.to_s
|
|
490
|
+
else
|
|
491
|
+
raise ArgumentError, ":quote must be :posix, :windows, :none or a callable, got #{quote.inspect}" unless quote.respond_to?(:call)
|
|
492
|
+
quote.call(remote).to_s
|
|
493
|
+
end
|
|
494
|
+
end
|
|
495
|
+
|
|
439
496
|
# Imported from ruby 1.9.2 shellwords.rb
|
|
440
497
|
def shellescape(path)
|
|
441
498
|
# Convert path to a string if it isn't already one.
|
data.tar.gz.sig
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
S#6 &�t����\�+��tBk�����P�P�g��{s����j����Nί�q+��5D7�(�^�ʘ��)�x0.���$�m�T f~:��%%{P��b��GO!��V#���i�7c���"���8L3;��HB�j���3�T���e �$:�A�?C
|
|
1
|
+
T.7B����4�
|
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.
|
|
4
|
+
version: 4.2.0
|
|
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-10-
|
|
35
|
+
date: 2026-10-29 00:00:00.000000000 Z
|
|
36
36
|
dependencies:
|
|
37
37
|
- !ruby/object:Gem::Dependency
|
|
38
38
|
name: net-ssh
|
|
@@ -91,19 +91,9 @@ 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"
|
|
96
|
-
- ".github/workflows/ci.yml"
|
|
97
|
-
- ".gitignore"
|
|
98
94
|
- CHANGES.txt
|
|
99
|
-
- CONTRIBUTING.md
|
|
100
|
-
- Gemfile
|
|
101
95
|
- LICENSE.txt
|
|
102
|
-
- Manifest
|
|
103
96
|
- README.md
|
|
104
|
-
- Rakefile
|
|
105
|
-
- SECURITY.md
|
|
106
|
-
- changelog.d/README.md
|
|
107
97
|
- lib/net/scp.rb
|
|
108
98
|
- lib/net/scp/download.rb
|
|
109
99
|
- lib/net/scp/errors.rb
|
|
@@ -111,9 +101,6 @@ files:
|
|
|
111
101
|
- lib/net/scp/version.rb
|
|
112
102
|
- lib/uri/open-scp.rb
|
|
113
103
|
- lib/uri/scp.rb
|
|
114
|
-
- net-scp-public_cert.pem
|
|
115
|
-
- net-scp.gemspec
|
|
116
|
-
- setup.rb
|
|
117
104
|
homepage: https://github.com/net-ssh/net-scp
|
|
118
105
|
licenses:
|
|
119
106
|
- MIT
|
|
@@ -127,7 +114,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
127
114
|
requirements:
|
|
128
115
|
- - ">="
|
|
129
116
|
- !ruby/object:Gem::Version
|
|
130
|
-
version: '
|
|
117
|
+
version: '2.6'
|
|
131
118
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
132
119
|
requirements:
|
|
133
120
|
- - ">="
|
metadata.gz.sig
CHANGED
|
Binary file
|
|
@@ -1 +0,0 @@
|
|
|
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. -->
|
|
@@ -1,18 +0,0 @@
|
|
|
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
|
data/.github/workflows/ci.yml
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
name: CI
|
|
2
|
-
on:
|
|
3
|
-
pull_request:
|
|
4
|
-
push: { branches: master }
|
|
5
|
-
permissions:
|
|
6
|
-
contents: read
|
|
7
|
-
jobs:
|
|
8
|
-
test:
|
|
9
|
-
runs-on: ubuntu-latest
|
|
10
|
-
strategy:
|
|
11
|
-
fail-fast: false
|
|
12
|
-
matrix:
|
|
13
|
-
ruby-version: ['2.6', '2.7', '3.0', '3.1', '3.2', '3.3', '3.4', '4.0', 'ruby-head']
|
|
14
|
-
continue-on-error: ${{ matrix.ruby-version == 'ruby-head' }}
|
|
15
|
-
steps:
|
|
16
|
-
- uses: actions/checkout@v3
|
|
17
|
-
- name: Set up Ruby ${{ matrix.ruby-version }}
|
|
18
|
-
uses: ruby/setup-ruby@v1
|
|
19
|
-
with:
|
|
20
|
-
ruby-version: ${{ matrix.ruby-version }}
|
|
21
|
-
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
|
|
22
|
-
- name: Run Tests
|
|
23
|
-
run: bundle exec rake test
|
|
24
|
-
env:
|
|
25
|
-
RUBYOPT: "--enable=frozen_string_literal"
|
data/.gitignore
DELETED
data/CONTRIBUTING.md
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
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
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
source 'https://rubygems.org'
|
|
2
|
-
|
|
3
|
-
# Specify your gem's dependencies in mygem.gemspec
|
|
4
|
-
gemspec
|
|
5
|
-
|
|
6
|
-
gem "net-ssh", ">= 7.3.1"
|
|
7
|
-
gem "logger" # no longer a default gem in Ruby 4.0+
|
|
8
|
-
|
|
9
|
-
# TODO: add to gemspec
|
|
10
|
-
gem "bundler", ">= 1.11"
|
|
11
|
-
gem "rake", ">= 12.0"
|
|
12
|
-
gem "rdoc"
|
|
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
|
-
|
|
17
|
-
gem 'byebug', group: %i[development test] if !Gem.win_platform? && RUBY_ENGINE == "ruby"
|
|
18
|
-
|
|
19
|
-
if ENV["CI"]
|
|
20
|
-
gem 'codecov', require: false, group: :test
|
|
21
|
-
gem 'simplecov', require: false, group: :test
|
|
22
|
-
end
|
data/Manifest
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
CHANGELOG.rdoc
|
|
2
|
-
lib/net/scp/download.rb
|
|
3
|
-
lib/net/scp/errors.rb
|
|
4
|
-
lib/net/scp/upload.rb
|
|
5
|
-
lib/net/scp/version.rb
|
|
6
|
-
lib/net/scp.rb
|
|
7
|
-
lib/uri/open-scp.rb
|
|
8
|
-
lib/uri/scp.rb
|
|
9
|
-
Rakefile
|
|
10
|
-
README.md
|
|
11
|
-
setup.rb
|
|
12
|
-
test/common.rb
|
|
13
|
-
test/test_all.rb
|
|
14
|
-
test/test_download.rb
|
|
15
|
-
test/test_scp.rb
|
|
16
|
-
test/test_upload.rb
|
|
17
|
-
Manifest
|
data/Rakefile
DELETED
|
@@ -1,185 +0,0 @@
|
|
|
1
|
-
require "rubygems"
|
|
2
|
-
require "rake"
|
|
3
|
-
require "rake/clean"
|
|
4
|
-
begin
|
|
5
|
-
require "rdoc/task"
|
|
6
|
-
rescue LoadError
|
|
7
|
-
end
|
|
8
|
-
require "bundler/gem_tasks"
|
|
9
|
-
|
|
10
|
-
desc "When releasing make sure NET_SSH_BUILDGEM_SIGNED is set"
|
|
11
|
-
task :check_NET_SSH_BUILDGEM_SIGNED do
|
|
12
|
-
raise "NET_SSH_BUILDGEM_SIGNED should be set to release" unless ENV['NET_SSH_BUILDGEM_SIGNED']
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
Rake::Task[:release].enhance [:check_NET_SSH_BUILDGEM_SIGNED]
|
|
16
|
-
Rake::Task[:release].prerequisites.unshift(:check_NET_SSH_BUILDGEM_SIGNED)
|
|
17
|
-
|
|
18
|
-
task default: ["build"]
|
|
19
|
-
CLEAN.include [ 'pkg', 'rdoc' ]
|
|
20
|
-
name = "net-scp"
|
|
21
|
-
|
|
22
|
-
require_relative "lib/net/scp/version"
|
|
23
|
-
version = Net::SCP::Version::CURRENT
|
|
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
|
-
|
|
35
|
-
namespace :cert do
|
|
36
|
-
desc "Update public cert from private - only run if public is expired"
|
|
37
|
-
task :update_public_when_expired do
|
|
38
|
-
require 'openssl'
|
|
39
|
-
require 'time'
|
|
40
|
-
raw = File.read "net-scp-public_cert.pem"
|
|
41
|
-
certificate = OpenSSL::X509::Certificate.new raw
|
|
42
|
-
raise Exception, "Not yet expired: #{certificate.not_after}" unless certificate.not_after < Time.now
|
|
43
|
-
sh "gem cert --build netssh@solutious.com --days 365*5 --private-key /mnt/gem/net-ssh-private_key.pem"
|
|
44
|
-
sh "mv gem-public_cert.pem net-scp-public_cert.pem"
|
|
45
|
-
sh "gem cert --add net-scp-public_cert.pem"
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
if false
|
|
50
|
-
begin
|
|
51
|
-
require "jeweler"
|
|
52
|
-
Jeweler::Tasks.new do |s|
|
|
53
|
-
s.version = version
|
|
54
|
-
s.name = name
|
|
55
|
-
s.summary = "A pure Ruby implementation of the SCP client protocol"
|
|
56
|
-
s.description = s.summary
|
|
57
|
-
s.email = "net-ssh@solutious.com"
|
|
58
|
-
s.homepage = "https://github.com/net-ssh/net-scp"
|
|
59
|
-
s.authors = ["Jamis Buck", "Delano Mandelbaum"]
|
|
60
|
-
|
|
61
|
-
s.add_dependency 'net-ssh', ">=2.6.5"
|
|
62
|
-
|
|
63
|
-
s.add_development_dependency 'test-unit'
|
|
64
|
-
s.add_development_dependency 'mocha'
|
|
65
|
-
|
|
66
|
-
s.license = "MIT"
|
|
67
|
-
|
|
68
|
-
s.signing_key = File.join('/mnt/gem/', 'gem-private_key.pem')
|
|
69
|
-
s.cert_chain = ['gem-public_cert.pem']
|
|
70
|
-
end
|
|
71
|
-
Jeweler::GemcutterTasks.new
|
|
72
|
-
rescue LoadError
|
|
73
|
-
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
|
74
|
-
end
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
require 'rake/testtask'
|
|
78
|
-
Rake::TestTask.new do |t|
|
|
79
|
-
t.libs = ["lib", "test"]
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
extra_files = %w[LICENSE.txt THANKS.txt CHANGES.txt ]
|
|
83
|
-
if defined?(RDoc::Task)
|
|
84
|
-
RDoc::Task.new do |rdoc|
|
|
85
|
-
rdoc.rdoc_dir = "rdoc"
|
|
86
|
-
rdoc.title = "#{name} #{version}"
|
|
87
|
-
rdoc.generator = 'hanna' # gem install hanna-nouveau
|
|
88
|
-
rdoc.main = 'README.md'
|
|
89
|
-
rdoc.rdoc_files.include("README*")
|
|
90
|
-
rdoc.rdoc_files.include("bin/*.rb")
|
|
91
|
-
rdoc.rdoc_files.include("lib/**/*.rb")
|
|
92
|
-
extra_files.each { |file|
|
|
93
|
-
rdoc.rdoc_files.include(file) if File.exist?(file)
|
|
94
|
-
}
|
|
95
|
-
end
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
def change_version(&block)
|
|
99
|
-
Rake::Task["changelog:check"].invoke
|
|
100
|
-
version_file = 'lib/net/scp/version.rb'
|
|
101
|
-
require_relative version_file
|
|
102
|
-
pre = Net::SCP::Version::PRE
|
|
103
|
-
tiny = Net::SCP::Version::TINY
|
|
104
|
-
result = block[pre: pre, tiny: Net::SCP::Version::TINY]
|
|
105
|
-
raise ArgumentError, "Version change logic should always return a pre" unless result.key?(:pre)
|
|
106
|
-
|
|
107
|
-
new_pre = result[:pre]
|
|
108
|
-
new_tiny = result[:tiny] || tiny
|
|
109
|
-
found = { pre: false, tiny: false }
|
|
110
|
-
File.open("#{version_file}.new", "w") do |f|
|
|
111
|
-
File.readlines(version_file).each do |line|
|
|
112
|
-
match =
|
|
113
|
-
if pre.nil?
|
|
114
|
-
/^(\s+PRE\s+=\s+)nil(\s*)$/.match(line)
|
|
115
|
-
else
|
|
116
|
-
/^(\s+PRE\s+=\s+")#{pre}("\s*)$/.match(line)
|
|
117
|
-
end
|
|
118
|
-
if match
|
|
119
|
-
prefix = match[1]
|
|
120
|
-
postfix = match[2]
|
|
121
|
-
prefix.delete_suffix!('"')
|
|
122
|
-
postfix.delete_prefix!('"')
|
|
123
|
-
new_line = "#{prefix}#{new_pre.inspect}#{postfix}"
|
|
124
|
-
puts "Changing:\n - #{line} + #{new_line}"
|
|
125
|
-
line = new_line
|
|
126
|
-
found[:pre] = true
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
if new_tiny != tiny
|
|
130
|
-
match = /^(\s+TINY\s+=\s+)#{tiny}(\s*)$/.match(line)
|
|
131
|
-
if match
|
|
132
|
-
prefix = match[1]
|
|
133
|
-
postfix = match[2]
|
|
134
|
-
new_line = "#{prefix}#{new_tiny}#{postfix}"
|
|
135
|
-
puts "Changing:\n - #{line} + #{new_line}"
|
|
136
|
-
line = new_line
|
|
137
|
-
found[:tiny] = true
|
|
138
|
-
end
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
f.write(line)
|
|
142
|
-
end
|
|
143
|
-
raise ArgumentError, "Cound not find line: PRE = \"#{pre}\" in #{version_file}" unless found[:pre]
|
|
144
|
-
raise ArgumentError, "Cound not find line: TINY = \"#{tiny}\" in #{version_file}" unless found[:tiny] || new_tiny == tiny
|
|
145
|
-
end
|
|
146
|
-
|
|
147
|
-
FileUtils.mv version_file, "#{version_file}.old"
|
|
148
|
-
FileUtils.mv "#{version_file}.new", version_file
|
|
149
|
-
Rake::Task["changelog:release"].invoke
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
namespace :vbump do
|
|
153
|
-
desc "Final release"
|
|
154
|
-
task :final do
|
|
155
|
-
change_version do |pre:, tiny:|
|
|
156
|
-
_ = tiny
|
|
157
|
-
if pre.nil?
|
|
158
|
-
{ tiny: tiny + 1, pre: nil }
|
|
159
|
-
else
|
|
160
|
-
raise ArgumentError, "Unexpected pre: #{pre}" if pre.nil?
|
|
161
|
-
|
|
162
|
-
{ pre: nil }
|
|
163
|
-
end
|
|
164
|
-
end
|
|
165
|
-
end
|
|
166
|
-
|
|
167
|
-
desc "Increment prerelease"
|
|
168
|
-
task :pre, [:type] do |_t, args|
|
|
169
|
-
change_version do |pre:, tiny:|
|
|
170
|
-
puts " PRE => #{pre.inspect}"
|
|
171
|
-
match = /^([a-z]+)(\d+)/.match(pre)
|
|
172
|
-
raise ArgumentError, "Unexpected pre: #{pre}" if match.nil? && args[:type].nil?
|
|
173
|
-
|
|
174
|
-
if match.nil? || (!args[:type].nil? && args[:type] != match[1])
|
|
175
|
-
if pre.nil?
|
|
176
|
-
{ pre: "#{args[:type]}1", tiny: tiny + 1 }
|
|
177
|
-
else
|
|
178
|
-
{ pre: "#{args[:type]}1" }
|
|
179
|
-
end
|
|
180
|
-
else
|
|
181
|
-
{ pre: "#{match[1]}#{match[2].to_i + 1}" }
|
|
182
|
-
end
|
|
183
|
-
end
|
|
184
|
-
end
|
|
185
|
-
end
|