net-scp 4.1.1 → 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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data/.github/pull_request_template.md +1 -0
- data/.github/workflows/changelog.yml +18 -0
- data/CHANGES.txt +16 -0
- data/CONTRIBUTING.md +33 -0
- data/Gemfile +3 -0
- data/Rakefile +12 -0
- data/changelog.d/README.md +1 -0
- data/lib/net/scp/download.rb +49 -14
- data/lib/net/scp/upload.rb +4 -0
- data/lib/net/scp/version.rb +1 -1
- data/lib/net/scp.rb +17 -8
- data/lib/uri/open-scp.rb +14 -2
- data/lib/uri/scp.rb +11 -4
- data/setup.rb +19 -19
- data.tar.gz.sig +2 -3
- metadata +6 -2
- 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: b1413e9916cf7c4d82160a87e9da29044528f7c32dc746d12a873ea4d902a940
|
|
4
|
+
data.tar.gz: b0e33d71c6a78b8d263fd95408e23188c3c7e0bb274d52c1df6745bbc6793cc6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
data/CHANGES.txt
CHANGED
|
@@ -1,3 +1,19 @@
|
|
|
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
|
|
|
3
19
|
* Allow net-ssh 8
|
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).
|
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
|
@@ -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
|
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.
|
|
@@ -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
|
-
#
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
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
|
-
|
|
378
|
-
|
|
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.
|
|
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/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
|
@@ -1,3 +1,2 @@
|
|
|
1
|
-
B
|
|
2
|
-
|
|
3
|
-
]W�b fn���kg�n}y�ו�`
|
|
1
|
+
�bf3p�l��q��E@P��a� �y?��i�v_���K��?��B�Q�x�4�U�RM�u�������й�<���H\��Յl�9��
|
|
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
|
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.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-10-
|
|
35
|
+
date: 2026-10-25 00:00:00.000000000 Z
|
|
36
36
|
dependencies:
|
|
37
37
|
- !ruby/object:Gem::Dependency
|
|
38
38
|
name: net-ssh
|
|
@@ -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
|