aspera-cli 4.23.0 → 4.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +32 -1
- data/CONTRIBUTING.md +86 -29
- data/README.md +1651 -856
- data/bin/ascli +2 -1
- data/bin/asession +4 -4
- data/lib/aspera/agent/base.rb +4 -0
- data/lib/aspera/agent/connect.rb +20 -18
- data/lib/aspera/agent/desktop.rb +14 -11
- data/lib/aspera/agent/direct.rb +39 -31
- data/lib/aspera/agent/httpgw.rb +2 -2
- data/lib/aspera/agent/node.rb +9 -11
- data/lib/aspera/agent/transferd.rb +18 -11
- data/lib/aspera/api/aoc.rb +44 -31
- data/lib/aspera/api/cos_node.rb +7 -5
- data/lib/aspera/api/httpgw.rb +15 -18
- data/lib/aspera/api/node.rb +104 -22
- data/lib/aspera/ascmd.rb +22 -16
- data/lib/aspera/ascp/installation.rb +37 -40
- data/lib/aspera/ascp/management.rb +5 -4
- data/lib/aspera/assert.rb +54 -23
- data/lib/aspera/cli/basic_auth_plugin.rb +8 -7
- data/lib/aspera/cli/error.rb +1 -1
- data/lib/aspera/cli/extended_value.rb +28 -29
- data/lib/aspera/cli/formatter.rb +191 -168
- data/lib/aspera/cli/hints.rb +29 -3
- data/lib/aspera/cli/main.rb +138 -107
- data/lib/aspera/cli/manager.rb +50 -30
- data/lib/aspera/cli/plugin.rb +148 -77
- data/lib/aspera/cli/plugin_factory.rb +2 -2
- data/lib/aspera/cli/plugins/aoc.rb +189 -70
- data/lib/aspera/cli/plugins/ats.rb +15 -13
- data/lib/aspera/cli/plugins/config.rb +86 -213
- data/lib/aspera/cli/plugins/console.rb +49 -18
- data/lib/aspera/cli/plugins/cos.rb +4 -4
- data/lib/aspera/cli/plugins/faspex.rb +45 -51
- data/lib/aspera/cli/plugins/faspex5.rb +162 -163
- data/lib/aspera/cli/plugins/faspio.rb +6 -5
- data/lib/aspera/cli/plugins/httpgw.rb +2 -2
- data/lib/aspera/cli/plugins/node.rb +144 -162
- data/lib/aspera/cli/plugins/orchestrator.rb +10 -14
- data/lib/aspera/cli/plugins/preview.rb +26 -29
- data/lib/aspera/cli/plugins/server.rb +28 -28
- data/lib/aspera/cli/plugins/shares.rb +40 -28
- data/lib/aspera/cli/sync_actions.rb +101 -80
- data/lib/aspera/cli/transfer_agent.rb +51 -50
- data/lib/aspera/cli/transfer_progress.rb +29 -20
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/cli/wizard.rb +160 -0
- data/lib/aspera/colors.rb +13 -8
- data/lib/aspera/command_line_builder.rb +28 -22
- data/lib/aspera/command_line_converter.rb +31 -0
- data/lib/aspera/environment.rb +144 -101
- data/lib/aspera/faspex_gw.rb +1 -1
- data/lib/aspera/faspex_postproc.rb +3 -2
- data/lib/aspera/hash_ext.rb +1 -1
- data/lib/aspera/id_generator.rb +10 -10
- data/lib/aspera/keychain/base.rb +18 -0
- data/lib/aspera/keychain/encrypted_hash.rb +6 -12
- data/lib/aspera/keychain/factory.rb +9 -3
- data/lib/aspera/keychain/hashicorp_vault.rb +9 -6
- data/lib/aspera/keychain/macos_security.rb +13 -13
- data/lib/aspera/log.rb +69 -20
- data/lib/aspera/nagios.rb +5 -6
- data/lib/aspera/node_simulator.rb +12 -7
- data/lib/aspera/oauth/base.rb +5 -3
- data/lib/aspera/oauth/factory.rb +24 -18
- data/lib/aspera/oauth/jwt.rb +13 -1
- data/lib/aspera/oauth/url_json.rb +3 -3
- data/lib/aspera/oauth/web.rb +5 -3
- data/lib/aspera/persistency_folder.rb +2 -2
- data/lib/aspera/preview/file_types.rb +4 -3
- data/lib/aspera/preview/generator.rb +25 -12
- data/lib/aspera/preview/terminal.rb +10 -7
- data/lib/aspera/preview/utils.rb +11 -9
- data/lib/aspera/products/connect.rb +1 -1
- data/lib/aspera/products/desktop.rb +1 -1
- data/lib/aspera/products/other.rb +2 -2
- data/lib/aspera/products/transferd.rb +8 -6
- data/lib/aspera/proxy_auto_config.rb +1 -1
- data/lib/aspera/rest.rb +29 -22
- data/lib/aspera/rest_call_error.rb +1 -1
- data/lib/aspera/resumer.rb +1 -1
- data/lib/aspera/secret_hider.rb +46 -40
- data/lib/aspera/ssh.rb +13 -3
- data/lib/aspera/sync/args.schema.yaml +102 -0
- data/lib/aspera/sync/conf.schema.yaml +701 -0
- data/lib/aspera/sync/database.rb +83 -0
- data/lib/aspera/{transfer/sync.rb → sync/operations.rb} +132 -65
- data/lib/aspera/temp_file_manager.rb +3 -2
- data/lib/aspera/transfer/error.rb +1 -1
- data/lib/aspera/transfer/error_info.rb +1 -2
- data/lib/aspera/transfer/faux_file.rb +11 -10
- data/lib/aspera/transfer/parameters.rb +6 -5
- data/lib/aspera/transfer/spec.rb +15 -1
- data/lib/aspera/transfer/spec.schema.yaml +316 -293
- data/lib/aspera/transfer/spec_doc.rb +34 -16
- data/lib/aspera/transfer/uri.rb +5 -5
- data/lib/aspera/uri_reader.rb +14 -10
- data/lib/aspera/web_auth.rb +2 -2
- data/lib/aspera/web_server_simple.rb +2 -2
- data.tar.gz.sig +0 -0
- metadata +15 -13
- metadata.gz.sig +2 -2
- data/lib/aspera/transfer/async_conf.schema.yaml +0 -716
- data/lib/aspera/transfer/convert.rb +0 -29
- data/lib/aspera/transfer/sync_instance.schema.yaml +0 -20
- data/lib/aspera/transfer/sync_session.schema.yaml +0 -86
data/lib/aspera/colors.rb
CHANGED
@@ -10,7 +10,7 @@ class String
|
|
10
10
|
def vt_cmd(code); "\e[#{code}m"; end
|
11
11
|
end
|
12
12
|
# see https://en.wikipedia.org/wiki/ANSI_escape_code
|
13
|
-
# symbol is the method name added to String
|
13
|
+
# symbol is the method name added to String, e.g. "hello".bold
|
14
14
|
# it adds control chars to set color (and reset at the end).
|
15
15
|
VT_STYLES = {
|
16
16
|
bold: 1,
|
@@ -20,6 +20,7 @@ class String
|
|
20
20
|
blink: 5,
|
21
21
|
reverse_color: 7,
|
22
22
|
invisible: 8,
|
23
|
+
strike: 9,
|
23
24
|
black: 30,
|
24
25
|
red: 31,
|
25
26
|
green: 32,
|
@@ -38,21 +39,25 @@ class String
|
|
38
39
|
bg_gray: 47
|
39
40
|
}.freeze
|
40
41
|
private_constant :VT_STYLES
|
41
|
-
#
|
42
|
+
# Defines methods to String, one per entry in VT_STYLES
|
42
43
|
VT_STYLES.each do |name, code|
|
43
44
|
if $stdout.tty?
|
44
45
|
begin_seq = vt_cmd(code)
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
46
|
+
end_seq = vt_cmd(
|
47
|
+
if code <= 2 then 22
|
48
|
+
elsif code <= 8 then code + 20
|
49
|
+
elsif code <= 37 then 39
|
50
|
+
elsif code <= 47 then 49
|
51
|
+
else
|
52
|
+
0 # by default reset all
|
53
|
+
end
|
54
|
+
)
|
51
55
|
define_method(name){"#{begin_seq}#{self}#{end_seq}"}
|
52
56
|
else
|
53
57
|
define_method(name){self}
|
54
58
|
end
|
55
59
|
end
|
60
|
+
# Transform capitalized to snake case
|
56
61
|
def capital_to_snake
|
57
62
|
return gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
58
63
|
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
@@ -13,19 +13,24 @@ module Aspera
|
|
13
13
|
# type [String,Array] Accepted type(s) for non-enum
|
14
14
|
# default [String] Default value if not specified
|
15
15
|
# enum [Array] Set with list of values for enum types accepted in transfer spec
|
16
|
+
# items [Array]
|
17
|
+
# properties [Array]
|
16
18
|
# x-cli-envvar [String] Name of env var
|
17
19
|
# x-cli-option [String] Command line option (starts with "-")
|
18
20
|
# x-cli-switch [Bool] true if option has no arg, else by default option has a value
|
19
21
|
# x-cli-special [Bool] true if special handling (defered)
|
20
22
|
# x-cli-convert [String,Hash] Method name for Convert object or Conversion for enum ts to arg
|
21
23
|
# x-agents [Array] Supported agents (for doc only), if not specified: all
|
22
|
-
# x-
|
24
|
+
# x-ts-name [Bool,String] (async) true if same name in transfer spec, else real name in transfer spec, else ignored
|
25
|
+
# x-ts-convert [String] (async) Method name for Convert object
|
23
26
|
# x-deprecation [String] Deprecation message for doc
|
24
|
-
|
27
|
+
PROPERTY_KEYS = %w[
|
25
28
|
description
|
26
29
|
type
|
27
30
|
default
|
28
31
|
enum
|
32
|
+
items
|
33
|
+
properties
|
29
34
|
required
|
30
35
|
$comment
|
31
36
|
x-cli-envvar
|
@@ -34,13 +39,13 @@ module Aspera
|
|
34
39
|
x-cli-special
|
35
40
|
x-cli-convert
|
36
41
|
x-agents
|
37
|
-
x-
|
42
|
+
x-ts-name
|
38
43
|
x-deprecation
|
39
44
|
].freeze
|
40
45
|
|
41
46
|
CLI_AGENT = 'direct'
|
42
47
|
|
43
|
-
private_constant :
|
48
|
+
private_constant :PROPERTY_KEYS, :CLI_AGENT
|
44
49
|
|
45
50
|
class << self
|
46
51
|
# @return true if given agent supports that field
|
@@ -48,22 +53,23 @@ module Aspera
|
|
48
53
|
!properties.key?('x-agents') || properties['x-agents'].include?(agent)
|
49
54
|
end
|
50
55
|
|
51
|
-
#
|
52
|
-
def
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
Aspera.assert_type(properties, Hash){name}
|
57
|
-
unsupported_keys = properties.keys - SCHEMA_KEYS
|
56
|
+
# fill default values
|
57
|
+
def adjust_properties_defaults(properties)
|
58
|
+
properties.each do |name, info|
|
59
|
+
Aspera.assert_type(info, Hash){"#{info.class} for #{name}"}
|
60
|
+
unsupported_keys = info.keys - PROPERTY_KEYS
|
58
61
|
Aspera.assert(unsupported_keys.empty?){"Unsupported definition keys: #{unsupported_keys}"}
|
59
62
|
# by default : string, unless it's without arg
|
60
|
-
|
63
|
+
info['type'] ||= info['x-cli-switch'] ? 'boolean' : 'string'
|
61
64
|
# add default cli option name if not present, and if supported in "direct".
|
62
|
-
|
63
|
-
|
65
|
+
info['x-cli-option'] = "--#{name.to_s.tr('_', '-')}" if !info.key?('x-cli-option') && !info['x-cli-envvar'] && (info.key?('x-cli-switch') || supported_by_agent(CLI_AGENT, info))
|
66
|
+
info.freeze
|
64
67
|
end
|
65
|
-
|
66
|
-
|
68
|
+
end
|
69
|
+
|
70
|
+
# Called by provider of definition before constructor of this class so that schema has all mandatory fields
|
71
|
+
def read_schema(source_path, name)
|
72
|
+
YAML.load_file(File.join(File.dirname(source_path), "#{name}.schema.yaml"))
|
67
73
|
end
|
68
74
|
end
|
69
75
|
|
@@ -83,7 +89,7 @@ module Aspera
|
|
83
89
|
# Change required-ness of property in schema
|
84
90
|
def required(name, required)
|
85
91
|
if required
|
86
|
-
@schema['required'].push(name) unless @schema['required']
|
92
|
+
@schema['required'].push(name) unless @schema['required']&.include?(name)
|
87
93
|
else
|
88
94
|
@schema['required'].delete(name)
|
89
95
|
end
|
@@ -100,7 +106,7 @@ module Aspera
|
|
100
106
|
# set result
|
101
107
|
env_args[:env].merge!(@result[:env])
|
102
108
|
env_args[:args].concat(@result[:args])
|
103
|
-
return
|
109
|
+
return
|
104
110
|
end
|
105
111
|
|
106
112
|
# add options directly to command line
|
@@ -132,7 +138,7 @@ module Aspera
|
|
132
138
|
return
|
133
139
|
end
|
134
140
|
# check mandatory parameter (nil is valid value), TODO: change exception ?
|
135
|
-
raise Transfer::Error, "Missing mandatory parameter: #{name}" if @schema['required']
|
141
|
+
raise Transfer::Error, "Missing mandatory parameter: #{name}" if @schema['required']&.include?(name) && !properties['x-cli-special'] && !@object.key?(name)
|
136
142
|
parameter_value = @object[name]
|
137
143
|
# no default setting
|
138
144
|
# parameter_value=properties['default'] if parameter_value.nil? and properties.has_key?('default')
|
@@ -145,17 +151,17 @@ module Aspera
|
|
145
151
|
when 'object' then [Hash]
|
146
152
|
when 'integer' then [Integer]
|
147
153
|
when 'boolean' then [TrueClass, FalseClass]
|
148
|
-
else Aspera.error_unexpected_value(properties['type'])
|
154
|
+
else Aspera.error_unexpected_value(properties['type']){"Property #{name}"}
|
149
155
|
end
|
150
156
|
end.flatten
|
151
157
|
# check that value is of expected type
|
152
|
-
raise Transfer::Error, "#{name} is
|
158
|
+
raise Transfer::Error, "#{name} is #{parameter_value.class} (#{parameter_value}), shall be #{properties['type']}, " \
|
153
159
|
unless parameter_value.nil? || expected_classes.include?(parameter_value.class)
|
154
160
|
# special processing will be requested with type get_value
|
155
161
|
@processed_parameters.push(name) if !properties['x-cli-special'] || read
|
156
162
|
|
157
163
|
# process only non-nil values
|
158
|
-
return
|
164
|
+
return if parameter_value.nil?
|
159
165
|
|
160
166
|
# check that value is of an accepted type (string, integer, boolean)
|
161
167
|
raise "Enum value #{parameter_value} is not allowed for #{name}" if properties.key?('enum') && !properties['enum'].include?(parameter_value)
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'aspera/assert'
|
4
|
+
module Aspera
|
5
|
+
# conversion class for transfer spec values to CLI values (ascp)
|
6
|
+
class CommandLineConverter
|
7
|
+
class << self
|
8
|
+
# special encoding methods used in YAML (key: convert)
|
9
|
+
def remove_hyphen(value); value.tr('-', ''); end
|
10
|
+
|
11
|
+
# special encoding methods used in YAML (key: convert)
|
12
|
+
def json64(value); Base64.strict_encode64(JSON.generate(value)); end
|
13
|
+
|
14
|
+
# special encoding methods used in YAML (key: convert)
|
15
|
+
def base64(value); Base64.strict_encode64(value); end
|
16
|
+
|
17
|
+
# transform yes/no to true/false
|
18
|
+
def yes_to_true(value)
|
19
|
+
case value
|
20
|
+
when 'yes' then return true
|
21
|
+
when 'no' then return false
|
22
|
+
else Aspera.error_unexpected_value(value){'only: yes or no: '}
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def kbps_to_bps(value)
|
27
|
+
1000 * value
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/aspera/environment.rb
CHANGED
@@ -14,13 +14,12 @@ module Aspera
|
|
14
14
|
class Environment
|
15
15
|
include Singleton
|
16
16
|
|
17
|
-
USER_INTERFACES = %i[text graphical].freeze
|
18
|
-
|
19
17
|
OS_WINDOWS = :windows
|
20
18
|
OS_MACOS = :osx
|
21
19
|
OS_LINUX = :linux
|
22
20
|
OS_AIX = :aix
|
23
21
|
OS_LIST = [OS_WINDOWS, OS_MACOS, OS_LINUX, OS_AIX].freeze
|
22
|
+
|
24
23
|
CPU_X86_64 = :x86_64
|
25
24
|
CPU_ARM64 = :arm64
|
26
25
|
CPU_PPC64 = :ppc64
|
@@ -32,60 +31,18 @@ module Aspera
|
|
32
31
|
MEBI = 1024 * 1024
|
33
32
|
BYTES_PER_MEBIBIT = MEBI / BITS_PER_BYTE
|
34
33
|
|
34
|
+
I18N_VARS = %w(LC_ALL LC_CTYPE LANG).freeze
|
35
|
+
|
36
|
+
# "/" is invalid on both Unix and Windows, other are Windows special characters
|
37
|
+
# See: https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file
|
38
|
+
WINDOWS_FILENAME_INVALID_CHARACTERS = '<>:"/\\|?*'
|
39
|
+
REPLACE_CHARACTER = '_'
|
40
|
+
|
35
41
|
class << self
|
36
42
|
def ruby_version
|
37
43
|
return RbConfig::CONFIG['RUBY_PROGRAM_VERSION']
|
38
44
|
end
|
39
45
|
|
40
|
-
def os
|
41
|
-
case RbConfig::CONFIG['host_os']
|
42
|
-
when /mswin/, /msys/, /mingw/, /cygwin/, /bccwin/, /wince/, /emc/
|
43
|
-
return OS_WINDOWS
|
44
|
-
when /darwin/, /mac os/
|
45
|
-
return OS_MACOS
|
46
|
-
when /linux/
|
47
|
-
return OS_LINUX
|
48
|
-
when /aix/
|
49
|
-
return OS_AIX
|
50
|
-
else Aspera.error_unexpected_value(RbConfig::CONFIG['host_os']){'host_os'}
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
def cpu
|
55
|
-
case RbConfig::CONFIG['host_cpu']
|
56
|
-
when /x86_64/, /x64/
|
57
|
-
return CPU_X86_64
|
58
|
-
when /powerpc/, /ppc64/
|
59
|
-
return CPU_PPC64LE if os.eql?(OS_LINUX)
|
60
|
-
return CPU_PPC64
|
61
|
-
when /s390/
|
62
|
-
return CPU_S390
|
63
|
-
when /arm/, /aarch64/
|
64
|
-
return CPU_ARM64
|
65
|
-
else Aspera.error_unexpected_value(RbConfig::CONFIG['host_cpu']){'host_cpu'}
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
# normalized architecture name
|
70
|
-
# see constants: OS_* and CPU_*
|
71
|
-
def architecture
|
72
|
-
return "#{os}-#{cpu}"
|
73
|
-
end
|
74
|
-
|
75
|
-
# executable file extension for current OS
|
76
|
-
def exe_file(name='')
|
77
|
-
return "#{name}.exe" if os.eql?(OS_WINDOWS)
|
78
|
-
return name
|
79
|
-
end
|
80
|
-
|
81
|
-
# on Windows, the env var %USERPROFILE% provides the path to user's home more reliably than %HOMEDRIVE%%HOMEPATH%
|
82
|
-
# so, tell Ruby the right way
|
83
|
-
def fix_home
|
84
|
-
return unless os.eql?(OS_WINDOWS) && ENV.key?('USERPROFILE') && Dir.exist?(ENV.fetch('USERPROFILE', nil))
|
85
|
-
ENV['HOME'] = ENV.fetch('USERPROFILE', nil)
|
86
|
-
Log.log.debug{"Windows: set HOME to USERPROFILE: #{Dir.home}"}
|
87
|
-
end
|
88
|
-
|
89
46
|
# empty variable binding for secure eval
|
90
47
|
def empty_binding
|
91
48
|
return Kernel.binding
|
@@ -120,9 +77,9 @@ module Aspera
|
|
120
77
|
# @raise [Exception] if problem
|
121
78
|
def secure_spawn(exec:, args: nil, env: nil, **options)
|
122
79
|
Aspera.assert_type(exec, String)
|
123
|
-
Aspera.assert_type(args, Array)
|
124
|
-
Aspera.assert_type(env, Hash)
|
125
|
-
Aspera.assert_type(options, Hash)
|
80
|
+
Aspera.assert_type(args, Array, NilClass)
|
81
|
+
Aspera.assert_type(env, Hash, NilClass)
|
82
|
+
Aspera.assert_type(options, Hash, NilClass)
|
126
83
|
Log.log.debug{log_spawn(exec: exec, args: args, env: env)}
|
127
84
|
# start ascp in separate process
|
128
85
|
spawn_args = []
|
@@ -143,8 +100,8 @@ module Aspera
|
|
143
100
|
# @return [String] PID of process
|
144
101
|
def secure_execute(exec:, args: nil, env: nil, **system_args)
|
145
102
|
Aspera.assert_type(exec, String)
|
146
|
-
Aspera.assert_type(args, Array)
|
147
|
-
Aspera.assert_type(env, Hash)
|
103
|
+
Aspera.assert_type(args, Array, NilClass)
|
104
|
+
Aspera.assert_type(env, Hash, NilClass)
|
148
105
|
Log.log.debug{log_spawn(exec: exec, args: args, env: env)}
|
149
106
|
# start in separate process
|
150
107
|
spawn_args = []
|
@@ -163,15 +120,17 @@ module Aspera
|
|
163
120
|
# @param args [Array] arguments to executable
|
164
121
|
# @param opts [Hash] options to capture3
|
165
122
|
# @return stdout of executable or raise exception
|
166
|
-
def secure_capture(exec:, args: [], **opts)
|
123
|
+
def secure_capture(exec:, args: [], exception: true, **opts)
|
167
124
|
Aspera.assert_type(exec, String)
|
168
125
|
Aspera.assert_type(args, Array)
|
169
126
|
Aspera.assert_type(opts, Hash)
|
170
127
|
Log.log.debug{log_spawn(exec: exec, args: args)}
|
128
|
+
Log.dump(:opts, opts, level: :trace2)
|
129
|
+
Log.dump(:ENV, ENV.to_h, level: :trace1)
|
171
130
|
stdout, stderr, status = Open3.capture3(exec, *args, **opts)
|
172
131
|
Log.log.debug{"status=#{status}, stderr=#{stderr}"}
|
173
132
|
Log.log.trace1{"stdout=#{stdout}"}
|
174
|
-
raise "process failed: #{status.exitstatus}
|
133
|
+
raise "process failed: #{status.exitstatus} (#{stderr})" if !status.success? && exception
|
175
134
|
return stdout
|
176
135
|
end
|
177
136
|
|
@@ -181,7 +140,7 @@ module Aspera
|
|
181
140
|
# @param mode [Integer] the file mode (permissions)
|
182
141
|
# @block [Proc] return the content to write to the file
|
183
142
|
def write_file_restricted(path, force: false, mode: nil)
|
184
|
-
Aspera.assert(block_given?,
|
143
|
+
Aspera.assert(block_given?, type: Aspera::InternalError)
|
185
144
|
if force || !File.exist?(path)
|
186
145
|
# Windows may give error
|
187
146
|
File.unlink(path) rescue nil
|
@@ -214,71 +173,155 @@ module Aspera
|
|
214
173
|
$stdout.tty?
|
215
174
|
end
|
216
175
|
|
217
|
-
#
|
218
|
-
def
|
219
|
-
|
220
|
-
return :graphical if [Environment::OS_WINDOWS, Environment::OS_MACOS].include?(Environment.os)
|
221
|
-
# unix family
|
222
|
-
return :graphical if ENV.key?('DISPLAY') && !ENV['DISPLAY'].empty?
|
223
|
-
return :text
|
176
|
+
# force locale to C so that unicode characters are not used
|
177
|
+
def force_terminal_c
|
178
|
+
I18N_VARS.each{ |var| ENV[var] = 'C'}
|
224
179
|
end
|
225
180
|
|
226
|
-
#
|
227
|
-
#
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
when Environment::OS_WINDOWS then return system('start', 'explorer', %Q{"#{uri}"})
|
232
|
-
when Environment::OS_LINUX then return system('xdg-open', uri.to_s)
|
233
|
-
else
|
234
|
-
raise "no graphical open method for #{Environment.os}"
|
235
|
-
end
|
181
|
+
# @return true if we can display Unicode characters
|
182
|
+
# https://www.gnu.org/software/libc/manual/html_node/Locale-Categories.html
|
183
|
+
# https://pubs.opengroup.org/onlinepubs/7908799/xbd/envvar.html
|
184
|
+
def terminal_supports_unicode?
|
185
|
+
terminal? && I18N_VARS.any?{ |var| ENV[var]&.include?('UTF-8')}
|
236
186
|
end
|
187
|
+
end
|
188
|
+
attr_accessor :url_method, :file_illegal_characters
|
189
|
+
attr_reader :os, :cpu, :executable_extension, :default_gui_mode
|
190
|
+
|
191
|
+
def initialize
|
192
|
+
initialize_fields
|
193
|
+
end
|
237
194
|
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
195
|
+
# initialize fields from environment
|
196
|
+
def initialize_fields
|
197
|
+
@os =
|
198
|
+
case RbConfig::CONFIG['host_os']
|
199
|
+
when /mswin/, /msys/, /mingw/, /cygwin/, /bccwin/, /wince/, /emc/
|
200
|
+
OS_WINDOWS
|
201
|
+
when /darwin/, /mac os/
|
202
|
+
OS_MACOS
|
203
|
+
when /linux/
|
204
|
+
OS_LINUX
|
205
|
+
when /aix/
|
206
|
+
OS_AIX
|
207
|
+
else Aspera.error_unexpected_value(RbConfig::CONFIG['host_os']){'host_os'}
|
208
|
+
end
|
209
|
+
@cpu =
|
210
|
+
case RbConfig::CONFIG['host_cpu']
|
211
|
+
when /x86_64/, /x64/
|
212
|
+
CPU_X86_64
|
213
|
+
when /powerpc/, /ppc64/
|
214
|
+
@os.eql?(OS_LINUX) ? CPU_PPC64LE : CPU_PPC64
|
215
|
+
when /s390/
|
216
|
+
CPU_S390
|
217
|
+
when /arm/, /aarch64/
|
218
|
+
CPU_ARM64
|
219
|
+
else Aspera.error_unexpected_value(RbConfig::CONFIG['host_cpu']){'host_cpu'}
|
220
|
+
end
|
221
|
+
@executable_extension = @os.eql?(OS_WINDOWS) ? 'exe' : nil
|
222
|
+
# :text or :graphical depending on the environment
|
223
|
+
@default_gui_mode =
|
224
|
+
if [Environment::OS_WINDOWS, Environment::OS_MACOS].include?(os) ||
|
225
|
+
(ENV.key?('DISPLAY') && !ENV['DISPLAY'].empty?)
|
226
|
+
# assume not remotely connected on macos and windows or unix family
|
227
|
+
:graphical
|
244
228
|
else
|
245
|
-
|
229
|
+
:text
|
246
230
|
end
|
247
|
-
|
231
|
+
@url_method = @default_gui_mode
|
232
|
+
@file_illegal_characters = REPLACE_CHARACTER + WINDOWS_FILENAME_INVALID_CHARACTERS
|
233
|
+
nil
|
248
234
|
end
|
249
|
-
attr_accessor :url_method
|
250
235
|
|
251
|
-
|
252
|
-
|
253
|
-
|
236
|
+
# Normalized architecture name
|
237
|
+
# See constants: OS_* and CPU_*
|
238
|
+
def architecture
|
239
|
+
"#{@os}-#{@cpu}"
|
254
240
|
end
|
255
241
|
|
256
|
-
#
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
@terminal_supports_unicode = self.class.terminal? && %w(LC_ALL LC_CTYPE LANG).any?{ |var| ENV[var]&.include?('UTF-8')} if @terminal_supports_unicode.nil?
|
261
|
-
return @terminal_supports_unicode
|
242
|
+
# executable file extension for current OS
|
243
|
+
def exe_file(name)
|
244
|
+
return name unless @executable_extension
|
245
|
+
return "#{name}#{@executable_extension}"
|
262
246
|
end
|
263
247
|
|
264
|
-
#
|
265
|
-
#
|
266
|
-
|
248
|
+
# on Windows, the env var %USERPROFILE% provides the path to user's home more reliably than %HOMEDRIVE%%HOMEPATH%
|
249
|
+
# so, tell Ruby the right way
|
250
|
+
def fix_home
|
251
|
+
return unless @os.eql?(OS_WINDOWS) && ENV.key?('USERPROFILE') && Dir.exist?(ENV.fetch('USERPROFILE', nil))
|
252
|
+
ENV['HOME'] = ENV.fetch('USERPROFILE', nil)
|
253
|
+
Log.log.debug{"Windows: set HOME to USERPROFILE: #{Dir.home}"}
|
254
|
+
end
|
255
|
+
|
256
|
+
def graphical?
|
257
|
+
@default_gui_mode == :graphical
|
258
|
+
end
|
259
|
+
|
260
|
+
# Open a URI in a graphical browser
|
261
|
+
# Command must be non blocking
|
262
|
+
# @param uri [String] the URI to open
|
263
|
+
def open_uri_graphical(uri)
|
264
|
+
case @os
|
265
|
+
when Environment::OS_MACOS then return self.class.secure_execute(exec: 'open', args: [uri.to_s])
|
266
|
+
when Environment::OS_WINDOWS then return self.class.secure_execute(exec: 'start', args: ['explorer', %Q{"#{uri}"}])
|
267
|
+
when Environment::OS_LINUX then return self.class.secure_execute(exec: 'xdg-open', args: [uri.to_s])
|
268
|
+
else Assert.error_unexpected_value(os){'no graphical open method'}
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
# open a file in an editor
|
273
|
+
def open_editor(file_path)
|
274
|
+
if ENV.key?('EDITOR')
|
275
|
+
self.class.secure_execute(exec: ENV['EDITOR'], args: [file_path.to_s])
|
276
|
+
elsif @os.eql?(Environment::OS_WINDOWS)
|
277
|
+
self.class.secure_execute(exec: 'notepad.exe', args: [%Q{"#{file_path}"}])
|
278
|
+
else
|
279
|
+
open_uri_graphical(file_path.to_s)
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
# Allows a user to open a URL
|
284
|
+
# if method is :text, then URL is displayed on terminal
|
285
|
+
# if method is :graphical, then the URL will be opened with the default browser.
|
267
286
|
# this is non blocking
|
268
287
|
def open_uri(the_url)
|
269
288
|
case @url_method
|
270
289
|
when :graphical
|
271
|
-
|
290
|
+
open_uri_graphical(the_url)
|
272
291
|
when :text
|
273
292
|
case the_url.to_s
|
274
293
|
when /^http/
|
275
|
-
puts "USER ACTION: please enter this
|
294
|
+
puts "USER ACTION: please enter this URL in a browser:\n#{the_url.to_s.red}\n"
|
276
295
|
else
|
277
296
|
puts "USER ACTION: open this:\n#{the_url.to_s.red}\n"
|
278
297
|
end
|
279
|
-
else
|
280
|
-
|
298
|
+
else Aspera.error_unexpected_value(@url_method){'URL open method'}
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
# Replacement character for illegal filename characters
|
303
|
+
# Can also be used as safe "join" character
|
304
|
+
def safe_filename_character
|
305
|
+
return REPLACE_CHARACTER if @file_illegal_characters.nil? || @file_illegal_characters.empty?
|
306
|
+
@file_illegal_characters[0]
|
307
|
+
end
|
308
|
+
|
309
|
+
# Sanitize a filename by replacing illegal characters
|
310
|
+
# @param filename [String] the original filename
|
311
|
+
# @return [String] A file name safe to use on file system
|
312
|
+
def sanitized_filename(filename)
|
313
|
+
safe_char = safe_filename_character
|
314
|
+
# Windows does not allow file name ending with space or dot
|
315
|
+
# nor control characters anywhere.
|
316
|
+
filename = filename
|
317
|
+
.gsub(/[\. ]+$/, safe_char)
|
318
|
+
.gsub(/[\x00-\x1F\x7F]/, safe_char)
|
319
|
+
if @file_illegal_characters&.size.to_i >= 2
|
320
|
+
# replace all illegal characters with safe_char
|
321
|
+
filename = filename.tr(@file_illegal_characters[1..-1], safe_char)
|
281
322
|
end
|
323
|
+
# ensure only one safe_char is used at a time
|
324
|
+
return filename.gsub(/#{Regexp.escape(safe_char)}+/, safe_char).chomp(safe_char)
|
282
325
|
end
|
283
326
|
end
|
284
327
|
end
|
data/lib/aspera/faspex_gw.rb
CHANGED
@@ -66,7 +66,7 @@ module Aspera
|
|
66
66
|
case request.path
|
67
67
|
when '/aspera/faspex/send'
|
68
68
|
begin
|
69
|
-
|
69
|
+
Aspera.assert(!request.body.nil?){'payload missing'}
|
70
70
|
faspex_pkg_parameters = JSON.parse(request.body)
|
71
71
|
Log.log.debug{"faspex pkg create parameters=#{faspex_pkg_parameters}"}
|
72
72
|
# compare string, as class is not yet known here
|
@@ -14,7 +14,7 @@ module Aspera
|
|
14
14
|
def initialize(server, parameters)
|
15
15
|
Aspera.assert_type(parameters, Hash)
|
16
16
|
@parameters = parameters.symbolize_keys
|
17
|
-
Log.
|
17
|
+
Log.dump(:post_proc_parameters, @parameters)
|
18
18
|
not_allowed = @parameters.keys - ALLOWED_PARAMETERS
|
19
19
|
raise "unsupported parameters: #{not_allowed.join(', ')}" unless not_allowed.empty?
|
20
20
|
@parameters[:script_folder] ||= '.'
|
@@ -24,6 +24,7 @@ module Aspera
|
|
24
24
|
Log.log.debug{'Faspex4PostProcServlet initialized'}
|
25
25
|
end
|
26
26
|
|
27
|
+
# :reek:UncommunicativeMethodName
|
27
28
|
def do_POST(request, response)
|
28
29
|
Log.log.debug{"request=#{request.path}"}
|
29
30
|
begin
|
@@ -46,7 +47,7 @@ module Aspera
|
|
46
47
|
script_path = File.join(@parameters[:script_folder], script_file)
|
47
48
|
Log.log.debug{"script=#{script_path}"}
|
48
49
|
webhook_parameters = JSON.parse(request.body)
|
49
|
-
Log.
|
50
|
+
Log.dump(:webhook_parameters, webhook_parameters)
|
50
51
|
# env expects only strings
|
51
52
|
environment = webhook_parameters.each_with_object({}){ |(k, v), h| h[k] = v.to_s}
|
52
53
|
post_proc_pid = Environment.secure_spawn(env: environment, exec: script_path)
|
data/lib/aspera/hash_ext.rb
CHANGED
@@ -9,7 +9,7 @@ class ::Hash
|
|
9
9
|
merge!(second){ |_key, v1, v2| v1.is_a?(Hash) && v2.is_a?(Hash) ? v1.deep_merge!(v2) : v2}
|
10
10
|
end
|
11
11
|
|
12
|
-
def deep_do(memory=nil, &block)
|
12
|
+
def deep_do(memory = nil, &block)
|
13
13
|
each do |key, value|
|
14
14
|
if value.is_a?(Hash)
|
15
15
|
value.deep_do(memory, &block)
|
data/lib/aspera/id_generator.rb
CHANGED
@@ -1,27 +1,27 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'aspera/assert'
|
4
|
+
require 'aspera/environment'
|
4
5
|
require 'uri'
|
5
6
|
|
6
7
|
module Aspera
|
7
8
|
class IdGenerator
|
8
|
-
ID_SEPARATOR = '_'
|
9
|
-
WINDOWS_PROTECTED_CHAR = %r{[/:"<>\\*?]}.freeze
|
10
|
-
PROTECTED_CHAR_REPLACE = '_'
|
11
|
-
private_constant :ID_SEPARATOR, :PROTECTED_CHAR_REPLACE, :WINDOWS_PROTECTED_CHAR
|
12
9
|
class << self
|
10
|
+
# Generate an ID from a list of object IDs
|
11
|
+
# The generated ID is safe as file name
|
12
|
+
# @param object_id [Array<String>, String] the object IDs
|
13
|
+
# @return [String] the generated ID
|
13
14
|
def from_list(object_id)
|
15
|
+
safe_char = Environment.instance.safe_filename_character
|
14
16
|
if object_id.is_a?(Array)
|
15
17
|
# compact: remove nils
|
16
|
-
object_id = object_id.compact.map do |i|
|
18
|
+
object_id = object_id.flatten.compact.map do |i|
|
17
19
|
i.is_a?(String) && i.start_with?('https://') ? URI.parse(i).host : i.to_s
|
18
|
-
end.join(
|
20
|
+
end.join(safe_char)
|
19
21
|
end
|
20
22
|
Aspera.assert_type(object_id, String)
|
21
|
-
|
22
|
-
|
23
|
-
.gsub('.', PROTECTED_CHAR_REPLACE) # keep dot for extension only (nicer)
|
24
|
-
.downcase
|
23
|
+
# keep dot for extension only (nicer)
|
24
|
+
return Environment.instance.sanitized_filename(object_id.gsub('.', safe_char)).downcase
|
25
25
|
end
|
26
26
|
end
|
27
27
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Aspera
|
4
|
+
module Keychain
|
5
|
+
class Base
|
6
|
+
CONTENT_KEYS = %i[label username password url description].freeze
|
7
|
+
def validate_set(options)
|
8
|
+
Aspera.assert_type(options, Hash){'options'}
|
9
|
+
unsupported = options.keys - CONTENT_KEYS
|
10
|
+
Aspera.assert(unsupported.empty?){"unsupported options: #{unsupported}, use #{CONTENT_KEYS.join(', ')}"}
|
11
|
+
options.each_pair do |k, v|
|
12
|
+
Aspera.assert_type(v, String){k.to_s}
|
13
|
+
end
|
14
|
+
Aspera.assert(options.key?(:label)){'label is required'}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|