pax_global_header 0000666 0000000 0000000 00000000064 14322300260 0014502 g ustar 00root root 0000000 0000000 52 comment=d01bc17d2acb62c519c90e1afbb40977e00d3df8
net-scp-4.0.0/ 0000775 0000000 0000000 00000000000 14322300260 0013054 5 ustar 00root root 0000000 0000000 net-scp-4.0.0/.gitignore 0000664 0000000 0000000 00000000042 14322300260 0015040 0 ustar 00root root 0000000 0000000 coverage
pkg
doc
*.swp
.DS_Store
net-scp-4.0.0/.travis.yml 0000664 0000000 0000000 00000000322 14322300260 0015162 0 ustar 00root root 0000000 0000000 language: ruby
cache: bundler
rvm:
- 2.3.8
- 2.4.5
- 2.5.3
- 2.6.1
- ruby-head
matrix:
allow_failures:
- rvm: ruby-head
install: gem install jeweler test-unit mocha net-ssh
script: rake test
net-scp-4.0.0/CHANGES.txt 0000664 0000000 0000000 00000002263 14322300260 0014670 0 ustar 00root root 0000000 0000000 === 3.0.0
* NetSHH 6.* support
=== 2.0.0
* NetSSH 5.* support
=== 1.2.1 / 30 Apr 2014
* Resign gem with new pubkey
=== 1.2.0 / 11 Apr 2014
* Get the error string during download [jkeiser]
=== 1.1.2 / 6 Jul 2013
* Explicit convert to string in shellescape [jwils]
=== 1.1.1 / 13 May 2013
* Allow passing a shell to use when executing scp. [Arthur Schreiber]
=== 1.1.0 / 06 Feb 2013
* Added public cert. All gem releases are now signed. See INSTALL in readme.
=== 1.0.4 / 16 Sep 2010
* maintain filename sanitization compatibility with ruby 1.8.6 [Sung Pae, Tim Charper]
=== 1.0.3 / 17 Aug 2010
* replace :sanitize_file_name with a call to String#shellescape [Sung Pae]
* Added gemspec file and removed echoe dependency [Miron Cuperman, Delano Mandelbaum]
* Removed Hanna dependency in Rakefile [Delano Mandelbaum]
=== 1.0.2 / 4 Feb 2009
* Escape spaces in file names on remote server [Jamis Buck]
=== 1.0.1 / 29 May 2008
* Make sure downloads open the file in binary mode to appease Windows [Jamis Buck]
=== 1.0.0 / 1 May 2008
* Pass the channel object as the first argument to the progress callback [Jamis Buck]
=== 1.0 Preview Release 1 (0.99.0) / 22 Mar 2008
* Birthday!
net-scp-4.0.0/Gemfile 0000664 0000000 0000000 00000000553 14322300260 0014352 0 ustar 00root root 0000000 0000000 source 'https://rubygems.org'
# Specify your gem's dependencies in mygem.gemspec
gemspec
# TODO: add to gemspec
gem "bundler", "~> 1.11"
gem "rake", "~> 12.0"
gem 'byebug', group: %i[development test] if !Gem.win_platform? && RUBY_ENGINE == "ruby"
if ENV["CI"]
gem 'codecov', require: false, group: :test
gem 'simplecov', require: false, group: :test
end
net-scp-4.0.0/LICENSE.txt 0000664 0000000 0000000 00000002045 14322300260 0014700 0 ustar 00root root 0000000 0000000 Copyright © 2008 Jamis Buck
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the ‘Software’), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED ‘AS IS’, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
net-scp-4.0.0/Manifest 0000664 0000000 0000000 00000000436 14322300260 0014550 0 ustar 00root root 0000000 0000000 CHANGELOG.rdoc
lib/net/scp/download.rb
lib/net/scp/errors.rb
lib/net/scp/upload.rb
lib/net/scp/version.rb
lib/net/scp.rb
lib/uri/open-scp.rb
lib/uri/scp.rb
Rakefile
README.rdoc
setup.rb
test/common.rb
test/test_all.rb
test/test_download.rb
test/test_scp.rb
test/test_upload.rb
Manifest
net-scp-4.0.0/README.rdoc 0000664 0000000 0000000 00000010612 14322300260 0014662 0 ustar 00root root 0000000 0000000 = Net::SCP
Please note: this project is in maintenance mode. It is not under active development but pull requests are very much welcome. Just be sure to include tests! -- delano
* Docs: http://net-ssh.github.com/net-scp
* Issues: https://github.com/net-ssh/net-scp/issues
* Codes: https://github.com/net-ssh/net-scp
* Email: net-ssh@solutious.com
As of v1.0.5, all gem releases are signed. See INSTALL.
== DESCRIPTION:
Net::SCP is a pure-Ruby implementation of the SCP protocol. This operates over SSH (and requires the Net::SSH library), and allows files and directory trees to be copied to and from a remote server.
== FEATURES/PROBLEMS:
* Transfer files or entire directory trees to or from a remote host via SCP
* Can preserve file attributes across transfers
* Can download files in-memory, or direct-to-disk
* Support for SCP URI's, and OpenURI
== SYNOPSIS:
In a nutshell:
require 'net/scp'
# upload a file to a remote server
Net::SCP.upload!("remote.host.com", "username",
"/local/path", "/remote/path",
:ssh => { :password => "password" })
# upload recursively
Net::SCP.upload!("remote.host", "username", "/path/to/local", "/path/to/remote",
:ssh => { :password => "foo" }, :recursive => true)
# download a file from a remote server
Net::SCP.download!("remote.host.com", "username",
"/remote/path", "/local/path",
:ssh => { :password => "password" })
# download a file to an in-memory buffer
data = Net::SCP::download!("remote.host.com", "username", "/remote/path")
# use a persistent connection to transfer files
Net::SCP.start("remote.host.com", "username", :password => "password") do |scp|
# upload a file to a remote server
scp.upload! "/local/path", "/remote/path"
# upload from an in-memory buffer
scp.upload! StringIO.new("some data to upload"), "/remote/path"
# run multiple downloads in parallel
d1 = scp.download("/remote/path", "/local/path")
d2 = scp.download("/remote/path2", "/local/path2")
[d1, d2].each { |d| d.wait }
end
# You can also use open-uri to grab data via scp:
require 'uri/open-scp'
data = open("scp://user@host/path/to/file.txt").read
For more information, see Net::SCP.
== REQUIREMENTS:
* Net::SSH 2
If you wish to run the tests, you'll also need:
* Echoe (for Rakefile use)
* Mocha (for tests)
== INSTALL:
* gem install net-scp (might need sudo privileges)
However, in order to be sure the code you're installing hasn't been tampered with, it's recommended that you verify the signature[http://docs.seattlerb.org/rubygems/Gem/Security.html]. To do this, you need to add my public key as a trusted certificate (you only need to do this once):
# Add the public key as a trusted certificate
# (You only need to do this once)
$ curl -O https://raw.githubusercontent.com/net-ssh/net-ssh/master/net-ssh-public_cert.pem
$ gem cert --add net-ssh-public_cert.pem
Then- when installing the gem - do so with high security:
$ gem install net-scp -P HighSecurity
If you don't add the public key, you'll see an error like "Couldn't verify data signature". If you're still having trouble let me know and I'll give you a hand.
Or, you can do it the hard way (without Rubygems):
* tar xzf net-scp-*.tgz
* cd net-scp-*
* ruby setup.rb config
* ruby setup.rb install (might need sudo privileges)
== LICENSE:
(The MIT License)
Copyright (c) 2008 Jamis Buck
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
net-scp-4.0.0/Rakefile 0000664 0000000 0000000 00000004726 14322300260 0014532 0 ustar 00root root 0000000 0000000 require "rubygems"
require "rake"
require "rake/clean"
require "rdoc/task"
require "bundler/gem_tasks"
desc "When releasing make sure NET_SSH_BUILDGEM_SIGNED is set"
task :check_NET_SSH_BUILDGEM_SIGNED do
raise "NET_SSH_BUILDGEM_SIGNED should be set to release" unless ENV['NET_SSH_BUILDGEM_SIGNED']
end
Rake::Task[:release].enhance [:check_NET_SSH_BUILDGEM_SIGNED]
Rake::Task[:release].prerequisites.unshift(:check_NET_SSH_BUILDGEM_SIGNED)
task default: ["build"]
CLEAN.include [ 'pkg', 'rdoc' ]
name = "net-scp"
require_relative "lib/net/scp/version"
version = Net::SCP::Version::CURRENT
namespace :cert do
desc "Update public cert from private - only run if public is expired"
task :update_public_when_expired do
require 'openssl'
require 'time'
raw = File.read "net-scp-public_cert.pem"
certificate = OpenSSL::X509::Certificate.new raw
raise Exception, "Not yet expired: #{certificate.not_after}" unless certificate.not_after < Time.now
sh "gem cert --build netssh@solutious.com --days 365*5 --private-key /mnt/gem/net-ssh-private_key.pem"
sh "mv gem-public_cert.pem net-scp-public_cert.pem"
sh "gem cert --add net-scp-public_cert.pem"
end
end
if false
begin
require "jeweler"
Jeweler::Tasks.new do |s|
s.version = version
s.name = name
s.summary = "A pure Ruby implementation of the SCP client protocol"
s.description = s.summary
s.email = "net-ssh@solutious.com"
s.homepage = "https://github.com/net-ssh/net-scp"
s.authors = ["Jamis Buck", "Delano Mandelbaum"]
s.add_dependency 'net-ssh', ">=2.6.5"
s.add_development_dependency 'test-unit'
s.add_development_dependency 'mocha'
s.license = "MIT"
s.signing_key = File.join('/mnt/gem/', 'gem-private_key.pem')
s.cert_chain = ['gem-public_cert.pem']
end
Jeweler::GemcutterTasks.new
rescue LoadError
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
end
end
require 'rake/testtask'
Rake::TestTask.new do |t|
t.libs = ["lib", "test"]
end
extra_files = %w[LICENSE.txt THANKS.txt CHANGES.txt ]
RDoc::Task.new do |rdoc|
rdoc.rdoc_dir = "rdoc"
rdoc.title = "#{name} #{version}"
rdoc.generator = 'hanna' # gem install hanna-nouveau
rdoc.main = 'README.rdoc'
rdoc.rdoc_files.include("README*")
rdoc.rdoc_files.include("bin/*.rb")
rdoc.rdoc_files.include("lib/**/*.rb")
extra_files.each { |file|
rdoc.rdoc_files.include(file) if File.exists?(file)
}
end
net-scp-4.0.0/lib/ 0000775 0000000 0000000 00000000000 14322300260 0013622 5 ustar 00root root 0000000 0000000 net-scp-4.0.0/lib/net/ 0000775 0000000 0000000 00000000000 14322300260 0014410 5 ustar 00root root 0000000 0000000 net-scp-4.0.0/lib/net/scp.rb 0000664 0000000 0000000 00000046104 14322300260 0015527 0 ustar 00root root 0000000 0000000 require 'stringio'
require 'shellwords'
require 'net/ssh'
require 'net/scp/errors'
require 'net/scp/upload'
require 'net/scp/download'
module Net
# Net::SCP implements the SCP (Secure CoPy) client protocol, allowing Ruby
# programs to securely and programmatically transfer individual files or
# entire directory trees to and from remote servers. It provides support for
# multiple simultaneous SCP copies working in parallel over the same
# connection, as well as for synchronous, serial copies.
#
# Basic usage:
#
# require 'net/scp'
#
# Net::SCP.start("remote.host", "username", :password => "passwd") do |scp|
# # synchronous (blocking) upload; call blocks until upload completes
# scp.upload! "/local/path", "/remote/path"
#
# # asynchronous upload; call returns immediately and requires SSH
# # event loop to run
# channel = scp.upload("/local/path", "/remote/path")
# channel.wait
# end
#
# Net::SCP also provides an open-uri tie-in, so you can use the Kernel#open
# method to open and read a remote file:
#
# # if you just want to parse SCP URL's:
# require 'uri/scp'
# url = URI.parse("scp://user@remote.host/path/to/file")
#
# # if you want to read from a URL voa SCP:
# require 'uri/open-scp'
# puts open("scp://user@remote.host/path/to/file").read
#
# Lastly, Net::SCP adds a method to the Net::SSH::Connection::Session class,
# allowing you to easily grab a Net::SCP reference from an existing Net::SSH
# session:
#
# require 'net/ssh'
# require 'net/scp'
#
# Net::SSH.start("remote.host", "username", :password => "passwd") do |ssh|
# ssh.scp.download! "/remote/path", "/local/path"
# end
#
# == Progress Reporting
#
# By default, uploading and downloading proceed silently, without any
# outward indication of their progress. For long running uploads or downloads
# (and especially in interactive environments) it is desirable to report
# to the user the progress of the current operation.
#
# To receive progress reports for the current operation, just pass a block
# to #upload or #download (or one of their variants):
#
# scp.upload!("/path/to/local", "/path/to/remote") do |ch, name, sent, total|
# puts "#{name}: #{sent}/#{total}"
# end
#
# Whenever a new chunk of data is recieved for or sent to a file, the callback
# will be invoked, indicating the name of the file (local for downloads,
# remote for uploads), the number of bytes that have been sent or received
# so far for the file, and the size of the file.
#
#--
# = Protocol Description
#
# Although this information has zero relevance to consumers of the Net::SCP
# library, I'm documenting it here so that anyone else looking for documentation
# of the SCP protocol won't be left high-and-dry like I was. The following is
# reversed engineered from the OpenSSH SCP implementation, and so may
# contain errors. You have been warned!
#
# The first step is to invoke the "scp" command on the server. It accepts
# the following parameters, which must be set correctly to avoid errors:
#
# * "-t" -- tells the remote scp process that data will be sent "to" it,
# e.g., that data will be uploaded and it should initialize itself
# accordingly.
# * "-f" -- tells the remote scp process that data should come "from" it,
# e.g., that data will be downloaded and it should initialize itself
# accordingly.
# * "-v" -- verbose mode; the remote scp process should chatter about what
# it is doing via stderr.
# * "-p" -- preserve timestamps. 'T' directives (see below) should be/will
# be sent to indicate the modification and access times of each file.
# * "-r" -- recursive transfers should be allowed. Without this, it is an
# error to upload or download a directory.
#
# After those flags, the name of the remote file/directory should be passed
# as the sole non-switch argument to scp.
#
# Then the fun begins. If you're doing a download, enter the download_start_state.
# Otherwise, look for upload_start_state.
#
# == Net::SCP::Download#download_start_state
#
# This is the start state for downloads. It simply sends a 0-byte to the
# server. The next state is Net::SCP::Download#read_directive_state.
#
# == Net::SCP::Upload#upload_start_state
#
# Sets up the initial upload scaffolding and waits for a 0-byte from the
# server, and then switches to Net::SCP::Upload#upload_current_state.
#
# == Net::SCP::Download#read_directive_state
#
# Reads a directive line from the input. The following directives are
# recognized:
#
# * T%d %d %d %d -- a "times" packet. Indicates that the next file to be
# downloaded must have mtime/usec/atime/usec attributes preserved.
# * D%o %d %s -- a directory change. The process is changing to a directory
# with the given permissions/size/name, and the recipient should create
# a directory with the same name and permissions. Subsequent files and
# directories will be children of this directory, until a matching 'E'
# directive.
# * C%o %d %s -- a file is being sent next. The file will have the given
# permissions/size/name. Immediately following this line, +size+ bytes
# will be sent, raw.
# * E -- terminator directive. Indicates the end of a directory, and subsequent
# files and directories should be received by the parent of the current
# directory.
# * \0 -- indicates a successful response from the other end.
# * \1 -- warning directive. Indicates a warning from the other end. Text from
# this warning will be reported if the SCP results in an error.
# * \2 -- error directive. Indicates an error from the other end. Text from
# this error will be reported if the SCP results in an error.
#
# If a 'C' directive is received, we switch over to
# Net::SCP::Download#read_data_state. If an 'E' directive is received, and
# there is no parent directory, we switch over to Net::SCP#finish_state.
#
# Regardless of what the next state is, we send a 0-byte to the server
# before moving to the next state.
#
# == Net::SCP::Download#read_data_state
#
# Bytes are read to satisfy the size of the incoming file. When all pending
# data has been read, we wait for the server to send a 0-byte, and then we
# switch to the Net::SCP::Download#finish_read_state.
#
# == Net::SCP::Download#finish_read_state
#
# We sent a 0-byte to the server to indicate that the file was successfully
# received. If there is no parent directory, then we're downloading a single
# file and we switch to Net::SCP#finish_state. Otherwise we jump back to the
# Net::SCP::Download#read_directive state to see what we get to download next.
#
# == Net::SCP::Upload#upload_current_state
#
# If the current item is a file, send a file. Sending a file starts with a
# 'T' directive (if :preserve is true), then a wait for the server to respond,
# and then a 'C' directive, and then a wait for the server to respond, and
# then a jump to Net::SCP::Upload#send_data_state.
#
# If current item is a directory, send a 'D' directive, and wait for the
# server to respond with a 0-byte. Then jump to Net::SCP::Upload#next_item_state.
#
# == Net::SCP::Upload#send_data_state
#
# Reads and sends the next chunk of data to the server. The state machine
# remains in this state until all data has been sent, at which point we
# send a 0-byte to the server, and wait for the server to respond with a
# 0-byte of its own. Then we jump back to Net::SCP::Upload#next_item_state.
#
# == Net::SCP::Upload#next_item_state
#
# If there is nothing left to upload, and there is no parent directory,
# jump to Net::SCP#finish_state.
#
# If there is nothing left to upload from the current directory, send an
# 'E' directive and wait for the server to respond with a 0-byte. Then go
# to Net::SCP::Upload#next_item_state.
#
# Otherwise, set the current upload source and go to
# Net::SCP::Upload#upload_current_state.
#
# == Net::SCP#finish_state
#
# Tells the server that no more data is forthcoming from this end of the
# pipe (via Net::SSH::Connection::Channel#eof!) and leaves the pipe to drain.
# It will be terminated when the remote process closes with an exit status
# of zero.
#++
class SCP
include Net::SSH::Loggable
include Upload, Download
# Starts up a new SSH connection and instantiates a new SCP session on
# top of it. If a block is given, the SCP session is yielded, and the
# SSH session is closed automatically when the block terminates. If no
# block is given, the SCP session is returned.
def self.start(host, username, options={})
session = Net::SSH.start(host, username, options)
scp = new(session)
if block_given?
begin
yield scp
session.loop
ensure
session.close
end
else
return scp
end
end
# Starts up a new SSH connection using the +host+ and +username+ parameters,
# instantiates a new SCP session on top of it, and then begins an
# upload from +local+ to +remote+. If the +options+ hash includes an
# :ssh key, the value for that will be passed to the SSH connection as
# options (e.g., to set the password, etc.). All other options are passed
# to the #upload! method. If a block is given, it will be used to report
# progress (see "Progress Reporting", under Net::SCP).
def self.upload!(host, username, local, remote, options={}, &progress)
options = options.dup
start(host, username, options.delete(:ssh) || {}) do |scp|
scp.upload!(local, remote, options, &progress)
end
end
# Starts up a new SSH connection using the +host+ and +username+ parameters,
# instantiates a new SCP session on top of it, and then begins a
# download from +remote+ to +local+. If the +options+ hash includes an
# :ssh key, the value for that will be passed to the SSH connection as
# options (e.g., to set the password, etc.). All other options are passed
# to the #download! method. If a block is given, it will be used to report
# progress (see "Progress Reporting", under Net::SCP).
def self.download!(host, username, remote, local=nil, options={}, &progress)
options = options.dup
start(host, username, options.delete(:ssh) || {}) do |scp|
return scp.download!(remote, local, options, &progress)
end
end
# The underlying Net::SSH session that acts as transport for the SCP
# packets.
attr_reader :session
# Creates a new Net::SCP session on top of the given Net::SSH +session+
# object.
def initialize(session)
@session = session
self.logger = session.logger
end
# Inititiate a synchronous (non-blocking) upload from +local+ to +remote+.
# The following options are recognized:
#
# * :recursive - the +local+ parameter refers to a local directory, which
# should be uploaded to a new directory named +remote+ on the remote
# server.
# * :preserve - the atime and mtime of the file should be preserved.
# * :verbose - the process should result in verbose output on the server
# end (useful for debugging).
# * :chunk_size - the size of each "chunk" that should be sent. Defaults
# to 2048. Changing this value may improve throughput at the expense
# of decreasing interactivity.
#
# This method will return immediately, returning the Net::SSH::Connection::Channel
# object that will support the upload. To wait for the upload to finish,
# you can either call the #wait method on the channel, or otherwise run
# the Net::SSH event loop until the channel's #active? method returns false.
#
# channel = scp.upload("/local/path", "/remote/path")
# channel.wait
def upload(local, remote, options={}, &progress)
start_command(:upload, local, remote, options, &progress)
end
# Same as #upload, but blocks until the upload finishes. Identical to
# calling #upload and then calling the #wait method on the channel object
# that is returned. The return value is not defined.
def upload!(local, remote, options={}, &progress)
upload(local, remote, options, &progress).wait
end
# Inititiate a synchronous (non-blocking) download from +remote+ to +local+.
# The following options are recognized:
#
# * :recursive - the +remote+ parameter refers to a remote directory, which
# should be downloaded to a new directory named +local+ on the local
# machine.
# * :preserve - the atime and mtime of the file should be preserved.
# * :verbose - the process should result in verbose output on the server
# end (useful for debugging).
#
# This method will return immediately, returning the Net::SSH::Connection::Channel
# object that will support the download. To wait for the download to finish,
# you can either call the #wait method on the channel, or otherwise run
# the Net::SSH event loop until the channel's #active? method returns false.
#
# channel = scp.download("/remote/path", "/local/path")
# channel.wait
def download(remote, local, options={}, &progress)
start_command(:download, local, remote, options, &progress)
end
# Same as #download, but blocks until the download finishes. Identical to
# calling #download and then calling the #wait method on the channel
# object that is returned.
#
# scp.download!("/remote/path", "/local/path")
#
# If +local+ is nil, and the download is not recursive (e.g., it is downloading
# only a single file), the file will be downloaded to an in-memory buffer
# and the resulting string returned.
#
# data = download!("/remote/path")
def download!(remote, local=nil, options={}, &progress)
destination = local ? local : StringIO.new.tap { |io| io.set_encoding('BINARY') }
download(remote, destination, options, &progress).wait
local ? true : destination.string
end
private
# Constructs the scp command line needed to initiate and SCP session
# for the given +mode+ (:upload or :download) and with the given options
# (:verbose, :recursive, :preserve). Returns the command-line as a
# string, ready to execute.
def scp_command(mode, options)
command = "scp "
command << (mode == :upload ? "-t" : "-f")
command << " -v" if options[:verbose]
command << " -r" if options[:recursive]
command << " -p" if options[:preserve]
command
end
# Opens a new SSH channel and executes the necessary SCP command over
# it (see #scp_command). It then sets up the necessary callbacks, and
# sets up a state machine to use to process the upload or download.
# (See Net::SCP::Upload and Net::SCP::Download).
def start_command(mode, local, remote, options={}, &callback)
session.open_channel do |channel|
if options[:shell]
escaped_file = shellescape(remote).gsub(/'/) { |m| "'\\''" }
command = "#{options[:shell]} -c '#{scp_command(mode, options)} #{escaped_file}'"
else
command = "#{scp_command(mode, options)} #{shellescape remote}"
end
channel.exec(command) do |ch, success|
if success
channel[:local ] = local
channel[:remote ] = remote
channel[:options ] = options.dup
channel[:callback] = callback
channel[:buffer ] = Net::SSH::Buffer.new
channel[:state ] = "#{mode}_start"
channel[:stack ] = []
channel[:error_string] = ''
channel.on_close { |ch2| send("#{channel[:state]}_state", channel); raise Net::SCP::Error, "SCP did not finish successfully (#{channel[:exit]}): #{channel[:error_string]}" if channel[:exit] != 0 }
channel.on_data { |ch2, data| channel[:buffer].append(data) }
channel.on_extended_data { |ch2, type, data| debug { data.chomp } }
channel.on_request("exit-status") { |ch2, data| channel[:exit] = data.read_long }
channel.on_process { send("#{channel[:state]}_state", channel) }
else
channel.close
raise Net::SCP::Error, "could not exec scp on the remote host"
end
end
end
end
# Causes the state machine to enter the "await response" state, where
# things just pause until the server replies with a 0 (see
# #await_response_state), at which point the state machine will pick up
# at +next_state+ and continue processing.
def await_response(channel, next_state)
channel[:state] = :await_response
channel[:next ] = next_state.to_sym
# check right away, to see if the response is immediately available
await_response_state(channel)
end
# The action invoked while the state machine remains in the "await
# response" state. As long as there is no data ready to process, the
# machine will remain in this state. As soon as the server replies with
# an integer 0 as the only byte, the state machine is kicked into the
# next state (see +await_response+). If the response is not a 0, an
# exception is raised.
def await_response_state(channel)
return if channel[:buffer].available == 0
c = channel[:buffer].read_byte
raise Net::SCP::Error, "#{c.chr}#{channel[:buffer].read}" if c != 0
channel[:next], channel[:state] = nil, channel[:next]
send("#{channel[:state]}_state", channel)
end
# The action invoked when the state machine is in the "finish" state.
# It just tells the server not to expect any more data from this end
# of the pipe, and allows the pipe to drain until the server closes it.
def finish_state(channel)
channel.eof!
end
# Invoked to report progress back to the client. If a callback was not
# set, this does nothing.
def progress_callback(channel, name, sent, total)
channel[:callback].call(channel, name, sent, total) if channel[:callback]
end
# Imported from ruby 1.9.2 shellwords.rb
def shellescape(path)
# Convert path to a string if it isn't already one.
str = path.to_s
# ruby 1.8.7+ implements String#shellescape
return str.shellescape if str.respond_to? :shellescape
# An empty argument will be skipped, so return empty quotes.
return "''" if str.empty?
str = str.dup
# Process as a single byte sequence because not all shell
# implementations are multibyte aware.
str.gsub!(/([^A-Za-z0-9_\-.,:\/@\n])/n, "\\\\\\1")
# A LF cannot be escaped with a backslash because a backslash + LF
# combo is regarded as line continuation and simply ignored.
str.gsub!(/\n/, "'\n'")
return str
end
end
end
class Net::SSH::Connection::Session
# Provides a convenient way to initialize a SCP session given a Net::SSH
# session. Returns the Net::SCP instance, ready to use.
def scp
@scp ||= Net::SCP.new(self)
end
end
net-scp-4.0.0/lib/net/scp/ 0000775 0000000 0000000 00000000000 14322300260 0015175 5 ustar 00root root 0000000 0000000 net-scp-4.0.0/lib/net/scp/download.rb 0000664 0000000 0000000 00000014373 14322300260 0017341 0 ustar 00root root 0000000 0000000 require 'net/scp/errors'
module Net; class SCP
# This module implements the state machine for downloading information from
# a remote server. It exposes no public methods. See Net::SCP#download for
# a discussion of how to use Net::SCP to download data.
module Download
private
# This is the starting state for the download state machine. The
# #start_command method puts the state machine into this state the first
# time the channel is processed. This state does some basic error checking
# and scaffolding and then sends a 0-byte to the remote server, indicating
# readiness to proceed. Then, the state machine is placed into the
# "read directive" state (see #read_directive_state).
def download_start_state(channel)
if channel[:local].respond_to?(:write) && channel[:options][:recursive]
raise Net::SCP::Error, "cannot recursively download to an in-memory location"
elsif channel[:local].respond_to?(:write) && channel[:options][:preserve]
lwarn { ":preserve option is ignored when downloading to an in-memory buffer" }
channel[:options].delete(:preserve)
elsif channel[:options][:recursive] && !File.exist?(channel[:local])
Dir.mkdir(channel[:local])
end
channel.send_data("\0")
channel[:state] = :read_directive
end
# This state parses the next full line (up to a new-line) for the next
# directive. (See the SCP protocol documentation in Net::SCP for the
# possible directives).
def read_directive_state(channel)
return unless line = channel[:buffer].read_to("\n")
channel[:buffer].consume!
directive = parse_directive(line)
case directive[:type]
when :OK
return
when :warning
channel[:error_string] << directive[:message]
when :error
channel[:error_string] << directive[:message]
when :times
channel[:times] = directive
when :directory
read_directory(channel, directive)
when :file
read_file(channel, directive)
when :end
channel[:local] = File.dirname(channel[:local])
channel[:stack].pop
channel[:state] = :finish if channel[:stack].empty?
end
channel.send_data("\0")
end
# Reads data from the channel for as long as there is data remaining to
# be read. As soon as there is no more data to read for the current file,
# the state machine switches to #finish_read_state.
def read_data_state(channel)
return if channel[:buffer].empty?
data = channel[:buffer].read!(channel[:remaining])
channel[:io].write(data)
channel[:remaining] -= data.length
progress_callback(channel, channel[:file][:name], channel[:file][:size] - channel[:remaining], channel[:file][:size])
await_response(channel, :finish_read) if channel[:remaining] <= 0
end
# Finishes off the read, sets the times for the file (if any), and then
# jumps to either #finish_state (for single-file downloads) or
# #read_directive_state (for recursive downloads). A 0-byte is sent to the
# server to indicate that the file was recieved successfully.
def finish_read_state(channel)
channel[:io].close unless channel[:io] == channel[:local]
if channel[:options][:preserve] && channel[:file][:times]
File.utime(channel[:file][:times][:atime],
channel[:file][:times][:mtime], channel[:file][:name])
end
channel[:file] = nil
channel[:state] = channel[:stack].empty? ? :finish : :read_directive
channel.send_data("\0")
end
# Parses the given +text+ to extract which SCP directive it contains. It
# then returns a hash with at least one key, :type, which describes what
# type of directive it is. The hash may also contain other, directive-specific
# data.
def parse_directive(text)
case type = text[0]
when "\x00"
# Success
{ :type => :OK }
when "\x01"
{ :type => :warning,
:message => text[1..-1] }
when "\x02"
{ :type => :error,
:message => text[1..-1] }
when ?T
parts = text[1..-1].split(/ /, 4).map { |i| i.to_i }
{ :type => :times,
:mtime => Time.at(parts[0], parts[1]),
:atime => Time.at(parts[2], parts[3]) }
when ?C, ?D
parts = text[1..-1].split(/ /, 3)
{ :type => (type == ?C ? :file : :directory),
:mode => parts[0].to_i(8),
:size => parts[1].to_i,
:name => parts[2].chomp }
when ?E
{ :type => :end }
else raise ArgumentError, "unknown directive: #{text.inspect}"
end
end
# Sets the new directory as the current directory, creates the directory
# if it does not exist, and then falls back into #read_directive_state.
def read_directory(channel, directive)
if !channel[:options][:recursive]
raise Net::SCP::Error, ":recursive not specified for directory download"
end
channel[:local] = File.join(channel[:local], directive[:name])
if File.exist?(channel[:local]) && !File.directory?(channel[:local])
raise Net::SCP::Error, "#{channel[:local]} already exists and is not a directory"
elsif !File.exist?(channel[:local])
Dir.mkdir(channel[:local], directive[:mode] | 0700)
end
if channel[:options][:preserve] && channel[:times]
File.utime(channel[:times][:atime], channel[:times][:mtime], channel[:local])
end
channel[:stack] << directive
channel[:times] = nil
end
# Opens the given file locally, and switches to #read_data_state to do the
# actual read.
def read_file(channel, directive)
if !channel[:local].respond_to?(:write)
directive[:name] = (channel[:options][:recursive] || File.directory?(channel[:local])) ?
File.join(channel[:local], directive[:name]) :
channel[:local]
end
channel[:file] = directive.merge(:times => channel[:times])
channel[:io] = channel[:local].respond_to?(:write) ? channel[:local] :
File.new(directive[:name], "wb", directive[:mode] | 0600)
channel[:times] = nil
channel[:remaining] = channel[:file][:size]
channel[:state] = :read_data
progress_callback(channel, channel[:file][:name], 0, channel[:file][:size])
end
end
end; end
net-scp-4.0.0/lib/net/scp/errors.rb 0000664 0000000 0000000 00000000102 14322300260 0017027 0 ustar 00root root 0000000 0000000 module Net; class SCP
class Error < RuntimeError; end
end; end net-scp-4.0.0/lib/net/scp/upload.rb 0000664 0000000 0000000 00000013237 14322300260 0017014 0 ustar 00root root 0000000 0000000 require 'net/scp/errors'
module Net; class SCP
# This module implements the state machine for uploading information to
# a remote server. It exposes no public methods. See Net::SCP#upload for
# a discussion of how to use Net::SCP to upload data.
module Upload
private
# The default read chunk size, if an explicit chunk-size is not specified
# by the client.
DEFAULT_CHUNK_SIZE = 16384
# The start state for uploads. Simply sets up the upload scaffolding,
# sets the current item to upload, and jumps to #upload_current_state.
def upload_start_state(channel)
if channel[:local].respond_to?(:read)
channel[:options].delete(:recursive)
channel[:options].delete(:preserve)
end
channel[:chunk_size] = channel[:options][:chunk_size] || DEFAULT_CHUNK_SIZE
set_current(channel, channel[:local])
await_response(channel, :upload_current)
end
# Determines what the next thing to upload is, and branches. If the next
# item is a file, goes to #upload_file_state. If it is a directory, goes
# to #upload_directory_state.
def upload_current_state(channel)
if channel[:current].respond_to?(:read)
upload_file_state(channel)
elsif File.directory?(channel[:current])
raise Net::SCP::Error, "can't upload directories unless :recursive" unless channel[:options][:recursive]
upload_directory_state(channel)
elsif File.file?(channel[:current])
upload_file_state(channel)
else
raise Net::SCP::Error, "not a directory or a regular file: #{channel[:current].inspect}"
end
end
# After transferring attributes (if requested), sends a 'D' directive and
# awaites the server's 0-byte response. Then goes to #next_item_state.
def upload_directory_state(channel)
if preserve_attributes_if_requested(channel)
mode = channel[:stat].mode & 07777
directive = "D%04o %d %s\n" % [mode, 0, File.basename(channel[:current])]
channel.send_data(directive)
channel[:cwd] = channel[:current]
channel[:stack] << Dir.entries(channel[:current]).reject { |i| i == "." || i == ".." }
await_response(channel, :next_item)
end
end
# After transferring attributes (if requested), sends a 'C' directive and
# awaits the server's 0-byte response. Then goes to #send_data_state.
def upload_file_state(channel)
if preserve_attributes_if_requested(channel)
mode = channel[:stat] ? channel[:stat].mode & 07777 : channel[:options][:mode]
channel[:name] = channel[:current].respond_to?(:read) ? channel[:remote] : channel[:current]
directive = "C%04o %d %s\n" % [mode || 0640, channel[:size], File.basename(channel[:name])]
channel.send_data(directive)
channel[:io] = channel[:current].respond_to?(:read) ? channel[:current] : File.open(channel[:current], "rb")
channel[:sent] = 0
progress_callback(channel, channel[:name], channel[:sent], channel[:size])
await_response(channel, :send_data)
end
end
# If any data remains to be transferred from the current file, sends it.
# Otherwise, sends a 0-byte and transfers to #next_item_state.
def send_data_state(channel)
data = channel[:io].read(channel[:chunk_size])
if data.nil?
channel[:io].close unless channel[:local].respond_to?(:read)
channel.send_data("\0")
await_response(channel, :next_item)
else
channel[:sent] += data.length
progress_callback(channel, channel[:name], channel[:sent], channel[:size])
channel.send_data(data)
end
end
# Checks the work queue to see what needs to be done next. If there is
# nothing to do, calls Net::SCP#finish_state. If we're at the end of a
# directory, sends an 'E' directive and waits for the server to respond
# before moving to #next_item_state. Otherwise, sets the next thing to
# upload and moves to #upload_current_state.
def next_item_state(channel)
if channel[:stack].empty?
finish_state(channel)
else
next_item = channel[:stack].last.shift
if next_item.nil?
channel[:stack].pop
channel[:cwd] = File.dirname(channel[:cwd])
channel.send_data("E\n")
await_response(channel, channel[:stack].empty? ? :finish : :next_item)
else
set_current(channel, next_item)
upload_current_state(channel)
end
end
end
# Sets the given +path+ as the new current item to upload.
def set_current(channel, path)
path = channel[:cwd] ? File.join(channel[:cwd], path) : path
channel[:current] = path
if channel[:current].respond_to?(:read)
channel[:stat] = channel[:current].stat if channel[:current].respond_to?(:stat)
else
channel[:stat] = File.stat(channel[:current])
end
channel[:size] = channel[:stat] ? channel[:stat].size : channel[:current].size
end
# If the :preserve option is set, send a 'T' directive and wait for the
# server to respond before proceeding to either #upload_file_state or
# #upload_directory_state, depending on what is being uploaded.
def preserve_attributes_if_requested(channel)
if channel[:options][:preserve] && !channel[:preserved]
channel[:preserved] = true
stat = channel[:stat]
directive = "T%d %d %d %d\n" % [stat.mtime.to_i, stat.mtime.usec, stat.atime.to_i, stat.atime.usec]
channel.send_data(directive)
type = stat.directory? ? :directory : :file
await_response(channel, "upload_#{type}")
return false
else
channel[:preserved] = false
return true
end
end
end
end; end net-scp-4.0.0/lib/net/scp/version.rb 0000664 0000000 0000000 00000004205 14322300260 0017210 0 ustar 00root root 0000000 0000000 module Net
module SCP
# A class for describing the current version of a library. The version
# consists of three parts: the +major+ number, the +minor+ number, and the
# +tiny+ (or +patch+) number.
#
# Two Version instances may be compared, so that you can test that a version
# of a library is what you require:
#
# require 'net/scp/version'
#
# if Net::SCP::Version::CURRENT < Net::SCP::Version[2,1,0]
# abort "your software is too old!"
# end
class Version
include Comparable
# A convenience method for instantiating a new Version instance with the
# given +major+, +minor+, and +tiny+ components.
def self.[](major, minor, tiny, pre = nil)
new(major, minor, tiny, pre)
end
attr_reader :major, :minor, :tiny
# Create a new Version object with the given components.
def initialize(major, minor, tiny, pre = nil)
@major, @minor, @tiny, @pre = major, minor, tiny, pre
end
# Compare this version to the given +version+ object.
def <=>(version)
to_i <=> version.to_i
end
# Converts this version object to a string, where each of the three
# version components are joined by the '.' character. E.g., 2.0.0.
def to_s
@to_s ||= [@major, @minor, @tiny, @pre].compact.join(".")
end
# Converts this version to a canonical integer that may be compared
# against other version objects.
def to_i
@to_i ||= @major * 1_000_000 + @minor * 1_000 + @tiny
end
# The major component of this version of the Net::SSH library
MAJOR = 4
# The minor component of this version of the Net::SSH library
MINOR = 0
# The tiny component of this version of the Net::SSH library
TINY = 0
# The prerelease component of this version of the Net::SSH library
# nil allowed
PRE = nil
# The current version of the Net::SSH library as a Version instance
CURRENT = new(*[MAJOR, MINOR, TINY, PRE].compact)
# The current version of the Net::SSH library as a String
STRING = CURRENT.to_s
end
end
end
net-scp-4.0.0/lib/uri/ 0000775 0000000 0000000 00000000000 14322300260 0014421 5 ustar 00root root 0000000 0000000 net-scp-4.0.0/lib/uri/open-scp.rb 0000664 0000000 0000000 00000000646 14322300260 0016500 0 ustar 00root root 0000000 0000000 require 'open-uri'
require 'uri/scp'
require 'net/scp'
OpenURI::Options[:ssh] = nil
module URI
class SCP
def buffer_open(buf, proxy, open_options)
options = open_options.merge(:port => port, :password => password)
progress = options.delete(:progress_proc)
buf << Net::SCP.download!(host, user, path, nil, options, &progress)
buf.io.rewind
end
include OpenURI::OpenRead
end
end
net-scp-4.0.0/lib/uri/scp.rb 0000664 0000000 0000000 00000001363 14322300260 0015536 0 ustar 00root root 0000000 0000000 require 'uri/generic'
module URI
class SCP < Generic
DEFAULT_PORT = 22
COMPONENT = [
:scheme,
:userinfo,
:host, :port, :path,
:query
].freeze
attr_reader :options
def self.new2(user, password, host, port, path, query)
new('scp', [user, password], host, port, nil, path, nil, query)
end
def initialize(*args)
super(*args)
@options = Hash.new
(query || "").split(/&/).each do |pair|
name, value = pair.split(/=/, 2)
opt_name = name.to_sym
values = value.split(/,/).map { |v| v.to_i.to_s == v ? v.to_i : v }
values = values.first if values.length == 1
options[opt_name] = values
end
end
end
@@schemes['SCP'] = SCP
end net-scp-4.0.0/net-scp-public_cert.pem 0000664 0000000 0000000 00000002244 14322300260 0017423 0 ustar 00root root 0000000 0000000 -----BEGIN CERTIFICATE-----
MIIDQDCCAiigAwIBAgIBATANBgkqhkiG9w0BAQsFADAlMSMwIQYDVQQDDBpuZXRz
c2gvREM9c29sdXRpb3VzL0RDPWNvbTAeFw0yMjA3MjUxODEzNTJaFw0yMzA3MjUx
ODEzNTJaMCUxIzAhBgNVBAMMGm5ldHNzaC9EQz1zb2x1dGlvdXMvREM9Y29tMIIB
IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxieE22fR/qmdPKUHyYTyUx2g
wskLwrCkxay+Tvc97ZZUOwf85LDDDPqhQaTWLvRwnIOMgQE2nBPzwalVclK6a+pW
x/18KDeZY15vm3Qn5p42b0wi9hUxOqPm3J2hdCLCcgtENgdX21nVzejn39WVqFJO
lntgSDNW5+kCS8QaRsmIbzj17GKKkrsw39kiQw7FhWfJFeTjddzoZiWwc59KA/Bx
fBbmDnsMLAtAtauMOxORrbx3EOY7sHku/kSrMg3FXFay7jc6BkbbUij+MjJ/k82l
4o8o0YO4BAnya90xgEmgOG0LCCxRhuXQFnMDuDjK2XnUe0h4/6NCn94C+z9GsQID
AQABo3sweTAJBgNVHRMEAjAAMAsGA1UdDwQEAwIEsDAdBgNVHQ4EFgQUBfKiwO2e
M4NEiRrVG793qEPLYyMwHwYDVR0RBBgwFoEUbmV0c3NoQHNvbHV0aW91cy5jb20w
HwYDVR0SBBgwFoEUbmV0c3NoQHNvbHV0aW91cy5jb20wDQYJKoZIhvcNAQELBQAD
ggEBADxmDQVPrCXVbQqdaRJvoEdD/4LDWqSq6huG4n4PvhNmi9sKRVNh4eJNwFx+
VBX5PGeha0OL9tr5dlMLV7aCgty+3BTIg+Li8Ifc+TOUi+8nIHs6U4SZ4vFNs/bt
WUe6k027p0PHXPl63c5qQXnc3fkap7TFZJpFNEB0LbwHeQ98t1Tr9BAi/aZPEHTX
/JMWwrWBMOCPXCuZtvrRFRPgac7ElGu9r1rQWofdljxOPWwnjRdWc5jcgjq5/SAY
JHNAmLO0TwBERFVJ/Y/uu1o4GsxWG+7tNq8eLJSrBqh+Ty9yIWuaJ2njNB8rCkS6
1lmaMtrHdEPQAS75oTsrq4bfdOQ=
-----END CERTIFICATE-----
net-scp-4.0.0/net-scp.gemspec 0000664 0000000 0000000 00000003444 14322300260 0015777 0 ustar 00root root 0000000 0000000
require_relative 'lib/net/scp/version'
Gem::Specification.new do |spec|
spec.name = "net-scp"
spec.version = Net::SCP::Version::STRING
spec.authors = ["Jamis Buck", "Delano Mandelbaum", "Mikl\u{f3}s Fazekas"]
spec.email = ["net-ssh@solutious.com"]
if ENV['NET_SSH_BUILDGEM_SIGNED']
spec.cert_chain = ["net-scp-public_cert.pem"]
spec.signing_key = "/mnt/gem/net-ssh-private_key.pem"
end
spec.summary = %q{A pure Ruby implementation of the SCP client protocol.}
spec.description = %q{A pure Ruby implementation of the SCP client protocol}
spec.homepage = "https://github.com/net-ssh/net-scp"
spec.license = "MIT"
spec.required_rubygems_version = Gem::Requirement.new(">= 0") if spec.respond_to? :required_rubygems_version=
spec.metadata = {
"changelog_uri" => "https://github.com/net-ssh/net-scp/blob/master/CHANGES.txt"
}
spec.extra_rdoc_files = [
"LICENSE.txt",
"README.rdoc"
]
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
spec.require_paths = ["lib"]
if spec.respond_to? :specification_version then
spec.specification_version = 3
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
spec.add_runtime_dependency(%q, [">= 2.6.5", "< 8.0.0"])
spec.add_development_dependency(%q, [">= 0"])
spec.add_development_dependency(%q, [">= 0"])
else
spec.add_dependency(%q, [">= 2.6.5", "< 8.0.0"])
spec.add_dependency(%q, [">= 0"])
spec.add_dependency(%q, [">= 0"])
end
else
spec.add_dependency(%q, [">= 2.6.5", "< 8.0.0"])
spec.add_dependency(%q, [">= 0"])
spec.add_dependency(%q, [">= 0"])
end
end
net-scp-4.0.0/setup.rb 0000664 0000000 0000000 00000067237 14322300260 0014560 0 ustar 00root root 0000000 0000000 #
# setup.rb
#
# Copyright (c) 2000-2004 Minero Aoki
#
# This program is free software.
# You can distribute/modify this program under the terms of
# the GNU Lesser General Public License version 2.1.
#
#
# For backward compatibility
#
unless Enumerable.method_defined?(:map)
module Enumerable
alias map collect
end
end
unless Enumerable.method_defined?(:detect)
module Enumerable
alias detect find
end
end
unless Enumerable.method_defined?(:select)
module Enumerable
alias select find_all
end
end
unless Enumerable.method_defined?(:reject)
module Enumerable
def reject
result = []
each do |i|
result.push i unless yield(i)
end
result
end
end
end
unless Enumerable.method_defined?(:inject)
module Enumerable
def inject(result)
each do |i|
result = yield(result, i)
end
result
end
end
end
unless Enumerable.method_defined?(:any?)
module Enumerable
def any?
each do |i|
return true if yield(i)
end
false
end
end
end
unless File.respond_to?(:read)
def File.read(fname)
open(fname) {|f|
return f.read
}
end
end
#
# Application independent utilities
#
def File.binread(fname)
open(fname, 'rb') {|f|
return f.read
}
end
# for corrupted windows stat(2)
def File.dir?(path)
File.directory?((path[-1,1] == '/') ? path : path + '/')
end
#
# Config
#
if arg = ARGV.detect{|arg| /\A--rbconfig=/ =~ arg }
ARGV.delete(arg)
require arg.split(/=/, 2)[1]
$".push 'rbconfig.rb'
else
require 'rbconfig'
end
def multipackage_install?
FileTest.directory?(File.dirname($0) + '/packages')
end
class ConfigTable
c = ::Config::CONFIG
rubypath = c['bindir'] + '/' + c['ruby_install_name']
major = c['MAJOR'].to_i
minor = c['MINOR'].to_i
teeny = c['TEENY'].to_i
version = "#{major}.#{minor}"
# ruby ver. >= 1.4.4?
newpath_p = ((major >= 2) or
((major == 1) and
((minor >= 5) or
((minor == 4) and (teeny >= 4)))))
subprefix = lambda {|path|
path.sub(/\A#{Regexp.quote(c['prefix'])}/o, '$prefix')
}
if c['rubylibdir']
# V < 1.6.3
stdruby = subprefix.call(c['rubylibdir'])
siteruby = subprefix.call(c['sitedir'])
versite = subprefix.call(c['sitelibdir'])
sodir = subprefix.call(c['sitearchdir'])
elsif newpath_p
# 1.4.4 <= V <= 1.6.3
stdruby = "$prefix/lib/ruby/#{version}"
siteruby = subprefix.call(c['sitedir'])
versite = siteruby + '/' + version
sodir = "$site-ruby/#{c['arch']}"
else
# V < 1.4.4
stdruby = "$prefix/lib/ruby/#{version}"
siteruby = "$prefix/lib/ruby/#{version}/site_ruby"
versite = siteruby
sodir = "$site-ruby/#{c['arch']}"
end
if arg = c['configure_args'].split.detect {|arg| /--with-make-prog=/ =~ arg }
makeprog = arg.sub(/'/, '').split(/=/, 2)[1]
else
makeprog = 'make'
end
common_descripters = [
[ 'prefix', [ c['prefix'],
'path',
'path prefix of target environment' ] ],
[ 'std-ruby', [ stdruby,
'path',
'the directory for standard ruby libraries' ] ],
[ 'site-ruby-common', [ siteruby,
'path',
'the directory for version-independent non-standard ruby libraries' ] ],
[ 'site-ruby', [ versite,
'path',
'the directory for non-standard ruby libraries' ] ],
[ 'bin-dir', [ '$prefix/bin',
'path',
'the directory for commands' ] ],
[ 'rb-dir', [ '$site-ruby',
'path',
'the directory for ruby scripts' ] ],
[ 'so-dir', [ sodir,
'path',
'the directory for ruby extentions' ] ],
[ 'data-dir', [ '$prefix/share',
'path',
'the directory for shared data' ] ],
[ 'ruby-path', [ rubypath,
'path',
'path to set to #! line' ] ],
[ 'ruby-prog', [ rubypath,
'name',
'the ruby program using for installation' ] ],
[ 'make-prog', [ makeprog,
'name',
'the make program to compile ruby extentions' ] ],
[ 'without-ext', [ 'no',
'yes/no',
'does not compile/install ruby extentions' ] ]
]
multipackage_descripters = [
[ 'with', [ '',
'name,name...',
'package names that you want to install',
'ALL' ] ],
[ 'without', [ '',
'name,name...',
'package names that you do not want to install',
'NONE' ] ]
]
if multipackage_install?
DESCRIPTER = common_descripters + multipackage_descripters
else
DESCRIPTER = common_descripters
end
SAVE_FILE = 'config.save'
def ConfigTable.each_name(&block)
keys().each(&block)
end
def ConfigTable.keys
DESCRIPTER.map {|name, *dummy| name }
end
def ConfigTable.each_definition(&block)
DESCRIPTER.each(&block)
end
def ConfigTable.get_entry(name)
name, ent = DESCRIPTER.assoc(name)
ent
end
def ConfigTable.get_entry!(name)
get_entry(name) or raise ArgumentError, "no such config: #{name}"
end
def ConfigTable.add_entry(name, vals)
ConfigTable::DESCRIPTER.push [name,vals]
end
def ConfigTable.remove_entry(name)
get_entry(name) or raise ArgumentError, "no such config: #{name}"
DESCRIPTER.delete_if {|n, arr| n == name }
end
def ConfigTable.config_key?(name)
get_entry(name) ? true : false
end
def ConfigTable.bool_config?(name)
ent = get_entry(name) or return false
ent[1] == 'yes/no'
end
def ConfigTable.value_config?(name)
ent = get_entry(name) or return false
ent[1] != 'yes/no'
end
def ConfigTable.path_config?(name)
ent = get_entry(name) or return false
ent[1] == 'path'
end
class << self
alias newobj new
end
def ConfigTable.new
c = newobj()
c.initialize_from_table
c
end
def ConfigTable.load
c = newobj()
c.initialize_from_file
c
end
def initialize_from_table
@table = {}
DESCRIPTER.each do |k, (default, vname, desc, default2)|
@table[k] = default
end
end
def initialize_from_file
raise InstallError, "#{File.basename $0} config first"\
unless File.file?(SAVE_FILE)
@table = {}
File.foreach(SAVE_FILE) do |line|
k, v = line.split(/=/, 2)
@table[k] = v.strip
end
end
def save
File.open(SAVE_FILE, 'w') {|f|
@table.each do |k, v|
f.printf "%s=%s\n", k, v if v
end
}
end
def []=(k, v)
raise InstallError, "unknown config option #{k}"\
unless ConfigTable.config_key?(k)
@table[k] = v
end
def [](key)
return nil unless @table[key]
@table[key].gsub(%r<\$([^/]+)>) { self[$1] }
end
def set_raw(key, val)
@table[key] = val
end
def get_raw(key)
@table[key]
end
end
module MetaConfigAPI
def eval_file_ifexist(fname)
instance_eval File.read(fname), fname, 1 if File.file?(fname)
end
def config_names
ConfigTable.keys
end
def config?(name)
ConfigTable.config_key?(name)
end
def bool_config?(name)
ConfigTable.bool_config?(name)
end
def value_config?(name)
ConfigTable.value_config?(name)
end
def path_config?(name)
ConfigTable.path_config?(name)
end
def add_config(name, argname, default, desc)
ConfigTable.add_entry name,[default,argname,desc]
end
def add_path_config(name, default, desc)
add_config name, 'path', default, desc
end
def add_bool_config(name, default, desc)
add_config name, 'yes/no', default ? 'yes' : 'no', desc
end
def set_config_default(name, default)
if bool_config?(name)
ConfigTable.get_entry!(name)[0] = (default ? 'yes' : 'no')
else
ConfigTable.get_entry!(name)[0] = default
end
end
def remove_config(name)
ent = ConfigTable.get_entry(name)
ConfigTable.remove_entry name
ent
end
end
#
# File Operations
#
module FileOperations
def mkdir_p(dirname, prefix = nil)
dirname = prefix + dirname if prefix
$stderr.puts "mkdir -p #{dirname}" if verbose?
return if no_harm?
# does not check '/'... it's too abnormal case
dirs = dirname.split(%r<(?=/)>)
if /\A[a-z]:\z/i =~ dirs[0]
disk = dirs.shift
dirs[0] = disk + dirs[0]
end
dirs.each_index do |idx|
path = dirs[0..idx].join('')
Dir.mkdir path unless File.dir?(path)
end
end
def rm_f(fname)
$stderr.puts "rm -f #{fname}" if verbose?
return if no_harm?
if File.exist?(fname) or File.symlink?(fname)
File.chmod 0777, fname
File.unlink fname
end
end
def rm_rf(dn)
$stderr.puts "rm -rf #{dn}" if verbose?
return if no_harm?
Dir.chdir dn
Dir.foreach('.') do |fn|
next if fn == '.'
next if fn == '..'
if File.dir?(fn)
verbose_off {
rm_rf fn
}
else
verbose_off {
rm_f fn
}
end
end
Dir.chdir '..'
Dir.rmdir dn
end
def move_file(src, dest)
File.unlink dest if File.exist?(dest)
begin
File.rename src, dest
rescue
File.open(dest, 'wb') {|f| f.write File.binread(src) }
File.chmod File.stat(src).mode, dest
File.unlink src
end
end
def install(from, dest, mode, prefix = nil)
$stderr.puts "install #{from} #{dest}" if verbose?
return if no_harm?
realdest = prefix + dest if prefix
realdest = File.join(realdest, File.basename(from)) if File.dir?(realdest)
str = File.binread(from)
if diff?(str, realdest)
verbose_off {
rm_f realdest if File.exist?(realdest)
}
File.open(realdest, 'wb') {|f|
f.write str
}
File.chmod mode, realdest
File.open("#{objdir_root()}/InstalledFiles", 'a') {|f|
if prefix
f.puts realdest.sub(prefix, '')
else
f.puts realdest
end
}
end
end
def diff?(new_content, path)
return true unless File.exist?(path)
new_content != File.binread(path)
end
def command(str)
$stderr.puts str if verbose?
system str or raise RuntimeError, "'system #{str}' failed"
end
def ruby(str)
command config('ruby-prog') + ' ' + str
end
def make(task = '')
command config('make-prog') + ' ' + task
end
def extdir?(dir)
File.exist?(dir + '/MANIFEST')
end
def all_files_in(dirname)
Dir.open(dirname) {|d|
return d.select {|ent| File.file?("#{dirname}/#{ent}") }
}
end
REJECT_DIRS = %w(
CVS SCCS RCS CVS.adm
)
def all_dirs_in(dirname)
Dir.open(dirname) {|d|
return d.select {|n| File.dir?("#{dirname}/#{n}") } - %w(. ..) - REJECT_DIRS
}
end
end
#
# Main Installer
#
class InstallError < StandardError; end
module HookUtils
def run_hook(name)
try_run_hook "#{curr_srcdir()}/#{name}" or
try_run_hook "#{curr_srcdir()}/#{name}.rb"
end
def try_run_hook(fname)
return false unless File.file?(fname)
begin
instance_eval File.read(fname), fname, 1
rescue
raise InstallError, "hook #{fname} failed:\n" + $!.message
end
true
end
end
module HookScriptAPI
def get_config(key)
@config[key]
end
alias config get_config
def set_config(key, val)
@config[key] = val
end
#
# srcdir/objdir (works only in the package directory)
#
#abstract srcdir_root
#abstract objdir_root
#abstract relpath
def curr_srcdir
"#{srcdir_root()}/#{relpath()}"
end
def curr_objdir
"#{objdir_root()}/#{relpath()}"
end
def srcfile(path)
"#{curr_srcdir()}/#{path}"
end
def srcexist?(path)
File.exist?(srcfile(path))
end
def srcdirectory?(path)
File.dir?(srcfile(path))
end
def srcfile?(path)
File.file? srcfile(path)
end
def srcentries(path = '.')
Dir.open("#{curr_srcdir()}/#{path}") {|d|
return d.to_a - %w(. ..)
}
end
def srcfiles(path = '.')
srcentries(path).select {|fname|
File.file?(File.join(curr_srcdir(), path, fname))
}
end
def srcdirectories(path = '.')
srcentries(path).select {|fname|
File.dir?(File.join(curr_srcdir(), path, fname))
}
end
end
class ToplevelInstaller
Version = '3.2.4'
Copyright = 'Copyright (c) 2000-2004 Minero Aoki'
TASKS = [
[ 'config', 'saves your configurations' ],
[ 'show', 'shows current configuration' ],
[ 'setup', 'compiles ruby extentions and others' ],
[ 'install', 'installs files' ],
[ 'clean', "does `make clean' for each extention" ],
[ 'distclean',"does `make distclean' for each extention" ]
]
def ToplevelInstaller.invoke
instance().invoke
end
@singleton = nil
def ToplevelInstaller.instance
@singleton ||= new(File.dirname($0))
@singleton
end
include MetaConfigAPI
def initialize(ardir_root)
@config = nil
@options = { 'verbose' => true }
@ardir = File.expand_path(ardir_root)
end
def inspect
"#<#{self.class} #{__id__()}>"
end
def invoke
run_metaconfigs
task = parsearg_global()
@config = load_config(task)
__send__ "parsearg_#{task}"
init_installers
__send__ "exec_#{task}"
end
def run_metaconfigs
eval_file_ifexist "#{@ardir}/metaconfig"
end
def load_config(task)
case task
when 'config'
ConfigTable.new
when 'clean', 'distclean'
if File.exist?('config.save')
then ConfigTable.load
else ConfigTable.new
end
else
ConfigTable.load
end
end
def init_installers
@installer = Installer.new(@config, @options, @ardir, File.expand_path('.'))
end
#
# Hook Script API bases
#
def srcdir_root
@ardir
end
def objdir_root
'.'
end
def relpath
'.'
end
#
# Option Parsing
#
def parsearg_global
valid_task = /\A(?:#{TASKS.map {|task,desc| task }.join '|'})\z/
while arg = ARGV.shift
case arg
when /\A\w+\z/
raise InstallError, "invalid task: #{arg}" unless valid_task =~ arg
return arg
when '-q', '--quiet'
@options['verbose'] = false
when '--verbose'
@options['verbose'] = true
when '-h', '--help'
print_usage $stdout
exit 0
when '-v', '--version'
puts "#{File.basename($0)} version #{Version}"
exit 0
when '--copyright'
puts Copyright
exit 0
else
raise InstallError, "unknown global option '#{arg}'"
end
end
raise InstallError, <"
out.puts " ruby #{File.basename $0} [] []"
fmt = " %-20s %s\n"
out.puts
out.puts 'Global options:'
out.printf fmt, '-q,--quiet', 'suppress message outputs'
out.printf fmt, ' --verbose', 'output messages verbosely'
out.printf fmt, '-h,--help', 'print this message'
out.printf fmt, '-v,--version', 'print version and quit'
out.printf fmt, ' --copyright', 'print copyright and quit'
out.puts
out.puts 'Tasks:'
TASKS.each do |name, desc|
out.printf " %-10s %s\n", name, desc
end
out.puts
out.puts 'Options for config:'
ConfigTable.each_definition do |name, (default, arg, desc, default2)|
out.printf " %-20s %s [%s]\n",
'--'+ name + (ConfigTable.bool_config?(name) ? '' : '='+arg),
desc,
default2 || default
end
out.printf " %-20s %s [%s]\n",
'--rbconfig=path', 'your rbconfig.rb to load', "running ruby's"
out.puts
out.puts 'Options for install:'
out.printf " %-20s %s [%s]\n",
'--no-harm', 'only display what to do if given', 'off'
out.printf " %-20s %s [%s]\n",
'--prefix', 'install path prefix', '$prefix'
out.puts
end
#
# Task Handlers
#
def exec_config
@installer.exec_config
@config.save # must be final
end
def exec_setup
@installer.exec_setup
end
def exec_install
@installer.exec_install
end
def exec_show
ConfigTable.each_name do |k|
v = @config.get_raw(k)
if not v or v.empty?
v = '(not specified)'
end
printf "%-10s %s\n", k, v
end
end
def exec_clean
@installer.exec_clean
end
def exec_distclean
@installer.exec_distclean
end
end
class ToplevelInstallerMulti < ToplevelInstaller
include HookUtils
include HookScriptAPI
include FileOperations
def initialize(ardir)
super
@packages = all_dirs_in("#{@ardir}/packages")
raise 'no package exists' if @packages.empty?
end
def run_metaconfigs
eval_file_ifexist "#{@ardir}/metaconfig"
@packages.each do |name|
eval_file_ifexist "#{@ardir}/packages/#{name}/metaconfig"
end
end
def init_installers
@installers = {}
@packages.each do |pack|
@installers[pack] = Installer.new(@config, @options,
"#{@ardir}/packages/#{pack}",
"packages/#{pack}")
end
with = extract_selection(config('with'))
without = extract_selection(config('without'))
@selected = @installers.keys.select {|name|
(with.empty? or with.include?(name)) \
and not without.include?(name)
}
end
def extract_selection(list)
a = list.split(/,/)
a.each do |name|
raise InstallError, "no such package: #{name}" \
unless @installers.key?(name)
end
a
end
def print_usage(f)
super
f.puts 'Inluded packages:'
f.puts ' ' + @packages.sort.join(' ')
f.puts
end
#
# multi-package metaconfig API
#
attr_reader :packages
def declare_packages(list)
raise 'package list is empty' if list.empty?
list.each do |name|
raise "directory packages/#{name} does not exist"\
unless File.dir?("#{@ardir}/packages/#{name}")
end
@packages = list
end
#
# Task Handlers
#
def exec_config
run_hook 'pre-config'
each_selected_installers {|inst| inst.exec_config }
run_hook 'post-config'
@config.save # must be final
end
def exec_setup
run_hook 'pre-setup'
each_selected_installers {|inst| inst.exec_setup }
run_hook 'post-setup'
end
def exec_install
run_hook 'pre-install'
each_selected_installers {|inst| inst.exec_install }
run_hook 'post-install'
end
def exec_clean
rm_f 'config.save'
run_hook 'pre-clean'
each_selected_installers {|inst| inst.exec_clean }
run_hook 'post-clean'
end
def exec_distclean
rm_f 'config.save'
run_hook 'pre-distclean'
each_selected_installers {|inst| inst.exec_distclean }
run_hook 'post-distclean'
end
#
# lib
#
def each_selected_installers
Dir.mkdir 'packages' unless File.dir?('packages')
@selected.each do |pack|
$stderr.puts "Processing the package `#{pack}' ..." if @options['verbose']
Dir.mkdir "packages/#{pack}" unless File.dir?("packages/#{pack}")
Dir.chdir "packages/#{pack}"
yield @installers[pack]
Dir.chdir '../..'
end
end
def verbose?
@options['verbose']
end
def no_harm?
@options['no-harm']
end
end
class Installer
FILETYPES = %w( bin lib ext data )
include HookScriptAPI
include HookUtils
include FileOperations
def initialize(config, opt, srcroot, objroot)
@config = config
@options = opt
@srcdir = File.expand_path(srcroot)
@objdir = File.expand_path(objroot)
@currdir = '.'
end
def inspect
"#<#{self.class} #{File.basename(@srcdir)}>"
end
#
# Hook Script API bases
#
def srcdir_root
@srcdir
end
def objdir_root
@objdir
end
def relpath
@currdir
end
#
# configs/options
#
def no_harm?
@options['no-harm']
end
def verbose?
@options['verbose']
end
def verbose_off
begin
save, @options['verbose'] = @options['verbose'], false
yield
ensure
@options['verbose'] = save
end
end
#
# TASK config
#
def exec_config
exec_task_traverse 'config'
end
def config_dir_bin(rel)
end
def config_dir_lib(rel)
end
def config_dir_ext(rel)
extconf if extdir?(curr_srcdir())
end
def extconf
opt = @options['config-opt'].join(' ')
command "#{config('ruby-prog')} #{curr_srcdir()}/extconf.rb #{opt}"
end
def config_dir_data(rel)
end
#
# TASK setup
#
def exec_setup
exec_task_traverse 'setup'
end
def setup_dir_bin(rel)
all_files_in(curr_srcdir()).each do |fname|
adjust_shebang "#{curr_srcdir()}/#{fname}"
end
end
# modify: #!/usr/bin/ruby
# modify: #! /usr/bin/ruby
# modify: #!ruby
# not modify: #!/usr/bin/env ruby
SHEBANG_RE = /\A\#!\s*\S*ruby\S*/
def adjust_shebang(path)
return if no_harm?
tmpfile = File.basename(path) + '.tmp'
begin
File.open(path, 'rb') {|r|
File.open(tmpfile, 'wb') {|w|
first = r.gets
return unless SHEBANG_RE =~ first
$stderr.puts "adjusting shebang: #{File.basename path}" if verbose?
w.print first.sub(SHEBANG_RE, '#!' + config('ruby-path'))
w.write r.read
}
}
move_file tmpfile, File.basename(path)
ensure
File.unlink tmpfile if File.exist?(tmpfile)
end
end
def setup_dir_lib(rel)
end
def setup_dir_ext(rel)
make if extdir?(curr_srcdir())
end
def setup_dir_data(rel)
end
#
# TASK install
#
def exec_install
exec_task_traverse 'install'
end
def install_dir_bin(rel)
install_files collect_filenames_auto(), "#{config('bin-dir')}/#{rel}", 0755
end
def install_dir_lib(rel)
install_files ruby_scripts(), "#{config('rb-dir')}/#{rel}", 0644
end
def install_dir_ext(rel)
return unless extdir?(curr_srcdir())
install_files ruby_extentions('.'),
"#{config('so-dir')}/#{File.dirname(rel)}",
0555
end
def install_dir_data(rel)
install_files collect_filenames_auto(), "#{config('data-dir')}/#{rel}", 0644
end
def install_files(list, dest, mode)
mkdir_p dest, @options['install-prefix']
list.each do |fname|
install fname, dest, mode, @options['install-prefix']
end
end
def ruby_scripts
collect_filenames_auto().select {|n| /\.rb\z/ =~ n || "module.yml" == n }
end
# picked up many entries from cvs-1.11.1/src/ignore.c
reject_patterns = %w(
core RCSLOG tags TAGS .make.state
.nse_depinfo #* .#* cvslog.* ,* .del-* *.olb
*~ *.old *.bak *.BAK *.orig *.rej _$* *$
*.org *.in .*
)
mapping = {
'.' => '\.',
'$' => '\$',
'#' => '\#',
'*' => '.*'
}
REJECT_PATTERNS = Regexp.new('\A(?:' +
reject_patterns.map {|pat|
pat.gsub(/[\.\$\#\*]/) {|ch| mapping[ch] }
}.join('|') +
')\z')
def collect_filenames_auto
mapdir((existfiles() - hookfiles()).reject {|fname|
REJECT_PATTERNS =~ fname
})
end
def existfiles
all_files_in(curr_srcdir()) | all_files_in('.')
end
def hookfiles
%w( pre-%s post-%s pre-%s.rb post-%s.rb ).map {|fmt|
%w( config setup install clean ).map {|t| sprintf(fmt, t) }
}.flatten
end
def mapdir(filelist)
filelist.map {|fname|
if File.exist?(fname) # objdir
fname
else # srcdir
File.join(curr_srcdir(), fname)
end
}
end
def ruby_extentions(dir)
_ruby_extentions(dir) or
raise InstallError, "no ruby extention exists: 'ruby #{$0} setup' first"
end
DLEXT = /\.#{ ::Config::CONFIG['DLEXT'] }\z/
def _ruby_extentions(dir)
Dir.open(dir) {|d|
return d.select {|fname| DLEXT =~ fname }
}
end
#
# TASK clean
#
def exec_clean
exec_task_traverse 'clean'
rm_f 'config.save'
rm_f 'InstalledFiles'
end
def clean_dir_bin(rel)
end
def clean_dir_lib(rel)
end
def clean_dir_ext(rel)
return unless extdir?(curr_srcdir())
make 'clean' if File.file?('Makefile')
end
def clean_dir_data(rel)
end
#
# TASK distclean
#
def exec_distclean
exec_task_traverse 'distclean'
rm_f 'config.save'
rm_f 'InstalledFiles'
end
def distclean_dir_bin(rel)
end
def distclean_dir_lib(rel)
end
def distclean_dir_ext(rel)
return unless extdir?(curr_srcdir())
make 'distclean' if File.file?('Makefile')
end
#
# lib
#
def exec_task_traverse(task)
run_hook "pre-#{task}"
FILETYPES.each do |type|
if config('without-ext') == 'yes' and type == 'ext'
$stderr.puts 'skipping ext/* by user option' if verbose?
next
end
traverse task, type, "#{task}_dir_#{type}"
end
run_hook "post-#{task}"
end
def traverse(task, rel, mid)
dive_into(rel) {
run_hook "pre-#{task}"
__send__ mid, rel.sub(%r[\A.*?(?:/|\z)], '')
all_dirs_in(curr_srcdir()).each do |d|
traverse task, "#{rel}/#{d}", mid
end
run_hook "post-#{task}"
}
end
def dive_into(rel)
return unless File.dir?("#{@srcdir}/#{rel}")
dir = File.basename(rel)
Dir.mkdir dir unless File.dir?(dir)
prevdir = Dir.pwd
Dir.chdir dir
$stderr.puts '---> ' + rel if verbose?
@currdir = rel
yield
Dir.chdir prevdir
$stderr.puts '<--- ' + rel if verbose?
@currdir = File.dirname(rel)
end
end
if $0 == __FILE__
begin
if multipackage_install?
ToplevelInstallerMulti.invoke
else
ToplevelInstaller.invoke
end
rescue
raise if $DEBUG
$stderr.puts $!.message
$stderr.puts "Try 'ruby #{$0} --help' for detailed usage."
exit 1
end
end
net-scp-4.0.0/test/ 0000775 0000000 0000000 00000000000 14322300260 0014033 5 ustar 00root root 0000000 0000000 net-scp-4.0.0/test/common.rb 0000664 0000000 0000000 00000010767 14322300260 0015663 0 ustar 00root root 0000000 0000000 require 'test/unit'
require 'mocha/setup'
begin
gem 'net-ssh', ">= 2.0.0"
require 'net/ssh'
rescue LoadError
$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../../net-ssh/lib"
begin
require 'net/ssh'
require 'net/ssh/version'
raise LoadError, "wrong version" unless Net::SSH::Version::STRING >= '1.99.0'
rescue LoadError => e
abort "could not load net/ssh v2 (#{e.inspect})"
end
end
$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../lib"
require 'net/scp'
require 'net/ssh/test'
class Net::SSH::Test::Channel
def gets_ok
gets_data "\0"
end
def sends_ok
sends_data "\0"
end
end
class Net::SCP::TestCase < Test::Unit::TestCase
include Net::SSH::Test
def default_test
# do nothing, this is just a hacky-hack to work around Test::Unit's
# insistence that all TestCase subclasses have at least one test
# method defined.
end
protected
def prepare_file(path, contents="", mode=0666, mtime=Time.now, atime=Time.now)
entry = FileEntry.new(path, contents, mode, mtime, atime)
entry.stub!
entry
end
def prepare_directory(path, mode=0777, mtime=Time.now, atime=Time.now)
directory = DirectoryEntry.new(path, mode, mtime, atime)
yield directory if block_given?
directory.stub!
end
# The POSIX spec unfortunately allows all characters in file names except
# ASCII 0x00(NUL) and 0x2F(/)
#
# Ideally, we should be testing filenames with newlines, but Mocha doesn't
# like this at all, so we leave them out. However, the Shellwords module
# handles newlines just fine, so we can be reasonably confident that they
# will work in practice
def awful_file_name
(((0x00..0x7f).to_a - [0x00, 0x0a, 0x2b, 0x2f]).map { |n| n.chr }).join + '.txt'
end
def escaped_file_name
"\\\001\\\002\\\003\\\004\\\005\\\006\\\a\\\b\\\t\\\v\\\f\\\r\\\016\\\017\\\020\\\021\\\022\\\023\\\024\\\025\\\026\\\027\\\030\\\031\\\032\\\e\\\034\\\035\\\036\\\037\\ \\!\\\"\\#\\$\\%\\&\\'\\(\\)\\*,-.0123456789:\\;\\<\\=\\>\\?@ABCDEFGHIJKLMNOPQRSTUVWXYZ\\[\\\\\\]\\^_\\`abcdefghijklmnopqrstuvwxyz\\{\\|\\}\\~\\\177.txt"
end
class FileEntry
attr_reader :path, :contents, :mode, :mtime, :atime, :io
def initialize(path, contents, mode=0666, mtime=Time.now, atime=Time.now)
@path, @contents, @mode = path, contents, mode
@mtime, @atime = mtime, atime
end
def name
@name ||= File.basename(path)
end
def stub!
stat = Mocha::Mock.new("file::stat")
stat.stubs(:size => contents.length, :mode => mode, :mtime => mtime, :atime => atime, :directory? => false)
File.stubs(:stat).with(path).returns(stat)
File.stubs(:directory?).with(path).returns(false)
File.stubs(:file?).with(path).returns(true)
File.stubs(:open).with(path, "rb").returns(StringIO.new(contents))
@io = StringIO.new
File.stubs(:new).with(path, "wb", mode).returns(io)
end
end
class DirectoryEntry
attr_reader :path, :mode, :mtime, :atime
attr_reader :entries
def initialize(path, mode=0777, mtime=Time.now, atime=Time.now)
@path, @mode = path, mode
@mtime, @atime = mtime, atime
@entries = []
end
def name
@name ||= File.basename(path)
end
def file(name, *args)
(entries << FileEntry.new(File.join(path, name), *args)).last
end
def directory(name, *args)
entry = DirectoryEntry.new(File.join(path, name), *args)
yield entry if block_given?
(entries << entry).last
end
def stub!
Dir.stubs(:mkdir).with { |*a| a.first == path }
stat = Mocha::Mock.new("file::stat")
stat.stubs(:size => 1024, :mode => mode, :mtime => mtime, :atime => atime, :directory? => true)
File.stubs(:stat).with(path).returns(stat)
File.stubs(:directory?).with(path).returns(true)
File.stubs(:file?).with(path).returns(false)
Dir.stubs(:entries).with(path).returns(%w(. ..) + entries.map { |e| e.name }.sort)
entries.each { |e| e.stub! }
end
end
def expect_scp_session(arguments)
story do |session|
channel = session.opens_channel
channel.sends_exec "scp #{arguments}"
yield channel if block_given?
channel.sends_eof
channel.gets_exit_status
channel.gets_eof
channel.gets_close
channel.sends_close
end
end
def scp(options={})
@scp ||= Net::SCP.new(connection(options))
end
end
net-scp-4.0.0/test/test_all.rb 0000664 0000000 0000000 00000000157 14322300260 0016172 0 ustar 00root root 0000000 0000000 Dir.chdir(File.dirname(__FILE__)) do
(Dir['**/test_*.rb']-["test_all.rb"]).each { |file| require(file) }
end
net-scp-4.0.0/test/test_download.rb 0000664 0000000 0000000 00000017074 14322300260 0017237 0 ustar 00root root 0000000 0000000 require 'common'
class TestDownload < Net::SCP::TestCase
def test_download_file_should_transfer_file
file = prepare_file("/path/to/local.txt", "a" * 1234)
expect_scp_session "-f /path/to/remote.txt" do |channel|
simple_download(channel)
end
assert_scripted { scp.download!("/path/to/remote.txt", "/path/to/local.txt") }
assert_equal "a" * 1234, file.io.string
end
def test_download_file_with_spaces_in_name_should_escape_remote_file_name
_file = prepare_file("/path/to/local file.txt", "")
expect_scp_session "-f /path/to/remote\\ file.txt" do |channel|
channel.sends_ok
channel.gets_data "C0666 0 local file.txt\n"
channel.sends_ok
channel.gets_ok
channel.sends_ok
end
assert_scripted { scp.download!("/path/to/remote file.txt", "/path/to/local file.txt") }
end
def test_download_file_with_metacharacters_in_name_should_escape_remote_file_name
_file = prepare_file("/path/to/local/#{awful_file_name}", "")
expect_scp_session "-f /path/to/remote/#{escaped_file_name}" do |channel|
channel.sends_ok
channel.gets_data "C0666 0 #{awful_file_name}\n"
channel.sends_ok
channel.gets_ok
channel.sends_ok
end
assert_scripted { scp.download!("/path/to/remote/#{awful_file_name}", "/path/to/local/#{awful_file_name}") }
end
def test_download_with_preserve_should_send_times
file = prepare_file("/path/to/local.txt", "a" * 1234, 0644, Time.at(1234567890, 123456), Time.at(12121212, 232323))
expect_scp_session "-f -p /path/to/remote.txt" do |channel|
channel.sends_ok
channel.gets_data "T1234567890 123456 12121212 232323\n"
simple_download(channel, 0644)
end
File.expects(:utime).with(Time.at(12121212, 232323), Time.at(1234567890, 123456), "/path/to/local.txt")
assert_scripted { scp.download!("/path/to/remote.txt", "/path/to/local.txt", :preserve => true) }
assert_equal "a" * 1234, file.io.string
end
def test_download_with_error_should_respond_with_error_text
story do |session|
channel = session.opens_channel
channel.sends_exec "scp -f /path/to/remote.txt"
channel.sends_ok
channel.gets_data "\x01File not found: /path/to/remote.txt\n"
channel.sends_ok
channel.gets_eof
channel.gets_exit_status(1)
channel.gets_close
channel.sends_close
end
error = nil
Net::SSH::Test::Extensions::IO.with_test_extension do
begin
scp.download!("/path/to/remote.txt")
rescue
error = $!
end
end
assert_equal Net::SCP::Error, error.class
assert_equal "SCP did not finish successfully (1): File not found: /path/to/remote.txt\n", error.message
end
def test_download_with_progress_callback_should_invoke_callback
prepare_file("/path/to/local.txt", "a" * 3000 + "b" * 3000 + "c" * 3000 + "d" * 3000)
expect_scp_session "-f /path/to/remote.txt" do |channel|
channel.sends_ok
channel.gets_data "C0666 12000 remote.txt\n"
channel.sends_ok
channel.gets_data "a" * 3000
channel.inject_remote_delay!
channel.gets_data "b" * 3000
channel.inject_remote_delay!
channel.gets_data "c" * 3000
channel.inject_remote_delay!
channel.gets_data "d" * 3000
channel.gets_ok
channel.sends_ok
end
calls = []
progress = Proc.new { |ch, *args| calls << args }
assert_scripted do
scp.download!("/path/to/remote.txt", "/path/to/local.txt", &progress)
end
assert_equal ["/path/to/local.txt", 0, 12000], calls.shift
assert_equal ["/path/to/local.txt", 3000, 12000], calls.shift
assert_equal ["/path/to/local.txt", 6000, 12000], calls.shift
assert_equal ["/path/to/local.txt", 9000, 12000], calls.shift
assert_equal ["/path/to/local.txt", 12000, 12000], calls.shift
assert calls.empty?
end
def test_download_io_with_recursive_should_raise_error
expect_scp_session "-f -r /path/to/remote.txt"
Net::SSH::Test::Extensions::IO.with_test_extension do
assert_raises(Net::SCP::Error) { scp.download!("/path/to/remote.txt", StringIO.new, :recursive => true) }
end
end
def test_download_io_with_preserve_should_ignore_preserve
expect_scp_session "-f -p /path/to/remote.txt" do |channel|
simple_download(channel)
end
io = StringIO.new
assert_scripted { scp.download!("/path/to/remote.txt", io, :preserve => true) }
assert_equal "a" * 1234, io.string
end
def test_download_io_should_transfer_data
expect_scp_session "-f /path/to/remote.txt" do |channel|
simple_download(channel)
end
io = StringIO.new
assert_scripted { scp.download!("/path/to/remote.txt", io) }
assert_equal "a" * 1234, io.string
end
def test_download_bang_without_target_should_return_string
expect_scp_session "-f /path/to/remote.txt" do |channel|
simple_download(channel)
end
assert_scripted do
assert_equal "a" * 1234, scp.download!("/path/to/remote.txt")
end
end
def test_download_directory_without_recursive_should_raise_error
expect_scp_session "-f /path/to/remote" do |channel|
channel.sends_ok
channel.gets_data "D0755 0 remote\n"
end
Net::SSH::Test::Extensions::IO.with_test_extension do
assert_raises(Net::SCP::Error) { scp.download!("/path/to/remote") }
end
end
def test_download_should_raise_error_if_gets_not_ok
prepare_file("/path/to/local.txt", "")
expect_scp_session "-f /path/to/remote.txt" do |channel|
channel.sends_ok
channel.gets_data "C0666 0 remote.txt\n"
channel.sends_ok
channel.gets_data "\1"
end
Net::SSH::Test::Extensions::IO.with_test_extension do
e = assert_raises(Net::SCP::Error) { scp.download!("/path/to/remote.txt", "/path/to/local.txt") }
assert_equal("\1", e.message)
end
end
def test_download_directory_should_raise_error_if_local_exists_and_is_not_directory
File.stubs(:exist?).with("/path/to/local").returns(true)
File.stubs(:exist?).with("/path/to/local/remote").returns(true)
File.stubs(:directory?).with("/path/to/local/remote").returns(false)
expect_scp_session "-f -r /path/to/remote" do |channel|
channel.sends_ok
channel.gets_data "D0755 0 remote\n"
channel.sends_ok
channel.gets_data "E\n"
channel.sends_ok
end
Net::SSH::Test::Extensions::IO.with_test_extension do
e = assert_raises(Net::SCP::Error) { scp.download!("/path/to/remote", "/path/to/local", :recursive => true) }
assert_match(/exists and is not a directory/, e.message)
end
end
def test_download_directory_should_create_directory_and_files_locally
file = nil
prepare_directory "/path/to/local" do |dir|
dir.directory "remote" do |dir2|
dir2.directory "sub" do |dir3|
file = dir3.file "remote.txt", ""
end
end
end
expect_scp_session "-f -r /path/to/remote" do |channel|
channel.sends_ok
channel.gets_data "D0755 0 remote\n"
channel.sends_ok
channel.gets_data "D0755 0 sub\n"
simple_download(channel)
channel.gets_data "E\n"
channel.sends_ok
channel.gets_data "E\n"
channel.sends_ok
end
Net::SSH::Test::Extensions::IO.with_test_extension do
scp.download!("/path/to/remote", "/path/to/local", :recursive => true, :ssh => { :verbose => :debug })
end
assert_equal "a" * 1234, file.io.string
end
private
def simple_download(channel, mode=0666)
channel.sends_ok
channel.gets_data "C%04o 1234 remote.txt\n" % mode
channel.sends_ok
channel.gets_data "a" * 1234
channel.gets_ok
channel.sends_ok
end
end
net-scp-4.0.0/test/test_scp.rb 0000664 0000000 0000000 00000003545 14322300260 0016213 0 ustar 00root root 0000000 0000000 require 'common'
class TestSCP < Net::SCP::TestCase
def test_start_without_block_should_return_scp_instance
ssh = stub('session', :logger => nil)
Net::SSH.expects(:start).
with("remote.host", "username", :password => "foo").
returns(ssh)
ssh.expects(:close).never
scp = Net::SCP.start("remote.host", "username", :password => "foo")
assert_instance_of Net::SCP, scp
assert_equal ssh, scp.session
end
def test_start_with_block_should_yield_scp_and_close_ssh_session
ssh = stub('session', :logger => nil)
Net::SSH.expects(:start).
with("remote.host", "username", :password => "foo").
returns(ssh)
ssh.expects(:loop)
ssh.expects(:close)
yielded = false
Net::SCP.start("remote.host", "username", :password => "foo") do |scp|
yielded = true
assert_instance_of Net::SCP, scp
assert_equal ssh, scp.session
end
assert yielded
end
def test_self_upload_should_instatiate_scp_and_invoke_synchronous_upload
scp = stub('scp')
scp.expects(:upload!).with("/path/to/local", "/path/to/remote", :recursive => true)
Net::SCP.expects(:start).
with("remote.host", "username", :password => "foo").
yields(scp)
Net::SCP.upload!("remote.host", "username", "/path/to/local", "/path/to/remote",
:ssh => { :password => "foo" }, :recursive => true)
end
def test_self_download_should_instatiate_scp_and_invoke_synchronous_download
scp = stub('scp')
scp.expects(:download!).with("/path/to/remote", "/path/to/local", :recursive => true).returns(:result)
Net::SCP.expects(:start).
with("remote.host", "username", :password => "foo").
yields(scp)
result = Net::SCP.download!("remote.host", "username", "/path/to/remote", "/path/to/local",
:ssh => { :password => "foo" }, :recursive => true)
assert_equal :result, result
end
end
net-scp-4.0.0/test/test_upload.rb 0000664 0000000 0000000 00000022265 14322300260 0016712 0 ustar 00root root 0000000 0000000 require 'common'
class TestUpload < Net::SCP::TestCase
def test_upload_file_should_transfer_file
prepare_file("/path/to/local.txt", "a" * 1234)
expect_scp_session "-t /path/to/remote.txt" do |channel|
channel.gets_ok
channel.sends_data "C0666 1234 local.txt\n"
channel.gets_ok
channel.sends_data "a" * 1234
channel.sends_ok
channel.gets_ok
end
assert_scripted { scp.upload!("/path/to/local.txt", "/path/to/remote.txt") }
end
def test_upload_file_with_spaces_in_name_should_escape_remote_file_name
prepare_file("/path/to/local file.txt", "")
expect_scp_session "-t /path/to/remote\\ file.txt" do |channel|
channel.gets_ok
channel.sends_data "C0666 0 local file.txt\n"
channel.gets_ok
channel.sends_ok
channel.gets_ok
end
assert_scripted { scp.upload!("/path/to/local file.txt", "/path/to/remote file.txt") }
end
def test_upload_file_with_metacharacters_in_name_should_escape_remote_file_name
prepare_file("/path/to/local/#{awful_file_name}", "")
expect_scp_session "-t /path/to/remote/#{escaped_file_name}" do |channel|
channel.gets_ok
channel.sends_data "C0666 0 #{awful_file_name}\n"
channel.gets_ok
channel.sends_ok
channel.gets_ok
end
assert_scripted { scp.upload!("/path/to/local/#{awful_file_name}", "/path/to/remote/#{awful_file_name}") }
end
def test_upload_file_with_preserve_should_send_times
prepare_file("/path/to/local.txt", "a" * 1234, 0666, Time.at(1234567890, 123456), Time.at(1234543210, 345678))
expect_scp_session "-t -p /path/to/remote.txt" do |channel|
channel.gets_ok
channel.sends_data "T1234567890 123456 1234543210 345678\n"
channel.gets_ok
channel.sends_data "C0666 1234 local.txt\n"
channel.gets_ok
channel.sends_data "a" * 1234
channel.sends_ok
channel.gets_ok
end
assert_scripted { scp.upload!("/path/to/local.txt", "/path/to/remote.txt", :preserve => true) }
end
def test_upload_file_with_progress_callback_should_invoke_callback
prepare_file("/path/to/local.txt", "a" * 3000 + "b" * 3000 + "c" * 3000 + "d" * 3000)
expect_scp_session "-t /path/to/remote.txt" do |channel|
channel.gets_ok
channel.sends_data "C0666 12000 local.txt\n"
channel.gets_ok
channel.sends_data "a" * 3000
channel.sends_data "b" * 3000
channel.sends_data "c" * 3000
channel.sends_data "d" * 3000
channel.sends_ok
channel.gets_ok
end
calls = []
progress = Proc.new do |ch, name, sent, total|
calls << [name, sent, total]
end
assert_scripted do
scp.upload!("/path/to/local.txt", "/path/to/remote.txt", :chunk_size => 3000, &progress)
end
assert_equal ["/path/to/local.txt", 0, 12000], calls.shift
assert_equal ["/path/to/local.txt", 3000, 12000], calls.shift
assert_equal ["/path/to/local.txt", 6000, 12000], calls.shift
assert_equal ["/path/to/local.txt", 9000, 12000], calls.shift
assert_equal ["/path/to/local.txt", 12000, 12000], calls.shift
assert calls.empty?
end
def test_upload_io_with_recursive_should_ignore_recursive
expect_scp_session "-t -r /path/to/remote.txt" do |channel|
channel.gets_ok
channel.sends_data "C0640 1234 remote.txt\n"
channel.gets_ok
channel.sends_data "a" * 1234
channel.sends_ok
channel.gets_ok
end
io = StringIO.new("a" * 1234)
assert_scripted { scp.upload!(io, "/path/to/remote.txt", :recursive => true) }
end
def test_upload_io_with_preserve_should_ignore_preserve
expect_scp_session "-t -p /path/to/remote.txt" do |channel|
channel.gets_ok
channel.sends_data "C0640 1234 remote.txt\n"
channel.gets_ok
channel.sends_data "a" * 1234
channel.sends_ok
channel.gets_ok
end
io = StringIO.new("a" * 1234)
assert_scripted { scp.upload!(io, "/path/to/remote.txt", :preserve => true) }
end
def test_upload_io_should_transfer_data
expect_scp_session "-t /path/to/remote.txt" do |channel|
channel.gets_ok
channel.sends_data "C0640 1234 remote.txt\n"
channel.gets_ok
channel.sends_data "a" * 1234
channel.sends_ok
channel.gets_ok
end
io = StringIO.new("a" * 1234)
assert_scripted { scp.upload!(io, "/path/to/remote.txt") }
end
def test_upload_io_with_mode_should_honor_mode_as_permissions
expect_scp_session "-t /path/to/remote.txt" do |channel|
channel.gets_ok
channel.sends_data "C0666 1234 remote.txt\n"
channel.gets_ok
channel.sends_data "a" * 1234
channel.sends_ok
channel.gets_ok
end
io = StringIO.new("a" * 1234)
assert_scripted { scp.upload!(io, "/path/to/remote.txt", :mode => 0666) }
end
def test_upload_directory_without_recursive_should_error
prepare_directory("/path/to/local")
expect_scp_session("-t /path/to/remote") do |channel|
channel.gets_ok
end
Net::SSH::Test::Extensions::IO.with_test_extension do
assert_raises(Net::SCP::Error) { scp.upload!("/path/to/local", "/path/to/remote") }
end
end
def test_upload_empty_directory_should_create_directory_and_finish
prepare_directory("/path/to/local")
expect_scp_session("-t -r /path/to/remote") do |channel|
channel.gets_ok
channel.sends_data "D0777 0 local\n"
channel.gets_ok
channel.sends_data "E\n"
channel.gets_ok
end
assert_scripted { scp.upload!("/path/to/local", "/path/to/remote", :recursive => true) }
end
def test_upload_directory_should_recursively_create_and_upload_items
prepare_directory("/path/to/local") do |d|
d.file "hello.txt", "hello world\n"
d.directory "others" do |d2|
d2.file "data.dat", "abcdefghijklmnopqrstuvwxyz"
end
d.file "zoo.doc", "going to the zoo\n"
end
expect_scp_session("-t -r /path/to/remote") do |channel|
channel.gets_ok
channel.sends_data "D0777 0 local\n"
channel.gets_ok
channel.sends_data "C0666 12 hello.txt\n"
channel.gets_ok
channel.sends_data "hello world\n"
channel.sends_ok
channel.gets_ok
channel.sends_data "D0777 0 others\n"
channel.gets_ok
channel.sends_data "C0666 26 data.dat\n"
channel.gets_ok
channel.sends_data "abcdefghijklmnopqrstuvwxyz"
channel.sends_ok
channel.gets_ok
channel.sends_data "E\n"
channel.gets_ok
channel.sends_data "C0666 17 zoo.doc\n"
channel.gets_ok
channel.sends_data "going to the zoo\n"
channel.sends_ok
channel.gets_ok
channel.sends_data "E\n"
channel.gets_ok
end
assert_scripted { scp.upload!("/path/to/local", "/path/to/remote", :recursive => true) }
end
def test_upload_directory_with_preserve_should_send_times_for_all_items
prepare_directory("/path/to/local", 0755, Time.at(17171717, 191919), Time.at(18181818, 101010)) do |d|
d.file "hello.txt", "hello world\n", 0640, Time.at(12345, 67890), Time.at(234567, 890)
d.directory "others", 0770, Time.at(112233, 4455), Time.at(22334455, 667788) do |d2|
d2.file "data.dat", "abcdefghijklmnopqrstuvwxyz", 0600, Time.at(13579135, 13131), Time.at(7654321, 654321)
end
d.file "zoo.doc", "going to the zoo\n", 0444, Time.at(12121212, 131313), Time.at(23232323, 242424)
end
expect_scp_session("-t -r -p /path/to/remote") do |channel|
channel.gets_ok
channel.sends_data "T17171717 191919 18181818 101010\n"
channel.gets_ok
channel.sends_data "D0755 0 local\n"
channel.gets_ok
channel.sends_data "T12345 67890 234567 890\n"
channel.gets_ok
channel.sends_data "C0640 12 hello.txt\n"
channel.gets_ok
channel.sends_data "hello world\n"
channel.sends_ok
channel.gets_ok
channel.sends_data "T112233 4455 22334455 667788\n"
channel.gets_ok
channel.sends_data "D0770 0 others\n"
channel.gets_ok
channel.sends_data "T13579135 13131 7654321 654321\n"
channel.gets_ok
channel.sends_data "C0600 26 data.dat\n"
channel.gets_ok
channel.sends_data "abcdefghijklmnopqrstuvwxyz"
channel.sends_ok
channel.gets_ok
channel.sends_data "E\n"
channel.gets_ok
channel.sends_data "T12121212 131313 23232323 242424\n"
channel.gets_ok
channel.sends_data "C0444 17 zoo.doc\n"
channel.gets_ok
channel.sends_data "going to the zoo\n"
channel.sends_ok
channel.gets_ok
channel.sends_data "E\n"
channel.gets_ok
end
assert_scripted { scp.upload!("/path/to/local", "/path/to/remote", :preserve => true, :recursive => true) }
end
def test_upload_should_not_block
prepare_file("/path/to/local.txt", "data")
story { |s| s.opens_channel(false) }
assert_scripted { scp.upload("/path/to/local.txt", "/path/to/remote.txt") }
end
def test_upload_should_raise_error_if_gets_not_ok
prepare_file("/path/to/local.txt", "")
expect_scp_session "-t /path/to/remote.txt" do |channel|
channel.gets_data "\1"
end
Net::SSH::Test::Extensions::IO.with_test_extension do
e = assert_raises(Net::SCP::Error) { scp.upload!("/path/to/local.txt", "/path/to/remote.txt") }
assert_equal("\1", e.message)
end
end
end