net-scp 4.1.0 → 4.1.3
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/.github/pull_request_template.md +1 -0
- data/.github/workflows/changelog.yml +18 -0
- data/.github/workflows/ci.yml +18 -1
- data/.gitignore +1 -0
- data/CHANGES.txt +32 -0
- data/CONTRIBUTING.md +33 -0
- data/Gemfile +7 -0
- data/README.md +18 -0
- data/Rakefile +29 -12
- data/changelog.d/README.md +1 -0
- data/gemfiles/legacy.gemfile +7 -0
- data/lib/net/scp/download.rb +49 -14
- data/lib/net/scp/upload.rb +29 -1
- data/lib/net/scp/version.rb +1 -1
- data/lib/net/scp.rb +70 -24
- data/lib/uri/open-scp.rb +14 -2
- data/lib/uri/scp.rb +11 -4
- data/net-scp-public_cert.pem +7 -7
- data/net-scp.gemspec +4 -3
- data/setup.rb +19 -19
- data.tar.gz.sig +0 -0
- metadata +17 -12
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b666f903f4a48fb442f7ddea7efbdf5a222a44b7e2c9f7ae0cba5b6b8a126e10
|
|
4
|
+
data.tar.gz: 39032bdfeb545761cd2229af060ca0a79855e051ecab1123e2c68100deb03e19
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8608726350662b31607e5beaa21d0276fa93942b045ac29a5762236b99b5a57925f9fde59a9fb59ebdd6ada2d6ccb1f52aa17f90e7ee196d2c7a714d66afce2d
|
|
7
|
+
data.tar.gz: 3c4112b0662c5401f6bc4e50aec2797a583faa5d67947ee3e46c2dd7f1d48ed074f49df9893910d08ea09f0625b14071d1d66d8f7390bff66dee60e1e7d19095
|
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
|
data/.github/workflows/ci.yml
CHANGED
|
@@ -10,7 +10,7 @@ jobs:
|
|
|
10
10
|
strategy:
|
|
11
11
|
fail-fast: false
|
|
12
12
|
matrix:
|
|
13
|
-
ruby-version: ['2.
|
|
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
|
|
@@ -21,3 +21,20 @@ jobs:
|
|
|
21
21
|
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
|
|
22
22
|
- name: Run Tests
|
|
23
23
|
run: bundle exec rake test
|
|
24
|
+
env:
|
|
25
|
+
RUBYOPT: "--enable=frozen_string_literal"
|
|
26
|
+
test-legacy:
|
|
27
|
+
runs-on: ubuntu-latest
|
|
28
|
+
strategy:
|
|
29
|
+
fail-fast: false
|
|
30
|
+
matrix:
|
|
31
|
+
ruby-version: ['2.3', '2.4', '2.5']
|
|
32
|
+
steps:
|
|
33
|
+
- uses: actions/checkout@v3
|
|
34
|
+
# Plain docker run rather than container: the ruby:2.3/2.4 images' glibc is too old for the actions' Node.
|
|
35
|
+
# Not rake test: the Rakefile loads changelogd, which these Rubies can't install.
|
|
36
|
+
# No frozen_string_literal: net-ssh 6 mutates string literals.
|
|
37
|
+
- name: Run Tests
|
|
38
|
+
run: >-
|
|
39
|
+
docker run --rm -v "$PWD:/w" -w /w -e BUNDLE_GEMFILE=gemfiles/legacy.gemfile ruby:${{ matrix.ruby-version }}
|
|
40
|
+
sh -c 'bundle install --jobs 4 && bundle exec ruby -Ilib -Itest -e "Dir.glob(\"./test/test_*.rb\").each { |f| require f }"'
|
data/.gitignore
CHANGED
data/CHANGES.txt
CHANGED
|
@@ -1,3 +1,35 @@
|
|
|
1
|
+
=== 4.1.3
|
|
2
|
+
|
|
3
|
+
* Load again on Ruby 2.3-2.5 (4.1.2 needed 2.5), and declare `required_ruby_version >= 2.3` [#112, #110]
|
|
4
|
+
* 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]
|
|
5
|
+
* 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]
|
|
6
|
+
|
|
7
|
+
=== 4.1.2
|
|
8
|
+
|
|
9
|
+
* 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]
|
|
10
|
+
* 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]
|
|
11
|
+
* 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]
|
|
12
|
+
* 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]
|
|
13
|
+
* 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]
|
|
14
|
+
* 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]
|
|
15
|
+
* 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]
|
|
16
|
+
* 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]
|
|
17
|
+
* 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]
|
|
18
|
+
* With `preserve: true`, recursive downloads now keep directories' modification times, which were overwritten when their contents were created. Thanks @OskarEichler [#88]
|
|
19
|
+
* `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]
|
|
20
|
+
* 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]
|
|
21
|
+
* 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]
|
|
22
|
+
|
|
23
|
+
=== 4.1.1
|
|
24
|
+
|
|
25
|
+
* Allow net-ssh 8
|
|
26
|
+
* Fix frozen string literal compatibility [npezza93, mfazekas]
|
|
27
|
+
- Use mutable string for scp command construction
|
|
28
|
+
- Use mutable string for error_string initialization
|
|
29
|
+
* Add Ruby 3.4 and ruby-head to CI matrix
|
|
30
|
+
* Add Ruby 4.0+ compatibility (logger, rdoc gems)
|
|
31
|
+
* Drop EOL Ruby 2.3-2.5 from CI
|
|
32
|
+
|
|
1
33
|
=== 3.0.0
|
|
2
34
|
|
|
3
35
|
* NetSHH 6.* support
|
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
|
@@ -3,9 +3,16 @@ source 'https://rubygems.org'
|
|
|
3
3
|
# Specify your gem's dependencies in mygem.gemspec
|
|
4
4
|
gemspec
|
|
5
5
|
|
|
6
|
+
gem "net-ssh", ">= 7.3.1"
|
|
7
|
+
gem "logger" # no longer a default gem in Ruby 4.0+
|
|
8
|
+
|
|
6
9
|
# TODO: add to gemspec
|
|
7
10
|
gem "bundler", ">= 1.11"
|
|
8
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
|
|
9
16
|
|
|
10
17
|
gem 'byebug', group: %i[development test] if !Gem.win_platform? && RUBY_ENGINE == "ruby"
|
|
11
18
|
|
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/Rakefile
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
require "rubygems"
|
|
2
2
|
require "rake"
|
|
3
3
|
require "rake/clean"
|
|
4
|
-
|
|
4
|
+
begin
|
|
5
|
+
require "rdoc/task"
|
|
6
|
+
rescue LoadError
|
|
7
|
+
end
|
|
5
8
|
require "bundler/gem_tasks"
|
|
6
9
|
|
|
7
10
|
desc "When releasing make sure NET_SSH_BUILDGEM_SIGNED is set"
|
|
@@ -19,6 +22,16 @@ name = "net-scp"
|
|
|
19
22
|
require_relative "lib/net/scp/version"
|
|
20
23
|
version = Net::SCP::Version::CURRENT
|
|
21
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
|
+
|
|
22
35
|
namespace :cert do
|
|
23
36
|
desc "Update public cert from private - only run if public is expired"
|
|
24
37
|
task :update_public_when_expired do
|
|
@@ -67,20 +80,23 @@ Rake::TestTask.new do |t|
|
|
|
67
80
|
end
|
|
68
81
|
|
|
69
82
|
extra_files = %w[LICENSE.txt THANKS.txt CHANGES.txt ]
|
|
70
|
-
RDoc::Task
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
|
81
96
|
end
|
|
82
97
|
|
|
83
98
|
def change_version(&block)
|
|
99
|
+
Rake::Task["changelog:check"].invoke
|
|
84
100
|
version_file = 'lib/net/scp/version.rb'
|
|
85
101
|
require_relative version_file
|
|
86
102
|
pre = Net::SCP::Version::PRE
|
|
@@ -130,6 +146,7 @@ def change_version(&block)
|
|
|
130
146
|
|
|
131
147
|
FileUtils.mv version_file, "#{version_file}.old"
|
|
132
148
|
FileUtils.mv "#{version_file}.new", version_file
|
|
149
|
+
Rake::Task["changelog:release"].invoke
|
|
133
150
|
end
|
|
134
151
|
|
|
135
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).
|
data/lib/net/scp/download.rb
CHANGED
|
@@ -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
|
|
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] =
|
|
132
|
+
channel[:local] = local_path_for(channel, directive[:name])
|
|
130
133
|
|
|
131
|
-
if File.
|
|
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
|
-
|
|
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
|
-
|
|
150
|
-
|
|
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
|
-
|
|
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
|
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
|
|
@@ -91,6 +92,7 @@ module Net; class SCP
|
|
|
91
92
|
# upload and moves to #upload_current_state.
|
|
92
93
|
def next_item_state(channel)
|
|
93
94
|
if channel[:stack].empty?
|
|
95
|
+
channel[:state] = :finish
|
|
94
96
|
finish_state(channel)
|
|
95
97
|
else
|
|
96
98
|
next_item = channel[:stack].last.shift
|
|
@@ -118,6 +120,32 @@ module Net; class SCP
|
|
|
118
120
|
end
|
|
119
121
|
|
|
120
122
|
channel[:size] = channel[:stat] ? channel[:stat].size : channel[:current].size
|
|
123
|
+
if channel[:current].respond_to?(:read) && channel[:current].respond_to?(:pos)
|
|
124
|
+
channel[:size] = [channel[:size] - channel[:current].pos, 0].max
|
|
125
|
+
end
|
|
126
|
+
end
|
|
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(', ')}"
|
|
121
149
|
end
|
|
122
150
|
|
|
123
151
|
# If the :preserve option is set, send a 'T' directive and wait for the
|
data/lib/net/scp/version.rb
CHANGED
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
|
|
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.
|
|
@@ -258,13 +259,24 @@ module Net
|
|
|
258
259
|
#
|
|
259
260
|
# * :recursive - the +local+ parameter refers to a local directory, which
|
|
260
261
|
# should be uploaded to a new directory named +remote+ on the remote
|
|
261
|
-
# 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.
|
|
262
264
|
# * :preserve - the atime and mtime of the file should be preserved.
|
|
263
265
|
# * :verbose - the process should result in verbose output on the server
|
|
264
266
|
# end (useful for debugging).
|
|
265
267
|
# * :chunk_size - the size of each "chunk" that should be sent. Defaults
|
|
266
268
|
# to 2048. Changing this value may improve throughput at the expense
|
|
267
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.
|
|
268
280
|
#
|
|
269
281
|
# This method will return immediately, returning the Net::SSH::Connection::Channel
|
|
270
282
|
# object that will support the upload. To wait for the upload to finish,
|
|
@@ -274,6 +286,10 @@ module Net
|
|
|
274
286
|
# channel = scp.upload("/local/path", "/remote/path")
|
|
275
287
|
# channel.wait
|
|
276
288
|
def upload(local, remote, options={}, &progress)
|
|
289
|
+
chunk_size = options[:chunk_size] || Upload::DEFAULT_CHUNK_SIZE
|
|
290
|
+
unless chunk_size.is_a?(Integer) && chunk_size > 0
|
|
291
|
+
raise ArgumentError, ":chunk_size must be a positive Integer"
|
|
292
|
+
end
|
|
277
293
|
start_command(:upload, local, remote, options, &progress)
|
|
278
294
|
end
|
|
279
295
|
|
|
@@ -293,6 +309,7 @@ module Net
|
|
|
293
309
|
# * :preserve - the atime and mtime of the file should be preserved.
|
|
294
310
|
# * :verbose - the process should result in verbose output on the server
|
|
295
311
|
# end (useful for debugging).
|
|
312
|
+
# * :quote - how +remote+ is quoted for the remote shell (see #upload).
|
|
296
313
|
#
|
|
297
314
|
# This method will return immediately, returning the Net::SSH::Connection::Channel
|
|
298
315
|
# object that will support the download. To wait for the download to finish,
|
|
@@ -329,7 +346,7 @@ module Net
|
|
|
329
346
|
# (:verbose, :recursive, :preserve). Returns the command-line as a
|
|
330
347
|
# string, ready to execute.
|
|
331
348
|
def scp_command(mode, options)
|
|
332
|
-
command = "scp "
|
|
349
|
+
command = +"scp "
|
|
333
350
|
command << (mode == :upload ? "-t" : "-f")
|
|
334
351
|
command << " -v" if options[:verbose]
|
|
335
352
|
command << " -r" if options[:recursive]
|
|
@@ -342,15 +359,15 @@ module Net
|
|
|
342
359
|
# sets up a state machine to use to process the upload or download.
|
|
343
360
|
# (See Net::SCP::Upload and Net::SCP::Download).
|
|
344
361
|
def start_command(mode, local, remote, options={}, &callback)
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
end
|
|
362
|
+
local = local.to_path if local.is_a?(Pathname)
|
|
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
|
|
353
369
|
|
|
370
|
+
session.open_channel do |channel|
|
|
354
371
|
channel.exec(command) do |ch, success|
|
|
355
372
|
if success
|
|
356
373
|
channel[:local ] = local
|
|
@@ -360,22 +377,27 @@ module Net
|
|
|
360
377
|
channel[:buffer ] = Net::SSH::Buffer.new
|
|
361
378
|
channel[:state ] = "#{mode}_start"
|
|
362
379
|
channel[:stack ] = []
|
|
363
|
-
channel[:error_string] = ''
|
|
380
|
+
channel[:error_string] = +''
|
|
364
381
|
|
|
365
382
|
channel.on_close do
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
383
|
+
begin
|
|
384
|
+
# The close can be dispatched before on_process has consumed data that arrived with it.
|
|
385
|
+
send("#{channel[:state]}_state", channel)
|
|
386
|
+
# If we got an exit-status and it is not 0, something went wrong
|
|
387
|
+
if !channel[:exit].nil? && channel[:exit] != 0
|
|
388
|
+
raise Net::SCP::Error, 'SCP did not finish successfully ' \
|
|
389
|
+
"(#{channel[:exit]}): #{channel[:error_string]}"
|
|
390
|
+
end
|
|
391
|
+
# A zero or missing exit-status does not prove the transfer completed.
|
|
392
|
+
if channel[:state] != :finish
|
|
393
|
+
message = +'SCP did not finish successfully (channel closed before end of transmission)'
|
|
394
|
+
message << ": #{channel[:error_string]}" unless channel[:error_string].empty?
|
|
395
|
+
raise Net::SCP::Error, message
|
|
396
|
+
end
|
|
397
|
+
ensure
|
|
398
|
+
io = channel[:io]
|
|
399
|
+
io.close if io && io != channel[:local] && !io.closed?
|
|
370
400
|
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)'
|
|
376
|
-
end
|
|
377
|
-
# At this point, :state can be :finish or :next_item
|
|
378
|
-
send("#{channel[:state]}_state", channel)
|
|
379
401
|
end
|
|
380
402
|
channel.on_data { |ch2, data| channel[:buffer].append(data) }
|
|
381
403
|
channel.on_extended_data { |ch2, type, data| debug { data.chomp } }
|
|
@@ -419,6 +441,7 @@ module Net
|
|
|
419
441
|
# of the pipe, and allows the pipe to drain until the server closes it.
|
|
420
442
|
def finish_state(channel)
|
|
421
443
|
channel.eof!
|
|
444
|
+
raise_if_entries_skipped(channel)
|
|
422
445
|
end
|
|
423
446
|
|
|
424
447
|
# Invoked to report progress back to the client. If a callback was not
|
|
@@ -427,6 +450,29 @@ module Net
|
|
|
427
450
|
channel[:callback].call(channel, name, sent, total) if channel[:callback]
|
|
428
451
|
end
|
|
429
452
|
|
|
453
|
+
# Inside double quotes cmd.exe still expands % and !, PowerShell expands $ and `, and PowerShell also ends a string at U+2018..U+201E.
|
|
454
|
+
WINDOWS_UNSAFE = /["$`%!\r\n\0\u2018-\u201E]/
|
|
455
|
+
|
|
456
|
+
def quote_remote(remote, quote)
|
|
457
|
+
case quote
|
|
458
|
+
when :posix
|
|
459
|
+
remote = remote.to_s
|
|
460
|
+
return remote if remote == "~" || remote == "~/"
|
|
461
|
+
return "~/#{shellescape(remote[2..-1])}" if remote.start_with?("~/")
|
|
462
|
+
shellescape(remote)
|
|
463
|
+
when :windows
|
|
464
|
+
if remote.dup.force_encoding(Encoding::UTF_8) =~ WINDOWS_UNSAFE
|
|
465
|
+
raise ArgumentError, "remote path #{remote.inspect} contains a character that can't be quoted safely for a Windows shell"
|
|
466
|
+
end
|
|
467
|
+
"\"#{remote}\""
|
|
468
|
+
when :none
|
|
469
|
+
remote.to_s
|
|
470
|
+
else
|
|
471
|
+
raise ArgumentError, ":quote must be :posix, :windows, :none or a callable, got #{quote.inspect}" unless quote.respond_to?(:call)
|
|
472
|
+
quote.call(remote).to_s
|
|
473
|
+
end
|
|
474
|
+
end
|
|
475
|
+
|
|
430
476
|
# Imported from ruby 1.9.2 shellwords.rb
|
|
431
477
|
def shellescape(path)
|
|
432
478
|
# Convert path to a string if it isn't already one.
|
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.
|
|
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
|
-
|
|
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-public_cert.pem
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
-----BEGIN CERTIFICATE-----
|
|
2
2
|
MIIDeDCCAmCgAwIBAgIBATANBgkqhkiG9w0BAQsFADBBMQ8wDQYDVQQDDAZuZXRz
|
|
3
3
|
c2gxGTAXBgoJkiaJk/IsZAEZFglzb2x1dGlvdXMxEzARBgoJkiaJk/IsZAEZFgNj
|
|
4
|
-
|
|
4
|
+
b20wHhcNMjYwNTE2MTYyMDUzWhcNMjcwNTE2MTYyMDUzWjBBMQ8wDQYDVQQDDAZu
|
|
5
5
|
ZXRzc2gxGTAXBgoJkiaJk/IsZAEZFglzb2x1dGlvdXMxEzARBgoJkiaJk/IsZAEZ
|
|
6
6
|
FgNjb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDGJ4TbZ9H+qZ08
|
|
7
7
|
pQfJhPJTHaDCyQvCsKTFrL5O9z3tllQ7B/zksMMM+qFBpNYu9HCcg4yBATacE/PB
|
|
@@ -12,10 +12,10 @@ KP4yMn+TzaXijyjRg7gECfJr3TGASaA4bQsILFGG5dAWcwO4OMrZedR7SHj/o0Kf
|
|
|
12
12
|
3gL7P0axAgMBAAGjezB5MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgSwMB0GA1UdDgQW
|
|
13
13
|
BBQF8qLA7Z4zg0SJGtUbv3eoQ8tjIzAfBgNVHREEGDAWgRRuZXRzc2hAc29sdXRp
|
|
14
14
|
b3VzLmNvbTAfBgNVHRIEGDAWgRRuZXRzc2hAc29sdXRpb3VzLmNvbTANBgkqhkiG
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
15
|
+
9w0BAQsFAAOCAQEAVx/yCCX0ZPQ8TFeZe1T+OBEn3Co/AfjAmCdhstf1TF+Jziyx
|
|
16
|
+
dFJ8m8mF4596gru87Mi8mvu+WbPsLlFMOf5LDUkAsgnlvJ+zqNL7jPjGmsh0KOHJ
|
|
17
|
+
0dzPYnTeGcfyrp1AXNN3Z2jWlEInrnqvo376vOmejjTtmDVVlhPjITHQqLSXzRaQ
|
|
18
|
+
e3M8fbJ+nupobti6jpXCYvH86KtpaxF66mwaynfdjzxQAVycV/ilKtL+sHH8zxlg
|
|
19
|
+
vNiMFH5yLu/tlav4xh+HlYscWVdFQIvd7f/Uxa+Vip1Pi16wmhnrE15z/vDX921v
|
|
20
|
+
hukBRIoCEAuUgNVBh+L/C8aETzbf/77WU3Si/w==
|
|
21
21
|
-----END CERTIFICATE-----
|
data/net-scp.gemspec
CHANGED
|
@@ -17,6 +17,7 @@ Gem::Specification.new do |spec|
|
|
|
17
17
|
spec.homepage = "https://github.com/net-ssh/net-scp"
|
|
18
18
|
spec.license = "MIT"
|
|
19
19
|
spec.required_rubygems_version = Gem::Requirement.new(">= 0") if spec.respond_to? :required_rubygems_version=
|
|
20
|
+
spec.required_ruby_version = ">= 2.3"
|
|
20
21
|
spec.metadata = {
|
|
21
22
|
"changelog_uri" => "https://github.com/net-ssh/net-scp/blob/master/CHANGES.txt"
|
|
22
23
|
}
|
|
@@ -33,16 +34,16 @@ Gem::Specification.new do |spec|
|
|
|
33
34
|
spec.specification_version = 3
|
|
34
35
|
|
|
35
36
|
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
|
36
|
-
spec.add_runtime_dependency(%q<net-ssh>, [">= 2.6.5", "<
|
|
37
|
+
spec.add_runtime_dependency(%q<net-ssh>, [">= 2.6.5", "< 9.0.0"])
|
|
37
38
|
spec.add_development_dependency(%q<test-unit>, [">= 0"])
|
|
38
39
|
spec.add_development_dependency(%q<mocha>, [">= 0"])
|
|
39
40
|
else
|
|
40
|
-
spec.add_dependency(%q<net-ssh>, [">= 2.6.5", "<
|
|
41
|
+
spec.add_dependency(%q<net-ssh>, [">= 2.6.5", "< 9.0.0"])
|
|
41
42
|
spec.add_dependency(%q<test-unit>, [">= 0"])
|
|
42
43
|
spec.add_dependency(%q<mocha>, [">= 0"])
|
|
43
44
|
end
|
|
44
45
|
else
|
|
45
|
-
spec.add_dependency(%q<net-ssh>, [">= 2.6.5", "<
|
|
46
|
+
spec.add_dependency(%q<net-ssh>, [">= 2.6.5", "< 9.0.0"])
|
|
46
47
|
spec.add_dependency(%q<test-unit>, [">= 0"])
|
|
47
48
|
spec.add_dependency(%q<mocha>, [">= 0"])
|
|
48
49
|
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
|
|
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
|
|
187
|
+
'the make program to compile ruby extensions' ] ],
|
|
188
188
|
[ 'without-ext', [ 'no',
|
|
189
189
|
'yes/no',
|
|
190
|
-
'does not compile/install ruby
|
|
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
|
|
609
|
+
[ 'setup', 'compiles ruby extensions and others' ],
|
|
610
610
|
[ 'install', 'installs files' ],
|
|
611
|
-
[ 'clean', "does `make clean' for each
|
|
612
|
-
[ 'distclean',"does `make distclean' for each
|
|
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 '
|
|
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
|
|
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
|
|
1216
|
-
|
|
1217
|
-
raise InstallError, "no ruby
|
|
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
|
|
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.
|
|
4
|
+
version: 4.1.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jamis Buck
|
|
@@ -14,7 +14,7 @@ cert_chain:
|
|
|
14
14
|
-----BEGIN CERTIFICATE-----
|
|
15
15
|
MIIDeDCCAmCgAwIBAgIBATANBgkqhkiG9w0BAQsFADBBMQ8wDQYDVQQDDAZuZXRz
|
|
16
16
|
c2gxGTAXBgoJkiaJk/IsZAEZFglzb2x1dGlvdXMxEzARBgoJkiaJk/IsZAEZFgNj
|
|
17
|
-
|
|
17
|
+
b20wHhcNMjYwNTE2MTYyMDUzWhcNMjcwNTE2MTYyMDUzWjBBMQ8wDQYDVQQDDAZu
|
|
18
18
|
ZXRzc2gxGTAXBgoJkiaJk/IsZAEZFglzb2x1dGlvdXMxEzARBgoJkiaJk/IsZAEZ
|
|
19
19
|
FgNjb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDGJ4TbZ9H+qZ08
|
|
20
20
|
pQfJhPJTHaDCyQvCsKTFrL5O9z3tllQ7B/zksMMM+qFBpNYu9HCcg4yBATacE/PB
|
|
@@ -25,14 +25,14 @@ cert_chain:
|
|
|
25
25
|
3gL7P0axAgMBAAGjezB5MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgSwMB0GA1UdDgQW
|
|
26
26
|
BBQF8qLA7Z4zg0SJGtUbv3eoQ8tjIzAfBgNVHREEGDAWgRRuZXRzc2hAc29sdXRp
|
|
27
27
|
b3VzLmNvbTAfBgNVHRIEGDAWgRRuZXRzc2hAc29sdXRpb3VzLmNvbTANBgkqhkiG
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
28
|
+
9w0BAQsFAAOCAQEAVx/yCCX0ZPQ8TFeZe1T+OBEn3Co/AfjAmCdhstf1TF+Jziyx
|
|
29
|
+
dFJ8m8mF4596gru87Mi8mvu+WbPsLlFMOf5LDUkAsgnlvJ+zqNL7jPjGmsh0KOHJ
|
|
30
|
+
0dzPYnTeGcfyrp1AXNN3Z2jWlEInrnqvo376vOmejjTtmDVVlhPjITHQqLSXzRaQ
|
|
31
|
+
e3M8fbJ+nupobti6jpXCYvH86KtpaxF66mwaynfdjzxQAVycV/ilKtL+sHH8zxlg
|
|
32
|
+
vNiMFH5yLu/tlav4xh+HlYscWVdFQIvd7f/Uxa+Vip1Pi16wmhnrE15z/vDX921v
|
|
33
|
+
hukBRIoCEAuUgNVBh+L/C8aETzbf/77WU3Si/w==
|
|
34
34
|
-----END CERTIFICATE-----
|
|
35
|
-
date:
|
|
35
|
+
date: 2026-10-27 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:
|
|
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:
|
|
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,20 @@ 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
|
|
107
|
+
- gemfiles/legacy.gemfile
|
|
103
108
|
- lib/net/scp.rb
|
|
104
109
|
- lib/net/scp/download.rb
|
|
105
110
|
- lib/net/scp/errors.rb
|
|
@@ -123,7 +128,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
123
128
|
requirements:
|
|
124
129
|
- - ">="
|
|
125
130
|
- !ruby/object:Gem::Version
|
|
126
|
-
version: '
|
|
131
|
+
version: '2.3'
|
|
127
132
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
128
133
|
requirements:
|
|
129
134
|
- - ">="
|
metadata.gz.sig
CHANGED
|
Binary file
|