aspera-cli 4.27.0 → 4.27.1
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 +29 -0
- data/CONTRIBUTING.md +1 -7
- data/TODO.md +50 -0
- data/docs/README.md +708 -488
- data/lib/aspera/agent/base.rb +10 -2
- data/lib/aspera/agent/connect.rb +7 -7
- data/lib/aspera/agent/desktop.rb +3 -3
- data/lib/aspera/agent/direct.rb +48 -19
- data/lib/aspera/agent/factory.rb +3 -3
- data/lib/aspera/agent/httpgw.rb +55 -14
- data/lib/aspera/agent/node.rb +2 -2
- data/lib/aspera/agent/transferd.rb +12 -12
- data/lib/aspera/api/aoc.rb +28 -28
- data/lib/aspera/api/cos_node.rb +10 -9
- data/lib/aspera/api/faspex.rb +2 -2
- data/lib/aspera/api/httpgw.rb +20 -20
- data/lib/aspera/api/node.rb +31 -53
- data/lib/aspera/ascmd.rb +13 -13
- data/lib/aspera/ascp/installation.rb +43 -36
- data/lib/aspera/ascp/management.rb +6 -6
- data/lib/aspera/assert.rb +12 -13
- data/lib/aspera/cli/ascp_actions.rb +7 -7
- data/lib/aspera/cli/async_transfer_store.rb +49 -5
- data/lib/aspera/cli/bootstrapper.rb +9 -9
- data/lib/aspera/cli/command_registry.rb +1 -1
- data/lib/aspera/cli/command_spec.rb +6 -3
- data/lib/aspera/cli/context.rb +3 -3
- data/lib/aspera/cli/extended_value.rb +26 -26
- data/lib/aspera/cli/formatter.rb +76 -54
- data/lib/aspera/cli/gem_checker.rb +1 -1
- data/lib/aspera/cli/hints.rb +2 -2
- data/lib/aspera/cli/http.rb +22 -29
- data/lib/aspera/cli/mailer.rb +7 -7
- data/lib/aspera/cli/mcp_tool.rb +31 -4
- data/lib/aspera/cli/option_declarator.rb +77 -0
- data/lib/aspera/cli/options.schema.yaml +273 -3
- data/lib/aspera/cli/parser.rb +539 -280
- data/lib/aspera/cli/plugins/alee.rb +1 -1
- data/lib/aspera/cli/plugins/aoc.rb +297 -313
- data/lib/aspera/cli/plugins/ats.rb +25 -21
- data/lib/aspera/cli/plugins/base.rb +357 -186
- data/lib/aspera/cli/plugins/basic_auth.rb +0 -12
- data/lib/aspera/cli/plugins/config.rb +76 -73
- data/lib/aspera/cli/plugins/console.rb +8 -8
- data/lib/aspera/cli/plugins/cos.rb +4 -3
- data/lib/aspera/cli/plugins/factory.rb +2 -2
- data/lib/aspera/cli/plugins/faspex5.rb +259 -248
- data/lib/aspera/cli/plugins/faspio.rb +7 -2
- data/lib/aspera/cli/plugins/httpgw.rb +1 -1
- data/lib/aspera/cli/plugins/mcp.rb +11 -11
- data/lib/aspera/cli/plugins/node.rb +227 -283
- data/lib/aspera/cli/plugins/orchestrator.rb +23 -23
- data/lib/aspera/cli/plugins/preview.rb +61 -64
- data/lib/aspera/cli/plugins/server.rb +36 -31
- data/lib/aspera/cli/plugins/shares.rb +174 -135
- data/lib/aspera/cli/preset_actions.rb +39 -19
- data/lib/aspera/cli/preset_manager.rb +74 -32
- data/lib/aspera/cli/result.rb +18 -16
- data/lib/aspera/cli/runner.rb +75 -39
- data/lib/aspera/cli/secret_finder.rb +1 -1
- data/lib/aspera/cli/sync_actions.rb +36 -35
- data/lib/aspera/cli/terminal_formatter.rb +2 -2
- data/lib/aspera/cli/transfer_actions.rb +17 -10
- data/lib/aspera/cli/transfer_agent.rb +44 -44
- data/lib/aspera/cli/transfer_progress.rb +7 -7
- data/lib/aspera/cli/vault_manager.rb +34 -12
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/cli/wizard.rb +17 -20
- data/lib/aspera/colors.rb +3 -3
- data/lib/aspera/command_line_builder.rb +16 -16
- data/lib/aspera/command_line_converter.rb +1 -1
- data/lib/aspera/coverage.rb +1 -2
- data/lib/aspera/data_repository.rb +1 -1
- data/lib/aspera/dot_container.rb +8 -8
- data/lib/aspera/environment.rb +12 -12
- data/lib/aspera/faspex_gw.rb +4 -4
- data/lib/aspera/faspex_postproc.rb +14 -9
- data/lib/aspera/graphql.rb +4 -2
- data/lib/aspera/hash_ext.rb +2 -2
- data/lib/aspera/json_rpc/client.rb +1 -1
- data/lib/aspera/keychain/base.rb +10 -3
- data/lib/aspera/keychain/encrypted_hash.rb +92 -17
- data/lib/aspera/keychain/factory.rb +17 -5
- data/lib/aspera/keychain/hashicorp_vault.rb +1 -1
- data/lib/aspera/keychain/macos_security.rb +23 -25
- data/lib/aspera/keychain/one_password_api.rb +86 -0
- data/lib/aspera/keychain/one_password_base.rb +34 -0
- data/lib/aspera/keychain/one_password_cli.rb +98 -0
- data/lib/aspera/link_header.rb +2 -2
- data/lib/aspera/log.rb +13 -13
- data/lib/aspera/markdown.rb +5 -5
- data/lib/aspera/nagios.rb +8 -8
- data/lib/aspera/node_simulator.rb +33 -27
- data/lib/aspera/oauth/base.rb +4 -5
- data/lib/aspera/oauth/boot.rb +3 -3
- data/lib/aspera/oauth/factory.rb +5 -5
- data/lib/aspera/oauth/jwt.rb +5 -7
- data/lib/aspera/oauth/web.rb +4 -4
- data/lib/aspera/persistency_action_once.rb +4 -4
- data/lib/aspera/persistency_folder.rb +8 -8
- data/lib/aspera/preview/file_types.rb +4 -4
- data/lib/aspera/preview/generator.rb +49 -46
- data/lib/aspera/preview/options.rb +3 -2
- data/lib/aspera/preview/terminal.rb +5 -5
- data/lib/aspera/preview/utils.rb +65 -18
- data/lib/aspera/products/connect.rb +2 -2
- data/lib/aspera/products/desktop.rb +1 -1
- data/lib/aspera/products/other.rb +2 -2
- data/lib/aspera/products/transferd.rb +3 -3
- data/lib/aspera/proxy_auto_config.rb +10 -10
- data/lib/aspera/rest.rb +149 -115
- data/lib/aspera/rest_error_analyzer.rb +3 -3
- data/lib/aspera/rest_list.rb +10 -9
- data/lib/aspera/schema/IBM Aspera Node API-4.4.6.yaml +6232 -0
- data/lib/aspera/schema/IBM Aspera faspio Gateway API-1.0.0.yaml +249 -0
- data/lib/aspera/schema/IBM_Aspera_Shares.yaml +4653 -0
- data/lib/aspera/schema/documentation.rb +17 -11
- data/lib/aspera/schema/reader.rb +78 -4
- data/lib/aspera/schema/registry.rb +26 -7
- data/lib/aspera/secret_hider.rb +5 -5
- data/lib/aspera/ssh.rb +64 -31
- data/lib/aspera/ssl.rb +3 -3
- data/lib/aspera/sync/database.rb +2 -2
- data/lib/aspera/sync/operations.rb +13 -13
- data/lib/aspera/temp_file_manager.rb +24 -4
- data/lib/aspera/transfer/faux_file.rb +2 -2
- data/lib/aspera/transfer/parameters.rb +15 -15
- data/lib/aspera/transfer/resumer.rb +9 -9
- data/lib/aspera/transfer/spec.rb +1 -1
- data/lib/aspera/uri_reader.rb +52 -15
- data/lib/aspera/web_auth.rb +6 -5
- data/lib/aspera/web_server_simple.rb +9 -9
- data/lib/aspera/yaml.rb +4 -4
- data.tar.gz.sig +0 -0
- metadata +10 -4
- metadata.gz.sig +0 -0
- data/lib/aspera/cli/plugins/faspex.rb +0 -576
- data/lib/aspera/transfer/uri.rb +0 -56
data/lib/aspera/api/node.rb
CHANGED
|
@@ -43,14 +43,11 @@ module Aspera
|
|
|
43
43
|
SEPARATOR = ':'
|
|
44
44
|
NODE_PREFIX = 'node.'
|
|
45
45
|
end
|
|
46
|
-
# Accepted types in `file_matcher`
|
|
47
|
-
MATCH_TYPES = [String, Proc, Regexp, NilClass].freeze
|
|
48
46
|
# Delimiter in decoded node token
|
|
49
47
|
SIGNATURE_DELIMITER = '==SIGNATURE=='
|
|
50
48
|
# Default validity when generating a bearer token "manually"
|
|
51
49
|
BEARER_TOKEN_VALIDITY_DEFAULT = 86400
|
|
52
|
-
private_constant :
|
|
53
|
-
:SIGNATURE_DELIMITER, :BEARER_TOKEN_VALIDITY_DEFAULT
|
|
50
|
+
private_constant :SIGNATURE_DELIMITER, :BEARER_TOKEN_VALIDITY_DEFAULT
|
|
54
51
|
# Node API permissions: delete list mkdir preview read rename write
|
|
55
52
|
ACCESS_LEVELS = %w[delete list mkdir preview read rename write].freeze
|
|
56
53
|
# Special HTTP Headers
|
|
@@ -63,7 +60,7 @@ module Aspera
|
|
|
63
60
|
HEADER_X_TOTAL_COUNT = 'X-Total-Count'
|
|
64
61
|
|
|
65
62
|
# Register node special token decoder
|
|
66
|
-
OAuth::Factory.instance.register_decoder(lambda{ |token| Node.decode_bearer_token(token)})
|
|
63
|
+
OAuth::Factory.instance.register_decoder(lambda { |token| Node.decode_bearer_token(token) })
|
|
67
64
|
|
|
68
65
|
# Class instance variable, access with accessors on class
|
|
69
66
|
@api_options = {
|
|
@@ -82,8 +79,8 @@ module Aspera
|
|
|
82
79
|
def api_options=(options_hash)
|
|
83
80
|
Aspera.assert_type(options_hash, Hash)
|
|
84
81
|
options_hash.each do |key, val|
|
|
85
|
-
Aspera.assert(@api_options.key?(key.to_sym)){"unknown api option: #{key} (#{OPTIONS.join(', ')})"}
|
|
86
|
-
Aspera.assert_type(val, TrueClass, FalseClass){"api options value for #{key} should be boolean"}
|
|
82
|
+
Aspera.assert(@api_options.key?(key.to_sym)) { "unknown api option: #{key} (#{OPTIONS.join(', ')})" }
|
|
83
|
+
Aspera.assert_type(val, TrueClass, FalseClass) { "api options value for #{key} should be boolean" }
|
|
87
84
|
@api_options[key.to_sym] = val
|
|
88
85
|
end
|
|
89
86
|
end
|
|
@@ -127,25 +124,6 @@ module Aspera
|
|
|
127
124
|
return parameters
|
|
128
125
|
end
|
|
129
126
|
|
|
130
|
-
# For access keys: provide expression to match entry in folder
|
|
131
|
-
# @param match_expression [Proc, Regexp, String] one of supported types
|
|
132
|
-
# @return [Proc] lambda function
|
|
133
|
-
def file_matcher(match_expression)
|
|
134
|
-
case match_expression
|
|
135
|
-
when Proc then return match_expression
|
|
136
|
-
when Regexp then return ->(f){f['name'].match?(match_expression)}
|
|
137
|
-
when String
|
|
138
|
-
return ->(f){File.fnmatch(match_expression, f['name'], File::FNM_DOTMATCH)}
|
|
139
|
-
when NilClass then return ->(_){true}
|
|
140
|
-
else Aspera.error_unexpected_value(match_expression.class.name, type: ParameterError)
|
|
141
|
-
end
|
|
142
|
-
end
|
|
143
|
-
|
|
144
|
-
# @return [Proc] lambda from provided CLI options
|
|
145
|
-
def file_matcher_from_argument(options)
|
|
146
|
-
return file_matcher(options.get_next_argument('filter', validation: MATCH_TYPES, mandatory: false))
|
|
147
|
-
end
|
|
148
|
-
|
|
149
127
|
# Split path into folder + filename
|
|
150
128
|
# @param path [String] file path to split
|
|
151
129
|
# @return [Array(String, String)] containing folder + inside folder/file
|
|
@@ -166,8 +144,8 @@ module Aspera
|
|
|
166
144
|
# @return [Hash] decoded scope with access key and scope parts
|
|
167
145
|
def decode_scope(scope)
|
|
168
146
|
items = scope.split(Scope::SEPARATOR, 2)
|
|
169
|
-
Aspera.assert(items.length.eql?(2)){"invalid scope: #{scope}"}
|
|
170
|
-
Aspera.assert(items[0].start_with?(Scope::NODE_PREFIX)){"invalid scope: #{scope}"}
|
|
147
|
+
Aspera.assert(items.length.eql?(2)) { "invalid scope: #{scope}" }
|
|
148
|
+
Aspera.assert(items[0].start_with?(Scope::NODE_PREFIX)) { "invalid scope: #{scope}" }
|
|
171
149
|
return {access_key: items[0].delete_prefix(Scope::NODE_PREFIX), scope: items[1]}
|
|
172
150
|
end
|
|
173
151
|
|
|
@@ -253,10 +231,10 @@ module Aspera
|
|
|
253
231
|
workspace_name: @app_info.workspace_name
|
|
254
232
|
)
|
|
255
233
|
end
|
|
256
|
-
Log.log.warn{"Cannot resolve link with node id #{node_id}, no resolver"}
|
|
234
|
+
Log.log.warn { "Cannot resolve link with node id #{node_id}, no resolver" }
|
|
257
235
|
return
|
|
258
236
|
rescue RestCallError => e
|
|
259
|
-
Log.log.warn{"Cannot resolve link with node id #{node_id}: #{e.message}"}
|
|
237
|
+
Log.log.warn { "Cannot resolve link with node id #{node_id}: #{e.message}" }
|
|
260
238
|
return
|
|
261
239
|
end
|
|
262
240
|
|
|
@@ -271,7 +249,7 @@ module Aspera
|
|
|
271
249
|
entry['target_id'] = link_entry['target_id']
|
|
272
250
|
end
|
|
273
251
|
return true unless entry['target_node_id'].nil? || entry['target_id'].nil?
|
|
274
|
-
Log.log.warn{"Missing target information for link: #{entry['name']}"}
|
|
252
|
+
Log.log.warn { "Missing target information for link: #{entry['name']}" }
|
|
275
253
|
return false
|
|
276
254
|
end
|
|
277
255
|
|
|
@@ -323,8 +301,8 @@ module Aspera
|
|
|
323
301
|
end
|
|
324
302
|
rescue StandardError => e
|
|
325
303
|
raise e if exception
|
|
326
|
-
Log.log.warn{"#{path || file_id}: #{e.class} #{e.message}"}
|
|
327
|
-
Log.log.debug{(['Backtrace:'] + e.backtrace).join("\n")}
|
|
304
|
+
Log.log.warn { "#{path || file_id}: #{e.class} #{e.message}" }
|
|
305
|
+
Log.log.debug { (['Backtrace:'] + e.backtrace).join("\n") }
|
|
328
306
|
ensure
|
|
329
307
|
RestParameters.instance.spinner_cb.call(folder_items.count, action: :success)
|
|
330
308
|
end
|
|
@@ -341,14 +319,14 @@ module Aspera
|
|
|
341
319
|
# @para query [Hash, nil] optional query for `read`
|
|
342
320
|
def process_folder_tree(method_sym:, state:, top_file_id:, top_file_path: '/', query: nil)
|
|
343
321
|
Aspera.assert(!top_file_path.nil?, 'top_file_path not set')
|
|
344
|
-
Log.log.debug{"process_folder_tree: node=#{@app_info ? @app_info.node_info['id'] : 'nil'}, file id=#{top_file_id}, path=#{top_file_path}"}
|
|
322
|
+
Log.log.debug { "process_folder_tree: node=#{@app_info ? @app_info.node_info['id'] : 'nil'}, file id=#{top_file_id}, path=#{top_file_path}" }
|
|
345
323
|
# Start at top folder
|
|
346
324
|
folders_to_explore = [{id: top_file_id, path: top_file_path}]
|
|
347
325
|
Log.dump(:folders_to_explore, folders_to_explore)
|
|
348
326
|
until folders_to_explore.empty?
|
|
349
327
|
# Consume first in job list
|
|
350
328
|
current_item = folders_to_explore.shift
|
|
351
|
-
Log.log.debug{"Exploring #{current_item[:path]}".bg_green}
|
|
329
|
+
Log.log.debug { "Exploring #{current_item[:path]}".bg_green }
|
|
352
330
|
# Get folder content
|
|
353
331
|
folder_contents = read_folder_content(current_item[:id], query, exception: false, path: current_item[:path])
|
|
354
332
|
Log.dump(:folder_contents, folder_contents)
|
|
@@ -358,7 +336,7 @@ module Aspera
|
|
|
358
336
|
next
|
|
359
337
|
end
|
|
360
338
|
current_path = File.join(current_item[:path], entry['name'])
|
|
361
|
-
Log.log.debug{"process_folder_tree: checking #{current_path}"}
|
|
339
|
+
Log.log.debug { "process_folder_tree: checking #{current_path}" }
|
|
362
340
|
# Call block, continue only if method returns true
|
|
363
341
|
next unless send(method_sym, entry, current_path, state)
|
|
364
342
|
# Entry type is file, folder or link
|
|
@@ -393,8 +371,8 @@ module Aspera
|
|
|
393
371
|
return NodeFileId.new(self, top_file_id) if path_elements.empty?
|
|
394
372
|
resolve_state = {path: path_elements, consumed: [], result: nil, process_last_link: process_last_link}
|
|
395
373
|
process_folder_tree(method_sym: :process_api_fid, state: resolve_state, top_file_id: top_file_id)
|
|
396
|
-
Aspera.assert(!resolve_state[:result].nil?, type: ParameterError){"Entry not found: #{resolve_state[:path].first} in /#{resolve_state[:consumed].join(PATH_SEPARATOR)}"}
|
|
397
|
-
Log.log.debug{"resolve_api_fid: #{path} -> #{resolve_state[:result].node_api.base_url} #{resolve_state[:result].file_id}"}
|
|
374
|
+
Aspera.assert(!resolve_state[:result].nil?, type: ParameterError) { "Entry not found: #{resolve_state[:path].first} in /#{resolve_state[:consumed].join(PATH_SEPARATOR)}" }
|
|
375
|
+
Log.log.debug { "resolve_api_fid: #{path} -> #{resolve_state[:result].node_api.base_url} #{resolve_state[:result].file_id}" }
|
|
398
376
|
return resolve_state[:result]
|
|
399
377
|
end
|
|
400
378
|
|
|
@@ -409,10 +387,10 @@ module Aspera
|
|
|
409
387
|
def resolve_api_fid_paths(top_file_id, paths)
|
|
410
388
|
Aspera.assert_type(paths, Array)
|
|
411
389
|
Aspera.assert(paths.size.positive?, 'paths must not be empty')
|
|
412
|
-
split_sources = paths.map{ |p| Pathname(p['source']).each_filename.to_a}
|
|
390
|
+
split_sources = paths.map { |p| Pathname(p['source']).each_filename.to_a }
|
|
413
391
|
root = []
|
|
414
392
|
split_sources.map(&:size).min.times do |i|
|
|
415
|
-
parts = split_sources.map{ |s| s[i]}
|
|
393
|
+
parts = split_sources.map { |s| s[i] }
|
|
416
394
|
break unless parts.uniq.size == 1
|
|
417
395
|
root << parts.first
|
|
418
396
|
end
|
|
@@ -440,7 +418,7 @@ module Aspera
|
|
|
440
418
|
# Single source is 'folder' or 'link'
|
|
441
419
|
# TODO: add this ? , 'destination'=>file_info['name']
|
|
442
420
|
[{'source' => '.'}]
|
|
443
|
-
else Aspera.error_unexpected_value(file_info['type']){'source type'}
|
|
421
|
+
else Aspera.error_unexpected_value(file_info['type']) { 'source type' }
|
|
444
422
|
end
|
|
445
423
|
end
|
|
446
424
|
[apifid, source_paths]
|
|
@@ -450,7 +428,7 @@ module Aspera
|
|
|
450
428
|
# @param top_file_id [String] Search root
|
|
451
429
|
# @param test_lambda [Proc] Test function
|
|
452
430
|
def find_files(top_file_id, test_lambda)
|
|
453
|
-
Log.log.debug{"find_files: file id=#{top_file_id}"}
|
|
431
|
+
Log.log.debug { "find_files: file id=#{top_file_id}" }
|
|
454
432
|
find_state = {found: [], test_lambda: test_lambda}
|
|
455
433
|
process_folder_tree(method_sym: :process_find_files, state: find_state, top_file_id: top_file_id)
|
|
456
434
|
return find_state[:found]
|
|
@@ -535,7 +513,7 @@ module Aspera
|
|
|
535
513
|
# Get the transfer user from info on access key
|
|
536
514
|
transfer_spec['remote_user'] = info['transfer_user'] if info['transfer_user']
|
|
537
515
|
# Get settings from name.value array to hash key.value
|
|
538
|
-
settings = info['settings']&.to_h{ |i| [i['name'], i['value']]}
|
|
516
|
+
settings = info['settings']&.to_h { |i| [i['name'], i['value']] }
|
|
539
517
|
# Check WSS ports
|
|
540
518
|
Transfer::Spec::WSS_FIELDS.each do |i|
|
|
541
519
|
transfer_spec[i] = settings[i] if settings.key?(i)
|
|
@@ -543,7 +521,7 @@ module Aspera
|
|
|
543
521
|
else
|
|
544
522
|
transfer_spec.merge!(transport_params)
|
|
545
523
|
end
|
|
546
|
-
Aspera.assert_values(transfer_spec['remote_user'], [Transfer::Spec::ACCESS_KEY_TRANSFER_USER], type: :warn){'transfer user'}
|
|
524
|
+
Aspera.assert_values(transfer_spec['remote_user'], [Transfer::Spec::ACCESS_KEY_TRANSFER_USER], type: :warn) { 'transfer user' }
|
|
547
525
|
return transfer_spec
|
|
548
526
|
end
|
|
549
527
|
|
|
@@ -552,8 +530,8 @@ module Aspera
|
|
|
552
530
|
# @param call_args [Hash] additional arguments to pass to `Rest.call`
|
|
553
531
|
# @return [Array] list of items returned by the API call
|
|
554
532
|
def read_with_paging(subpath, query = nil, iteration: nil, **call_args)
|
|
555
|
-
Aspera.assert_type(iteration, Array, NilClass){'iteration'}
|
|
556
|
-
Aspera.assert_type(query, Hash, NilClass){'query'}
|
|
533
|
+
Aspera.assert_type(iteration, Array, NilClass) { 'iteration' }
|
|
534
|
+
Aspera.assert_type(query, Hash, NilClass) { 'query' }
|
|
557
535
|
Aspera.assert(!call_args.key?(:query), ':query must not be in call_args (use query parameter)')
|
|
558
536
|
query = {} if query.nil?
|
|
559
537
|
query[:iteration_token] = iteration[0] unless iteration.nil? || iteration[0].nil?
|
|
@@ -563,7 +541,7 @@ module Aspera
|
|
|
563
541
|
item_list = []
|
|
564
542
|
loop do
|
|
565
543
|
data, http = read(subpath, query, **call_args, ret: :both)
|
|
566
|
-
Aspera.assert_type(data, Array){"Expected data to be an Array, got: #{data.class}"}
|
|
544
|
+
Aspera.assert_type(data, Array) { "Expected data to be an Array, got: #{data.class}" }
|
|
567
545
|
# no data
|
|
568
546
|
break if data.empty?
|
|
569
547
|
item_list.concat(data)
|
|
@@ -583,7 +561,7 @@ module Aspera
|
|
|
583
561
|
query_params = Rest.query_to_h(parsed_uri.query) if parsed_uri.query
|
|
584
562
|
next_iteration_token = query_params['iteration_token'] if query_params
|
|
585
563
|
rescue URI::InvalidURIError => e
|
|
586
|
-
Log.log.warn{"Invalid URI in Link header: #{next_url} - #{e.message}"}
|
|
564
|
+
Log.log.warn { "Invalid URI in Link header: #{next_url} - #{e.message}" }
|
|
587
565
|
end
|
|
588
566
|
end
|
|
589
567
|
# Stop if no next token
|
|
@@ -622,8 +600,8 @@ module Aspera
|
|
|
622
600
|
query['page'] += 1
|
|
623
601
|
end
|
|
624
602
|
rescue StandardError => e
|
|
625
|
-
Log.log.warn{"#{e.class} #{e.message}"}
|
|
626
|
-
Log.log.debug{(['Backtrace:'] + e.backtrace).join("\n")}
|
|
603
|
+
Log.log.warn { "#{e.class} #{e.message}" }
|
|
604
|
+
Log.log.debug { (['Backtrace:'] + e.backtrace).join("\n") }
|
|
627
605
|
ensure
|
|
628
606
|
RestParameters.instance.spinner_cb.call(items.count, action: :success)
|
|
629
607
|
return items # rubocop:disable Lint/EnsureReturn
|
|
@@ -644,7 +622,7 @@ module Aspera
|
|
|
644
622
|
# File must be terminal
|
|
645
623
|
raise "#{entry['name']} is a file, expecting folder to find: #{state[:path]}" unless path_fully_consumed
|
|
646
624
|
# It's terminal, we found it
|
|
647
|
-
Log.log.debug{"process_api_fid: found #{path} -> #{entry['id']}"}
|
|
625
|
+
Log.log.debug { "process_api_fid: found #{path} -> #{entry['id']}" }
|
|
648
626
|
state[:result] = NodeFileId.new(self, entry['id'])
|
|
649
627
|
return false
|
|
650
628
|
when 'folder'
|
|
@@ -659,7 +637,7 @@ module Aspera
|
|
|
659
637
|
# We found it
|
|
660
638
|
other_node = nil
|
|
661
639
|
other_node = node_id_to_node(entry['target_node_id']) if entry_has_link_information(entry)
|
|
662
|
-
Aspera.assert(!other_node.nil?, type: Error){'Cannot resolve link'}
|
|
640
|
+
Aspera.assert(!other_node.nil?, type: Error) { 'Cannot resolve link' }
|
|
663
641
|
state[:result] = NodeFileId.new(other_node, entry['target_id'])
|
|
664
642
|
else
|
|
665
643
|
# We found it but we do not process the link
|
|
@@ -667,7 +645,7 @@ module Aspera
|
|
|
667
645
|
end
|
|
668
646
|
return false
|
|
669
647
|
end
|
|
670
|
-
else Aspera.error_unexpected_value(entry['type'], type: :warn){'folder entry type'}
|
|
648
|
+
else Aspera.error_unexpected_value(entry['type'], type: :warn) { 'folder entry type' }
|
|
671
649
|
end
|
|
672
650
|
# Continue to dig folder
|
|
673
651
|
return true
|
data/lib/aspera/ascmd.rb
CHANGED
|
@@ -89,11 +89,11 @@ module Aspera
|
|
|
89
89
|
# @param action_sym [Symbol] one of OPERATIONS
|
|
90
90
|
# @param arguments [Array] parameters for "as" command
|
|
91
91
|
# @return [Boolean,Array,Hash] result of command, type depends on command
|
|
92
|
-
def execute_single(action_sym, arguments, version:
|
|
92
|
+
def execute_single(action_sym, arguments, version: 2, host: nil)
|
|
93
93
|
arguments = [] if arguments.nil?
|
|
94
|
-
Log.log.debug{"execute_single:#{action_sym}:#{arguments}"}
|
|
94
|
+
Log.log.debug { "execute_single:#{action_sym}:#{arguments}" }
|
|
95
95
|
Aspera.assert_type(action_sym, Symbol)
|
|
96
|
-
Aspera.assert_array_all(arguments, String){'arguments'}
|
|
96
|
+
Aspera.assert_array_all(arguments, String) { 'arguments' }
|
|
97
97
|
remote_cmd = 'ascmd'
|
|
98
98
|
# lines of commands (String's)
|
|
99
99
|
command_lines = []
|
|
@@ -117,7 +117,7 @@ module Aspera
|
|
|
117
117
|
# enclose arguments in double quotes, protect backslash and double quotes
|
|
118
118
|
# ascmd uses space as token separator, and optional quotes ('") or \ to escape
|
|
119
119
|
args.each do |v|
|
|
120
|
-
command.push(%Q{"#{v.gsub(/["\\]/){ |s| "\\#{s}"}}"})
|
|
120
|
+
command.push(%Q{"#{v.gsub(/["\\]/) { |s| "\\#{s}" }}"})
|
|
121
121
|
end
|
|
122
122
|
command_lines.push(command.join(' '))
|
|
123
123
|
end
|
|
@@ -125,7 +125,7 @@ module Aspera
|
|
|
125
125
|
command_lines.push('')
|
|
126
126
|
# execute the main command and then exit
|
|
127
127
|
stdin_input = command_lines.join("\n")
|
|
128
|
-
Log.log.trace1{"execute_single:#{stdin_input}"}
|
|
128
|
+
Log.log.trace1 { "execute_single:#{stdin_input}" }
|
|
129
129
|
# execute, get binary output
|
|
130
130
|
byte_buffer = @command_executor.execute(remote_cmd, input: stdin_input).unpack('C*')
|
|
131
131
|
Aspera.assert(!byte_buffer.empty?, 'empty answer from server')
|
|
@@ -154,7 +154,7 @@ module Aspera
|
|
|
154
154
|
end
|
|
155
155
|
# raise error as exception
|
|
156
156
|
raise Error.new(result[:errno], result[:errstr], action_sym, arguments) if
|
|
157
|
-
result.is_a?(Hash) && (result.keys.sort == TYPES_DESCR[:error][:fields].map{ |i| i[:name]}.sort)
|
|
157
|
+
result.is_a?(Hash) && (result.keys.sort == TYPES_DESCR[:error][:fields].map { |i| i[:name] }.sort)
|
|
158
158
|
return result
|
|
159
159
|
end
|
|
160
160
|
|
|
@@ -174,7 +174,7 @@ module Aspera
|
|
|
174
174
|
# @return [Hash] field description
|
|
175
175
|
def field_description(struct_name, typed_buffer)
|
|
176
176
|
result = TYPES_DESCR[struct_name][:fields][typed_buffer[:btype] - ENUM_START]
|
|
177
|
-
Aspera.assert(!result.nil?){"Unrecognized field for #{struct_name}: #{typed_buffer[:btype]}\n#{typed_buffer[:buffer]}"}
|
|
177
|
+
Aspera.assert(!result.nil?) { "Unrecognized field for #{struct_name}: #{typed_buffer[:btype]}\n#{typed_buffer[:buffer]}" }
|
|
178
178
|
return result
|
|
179
179
|
end
|
|
180
180
|
|
|
@@ -183,8 +183,8 @@ module Aspera
|
|
|
183
183
|
def parse(buffer, type_name, indent_level = nil)
|
|
184
184
|
indent_level = (indent_level || -1) + 1
|
|
185
185
|
type_descr = TYPES_DESCR[type_name]
|
|
186
|
-
Aspera.assert(!type_descr.nil?){"Unexpected type #{type_name}"}
|
|
187
|
-
Log.log.trace1{"#{' .' * indent_level}parse:#{type_name}:#{type_descr[:decode]}:#{buffer[0, 16]}...".red}
|
|
186
|
+
Aspera.assert(!type_descr.nil?) { "Unexpected type #{type_name}" }
|
|
187
|
+
Log.log.trace1 { "#{' .' * indent_level}parse:#{type_name}:#{type_descr[:decode]}:#{buffer[0, 16]}...".red }
|
|
188
188
|
result = nil
|
|
189
189
|
case type_descr[:decode]
|
|
190
190
|
when :base
|
|
@@ -194,7 +194,7 @@ module Aspera
|
|
|
194
194
|
byte_array = [byte_array] unless byte_array.is_a?(Array)
|
|
195
195
|
result = byte_array.pack('C*').unpack1(type_descr[:unpack])
|
|
196
196
|
result.force_encoding('UTF-8') if type_name.eql?(:zstr)
|
|
197
|
-
Log.log.trace1{"#{' .' * indent_level}-> base:#{byte_array} -> #{result}"}
|
|
197
|
+
Log.log.trace1 { "#{' .' * indent_level}-> base:#{byte_array} -> #{result}" }
|
|
198
198
|
result = Time.at(result) if type_name.eql?(:epoch)
|
|
199
199
|
when :buffer_list
|
|
200
200
|
# return a list of type_buffer
|
|
@@ -205,7 +205,7 @@ module Aspera
|
|
|
205
205
|
Aspera.assert(buffer.length >= length, 'not enough bytes')
|
|
206
206
|
value = buffer.shift(length)
|
|
207
207
|
result.push({btype: btype, buffer: value})
|
|
208
|
-
Log.log.trace1{"#{' .' * indent_level}:buffer_list[#{result.length - 1}] #{result.last}"}
|
|
208
|
+
Log.log.trace1 { "#{' .' * indent_level}:buffer_list[#{result.length - 1}] #{result.last}" }
|
|
209
209
|
end
|
|
210
210
|
when :field_list
|
|
211
211
|
# by default the result is one struct
|
|
@@ -214,7 +214,7 @@ module Aspera
|
|
|
214
214
|
parse(buffer, :blist, indent_level).each do |typed_buffer|
|
|
215
215
|
# what type of field is it ?
|
|
216
216
|
field_info = field_description(type_name, typed_buffer)
|
|
217
|
-
Log.log.trace1{"#{' .' * indent_level}+ field(special=#{field_info[:special]})=#{field_info[:name]}".green}
|
|
217
|
+
Log.log.trace1 { "#{' .' * indent_level}+ field(special=#{field_info[:special]})=#{field_info[:name]}".green }
|
|
218
218
|
case field_info[:special]
|
|
219
219
|
when nil # normal case
|
|
220
220
|
result[field_info[:name]] = parse(typed_buffer[:buffer], field_info[:is_a], indent_level)
|
|
@@ -224,7 +224,7 @@ module Aspera
|
|
|
224
224
|
result[field_info[:name]] ||= []
|
|
225
225
|
result[field_info[:name]].push(parse(typed_buffer[:buffer], field_info[:is_a], indent_level))
|
|
226
226
|
when :list_tlv_list # field is an array of values in a list of buffers
|
|
227
|
-
result[field_info[:name]] = parse(typed_buffer[:buffer], :blist, indent_level).map{ |r| parse(r[:buffer], field_info[:is_a], indent_level)}
|
|
227
|
+
result[field_info[:name]] = parse(typed_buffer[:buffer], :blist, indent_level).map { |r| parse(r[:buffer], field_info[:is_a], indent_level) }
|
|
228
228
|
when :list_tlv_restart # field is an array of values, but a new value is started on index 1
|
|
229
229
|
fl = result[field_info[:name]] = []
|
|
230
230
|
parse(typed_buffer[:buffer], :blist, indent_level).map do |tb|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# cspell:ignore protobuf ckpt
|
|
4
|
+
require 'aspera/yaml'
|
|
4
5
|
require 'aspera/environment'
|
|
5
6
|
require 'aspera/data_repository'
|
|
6
7
|
require 'aspera/log'
|
|
@@ -46,9 +47,9 @@ module Aspera
|
|
|
46
47
|
def sdk_locations
|
|
47
48
|
location_url = @transferd_urls
|
|
48
49
|
transferd_locations = UriReader.read(location_url)
|
|
49
|
-
Log.log.debug{"Retrieving SDK locations from #{location_url}"}
|
|
50
|
+
Log.log.debug { "Retrieving SDK locations from #{location_url}" }
|
|
50
51
|
begin
|
|
51
|
-
return
|
|
52
|
+
return Yaml.safe_load(transferd_locations)
|
|
52
53
|
rescue Psych::SyntaxError
|
|
53
54
|
raise "Error when parsing yaml data from: #{location_url}"
|
|
54
55
|
end
|
|
@@ -60,23 +61,23 @@ module Aspera
|
|
|
60
61
|
# - "product:PRODUCT_NAME" to use ascp from named product
|
|
61
62
|
# - "product:FIRST" to use ascp from first found product
|
|
62
63
|
def sdk_folder=(ascp_location)
|
|
63
|
-
Aspera.assert_type(ascp_location, String){'ascp_location'}
|
|
64
|
+
Aspera.assert_type(ascp_location, String) { 'ascp_location' }
|
|
64
65
|
Aspera.assert(!ascp_location.empty?, 'ascp location cannot be empty: check your config file')
|
|
65
66
|
folder =
|
|
66
67
|
if ascp_location.start_with?(USE_PRODUCT_PREFIX)
|
|
67
68
|
product_name = ascp_location.delete_prefix(USE_PRODUCT_PREFIX)
|
|
68
69
|
if product_name.eql?(FIRST_FOUND)
|
|
69
70
|
pl = installed_products.first
|
|
70
|
-
Aspera.assert(!pl.nil?){"No Aspera transfer module or SDK found.\nRefer to the manual or install SDK with command:\nascli conf transferd install"}
|
|
71
|
+
Aspera.assert(!pl.nil?) { "No Aspera transfer module or SDK found.\nRefer to the manual or install SDK with command:\nascli conf transferd install" }
|
|
71
72
|
else
|
|
72
|
-
pl = installed_products.find{ |i| i[:name].eql?(product_name)}
|
|
73
|
-
Aspera.assert(!pl.nil?){"No such product installed: #{product_name}"}
|
|
73
|
+
pl = installed_products.find { |i| i[:name].eql?(product_name) }
|
|
74
|
+
Aspera.assert(!pl.nil?) { "No such product installed: #{product_name}" }
|
|
74
75
|
end
|
|
75
76
|
File.dirname(pl[:ascp_path])
|
|
76
77
|
else
|
|
77
78
|
ascp_location.include?('/ascp') ? File.dirname(ascp_location) : ascp_location
|
|
78
79
|
end
|
|
79
|
-
Log.log.debug{"ascp_folder=#{folder}"}
|
|
80
|
+
Log.log.debug { "ascp_folder=#{folder}" }
|
|
80
81
|
Products::Transferd.sdk_directory = folder
|
|
81
82
|
nil
|
|
82
83
|
end
|
|
@@ -118,11 +119,11 @@ module Aspera
|
|
|
118
119
|
when :ssh_private_dsa, :ssh_private_rsa
|
|
119
120
|
# assume last 3 letters are type
|
|
120
121
|
type = file_type.to_s[-3..].to_sym
|
|
121
|
-
file = check_or_create_sdk_file("aspera_bypass_#{type}.pem"){DataRepository.instance.item(type)}
|
|
122
|
+
file = check_or_create_sdk_file("aspera_bypass_#{type}.pem") { DataRepository.instance.item(type) }
|
|
122
123
|
when :aspera_license
|
|
123
|
-
file = check_or_create_sdk_file('aspera-license'){DataRepository.instance.item(:license)}
|
|
124
|
+
file = check_or_create_sdk_file('aspera-license') { DataRepository.instance.item(:license) }
|
|
124
125
|
when :aspera_conf
|
|
125
|
-
file = check_or_create_sdk_file('aspera.conf'){DEFAULT_ASPERA_CONF}
|
|
126
|
+
file = check_or_create_sdk_file('aspera.conf') { DEFAULT_ASPERA_CONF }
|
|
126
127
|
when :fallback_certificate, :fallback_private_key
|
|
127
128
|
file_key = File.join(Products::Transferd.sdk_directory, 'aspera_fallback_cert_private_key.pem')
|
|
128
129
|
file_cert = File.join(Products::Transferd.sdk_directory, 'aspera_fallback_cert.pem')
|
|
@@ -131,14 +132,14 @@ module Aspera
|
|
|
131
132
|
# create new self signed certificate for http fallback
|
|
132
133
|
private_key = OpenSSL::PKey::RSA.new(4096)
|
|
133
134
|
cert = WebServerSimple.self_signed_cert(private_key)
|
|
134
|
-
check_or_create_sdk_file('aspera_fallback_cert_private_key.pem', force: true){private_key.to_pem}
|
|
135
|
-
check_or_create_sdk_file('aspera_fallback_cert.pem', force: true){cert.to_pem}
|
|
135
|
+
check_or_create_sdk_file('aspera_fallback_cert_private_key.pem', force: true) { private_key.to_pem }
|
|
136
|
+
check_or_create_sdk_file('aspera_fallback_cert.pem', force: true) { cert.to_pem }
|
|
136
137
|
end
|
|
137
138
|
file = file_type.eql?(:fallback_certificate) ? file_cert : file_key
|
|
138
139
|
else Aspera.error_unexpected_value(file_type)
|
|
139
140
|
end
|
|
140
141
|
return unless file_is_required || File.exist?(file)
|
|
141
|
-
Aspera.assert(File.exist?(file), type: Errno::ENOENT){"#{file_type} not found (#{file})"}
|
|
142
|
+
Aspera.assert(File.exist?(file), type: Errno::ENOENT) { "#{file_type} not found (#{file})" }
|
|
142
143
|
return file
|
|
143
144
|
end
|
|
144
145
|
|
|
@@ -155,7 +156,7 @@ module Aspera
|
|
|
155
156
|
Aspera.assert_values(types, CLIENT_SSH_KEY_OPTIONS)
|
|
156
157
|
return case types
|
|
157
158
|
when :dsa_rsa, :rsa
|
|
158
|
-
types.to_s.split('_').map{ |i| Installation.instance.path("ssh_private_#{i}".to_sym)}
|
|
159
|
+
types.to_s.split('_').map { |i| Installation.instance.path("ssh_private_#{i}".to_sym) }
|
|
159
160
|
when :per_client
|
|
160
161
|
Aspera.error_not_implemented
|
|
161
162
|
end
|
|
@@ -237,26 +238,26 @@ module Aspera
|
|
|
237
238
|
def sdk_url_for_platform(platform: nil, version: nil)
|
|
238
239
|
all_locations = sdk_locations
|
|
239
240
|
platform = Environment.instance.architecture if platform.nil?
|
|
240
|
-
locations = all_locations.select{ |l| l['platform'].eql?(platform)}
|
|
241
|
-
Aspera.assert(!locations.empty?){"No SDK for platform: #{platform}, available: #{all_locations.map{ |i| i['platform']}.uniq}"}
|
|
242
|
-
version = locations.max_by{ |entry| Gem::Version.new(entry['version'])}['version'] if version.nil?
|
|
243
|
-
info = locations.select{ |entry| entry['version'].eql?(version)}
|
|
244
|
-
Aspera.assert(!info.empty?){"No such version: #{version} for #{platform}"}
|
|
241
|
+
locations = all_locations.select { |l| l['platform'].eql?(platform) }
|
|
242
|
+
Aspera.assert(!locations.empty?) { "No SDK for platform: #{platform}, available: #{all_locations.map { |i| i['platform'] }.uniq}" }
|
|
243
|
+
version = locations.max_by { |entry| Gem::Version.new(entry['version']) }['version'] if version.nil?
|
|
244
|
+
info = locations.select { |entry| entry['version'].eql?(version) }
|
|
245
|
+
Aspera.assert(!info.empty?) { "No such version: #{version} for #{platform}" }
|
|
245
246
|
return info.first['url']
|
|
246
247
|
end
|
|
247
248
|
|
|
248
|
-
# @param
|
|
249
|
+
# @param url [String] URL or filename used to detect archive format
|
|
250
|
+
# @param archive_io [StringIO] archive content as an in-memory IO stream
|
|
249
251
|
# @yieldparam entry_name [String] File path in archive
|
|
250
252
|
# @yieldparam entry_stream [IO, Gem::Package::TarReader::Entry] Data stream
|
|
251
253
|
# @yieldparam link_target [String, nil] Link target if symlink, nil otherwise
|
|
252
|
-
def extract_archive_files(
|
|
254
|
+
def extract_archive_files(url, archive_io)
|
|
253
255
|
Aspera.assert(block_given?, 'missing block')
|
|
254
|
-
case
|
|
256
|
+
case url
|
|
255
257
|
# Windows and Mac use zip
|
|
256
258
|
when /\.zip$/
|
|
257
259
|
require 'zip'
|
|
258
|
-
|
|
259
|
-
Zip::File.open(sdk_archive_path) do |zip_file|
|
|
260
|
+
Zip::File.open_buffer(archive_io) do |zip_file|
|
|
260
261
|
zip_file.each do |entry|
|
|
261
262
|
next if entry.name.end_with?('/')
|
|
262
263
|
entry.get_input_stream do |io|
|
|
@@ -268,7 +269,7 @@ module Aspera
|
|
|
268
269
|
when /\.tar\.gz/
|
|
269
270
|
require 'zlib'
|
|
270
271
|
require 'rubygems/package'
|
|
271
|
-
Zlib::GzipReader.
|
|
272
|
+
Zlib::GzipReader.wrap(archive_io) do |gzip|
|
|
272
273
|
Gem::Package::TarReader.new(gzip) do |tar|
|
|
273
274
|
tar.each do |entry|
|
|
274
275
|
next if entry.directory?
|
|
@@ -277,7 +278,7 @@ module Aspera
|
|
|
277
278
|
end
|
|
278
279
|
end
|
|
279
280
|
else
|
|
280
|
-
raise "unknown archive extension: #{
|
|
281
|
+
raise "unknown archive extension: #{url}"
|
|
281
282
|
end
|
|
282
283
|
end
|
|
283
284
|
|
|
@@ -301,13 +302,19 @@ module Aspera
|
|
|
301
302
|
extracted_files = {}
|
|
302
303
|
# Security: Get canonical path of installation directory for boundary checks
|
|
303
304
|
install_boundary = File.realpath(folder)
|
|
304
|
-
|
|
305
|
-
|
|
305
|
+
archive_io = StringIO.new
|
|
306
|
+
if UriReader.file?(url)
|
|
307
|
+
archive_io.write(File.binread(UriReader.file_path(url)))
|
|
308
|
+
else
|
|
309
|
+
Rest.new(base_url: url, redirect_max: 3).call(operation: 'GET', save_to: archive_io)
|
|
310
|
+
archive_io.rewind
|
|
311
|
+
end
|
|
312
|
+
extract_archive_files(url, archive_io) do |entry_name, entry_stream, link_target|
|
|
306
313
|
dest_folder = if block_given?
|
|
307
314
|
yield(entry_name)
|
|
308
315
|
else
|
|
309
316
|
# default files to extract directly to main folder if in selected source folders
|
|
310
|
-
Products::Transferd::RUNTIME_FOLDERS.any?{ |i| entry_name.match?(%r{^[^/]*/#{i}/})} ? '/' : nil
|
|
317
|
+
Products::Transferd::RUNTIME_FOLDERS.any? { |i| entry_name.match?(%r{^[^/]*/#{i}/}) } ? '/' : nil
|
|
311
318
|
end
|
|
312
319
|
next if dest_folder.nil?
|
|
313
320
|
dest_folder = File.join(folder, dest_folder)
|
|
@@ -320,7 +327,7 @@ module Aspera
|
|
|
320
327
|
# Security: Detect basename collisions that could overwrite symlinks
|
|
321
328
|
file_basename = File.basename(dest_file)
|
|
322
329
|
if extracted_files.key?(file_basename)
|
|
323
|
-
Log.log.warn{"Rejecting file with duplicate basename: #{entry_name} (basename: #{file_basename}, previous: #{extracted_files[file_basename]})"}
|
|
330
|
+
Log.log.warn { "Rejecting file with duplicate basename: #{entry_name} (basename: #{file_basename}, previous: #{extracted_files[file_basename]})" }
|
|
324
331
|
next
|
|
325
332
|
end
|
|
326
333
|
extracted_files[file_basename] = entry_name
|
|
@@ -328,7 +335,7 @@ module Aspera
|
|
|
328
335
|
if link_target.nil?
|
|
329
336
|
# Security: Check if destination already exists
|
|
330
337
|
if File.exist?(dest_file)
|
|
331
|
-
Log.log.warn{"Rejecting write to existing file or link: #{dest_file}"}
|
|
338
|
+
Log.log.warn { "Rejecting write to existing file or link: #{dest_file}" }
|
|
332
339
|
next
|
|
333
340
|
end
|
|
334
341
|
# Security: Verify the resolved path stays within installation boundary
|
|
@@ -338,17 +345,17 @@ module Aspera
|
|
|
338
345
|
# Check where the file would resolve to (handles existing symlinks in path)
|
|
339
346
|
resolved_dest = File.realpath(File.dirname(dest_file))
|
|
340
347
|
unless resolved_dest.start_with?(install_boundary)
|
|
341
|
-
Log.log.warn{"Rejecting file outside installation directory: #{dest_file} resolves to #{resolved_dest}"}
|
|
348
|
+
Log.log.warn { "Rejecting file outside installation directory: #{dest_file} resolves to #{resolved_dest}" }
|
|
342
349
|
next
|
|
343
350
|
end
|
|
344
351
|
rescue Errno::ENOENT
|
|
345
352
|
# Directory doesn't exist yet, verify the intended path
|
|
346
353
|
unless dest_file.start_with?(folder)
|
|
347
|
-
Log.log.warn{"Rejecting file with path outside installation directory: #{dest_file}"}
|
|
354
|
+
Log.log.warn { "Rejecting file with path outside installation directory: #{dest_file}" }
|
|
348
355
|
next
|
|
349
356
|
end
|
|
350
357
|
end
|
|
351
|
-
File.open(dest_file, 'wb'){ |output_stream| IO.copy_stream(entry_stream, output_stream)}
|
|
358
|
+
File.open(dest_file, 'wb') { |output_stream| IO.copy_stream(entry_stream, output_stream) }
|
|
352
359
|
else
|
|
353
360
|
# Security: Validate symlink target stays within installation boundary
|
|
354
361
|
# Resolve the symlink target relative to its location
|
|
@@ -362,7 +369,7 @@ module Aspera
|
|
|
362
369
|
end
|
|
363
370
|
# Check if resolved target would be outside installation directory
|
|
364
371
|
unless resolved_target.start_with?(install_boundary)
|
|
365
|
-
Log.log.warn{"Rejecting symlink pointing outside installation directory: #{entry_name} -> #{link_target} (resolves to #{resolved_target})"}
|
|
372
|
+
Log.log.warn { "Rejecting symlink pointing outside installation directory: #{entry_name} -> #{link_target} (resolves to #{resolved_target})" }
|
|
366
373
|
next
|
|
367
374
|
end
|
|
368
375
|
File.symlink(link_target, dest_file)
|
|
@@ -419,7 +426,7 @@ module Aspera
|
|
|
419
426
|
# all executable files from SDK
|
|
420
427
|
EXE_FILES = %i[ascp ascp4 async transferd].freeze
|
|
421
428
|
# IDs of files present in SDK
|
|
422
|
-
SDK_FILES = %i[ssh_private_dsa ssh_private_rsa aspera_license aspera_conf fallback_certificate fallback_private_key]
|
|
429
|
+
SDK_FILES = (EXE_FILES + %i[ssh_private_dsa ssh_private_rsa aspera_license aspera_conf fallback_certificate fallback_private_key]).freeze
|
|
423
430
|
TRANSFERD_ARCHIVE_LOCATION_URL = 'https://ibm.biz/sdk_location'
|
|
424
431
|
# filename for ascp with optional extension (Windows)
|
|
425
432
|
private_constant :DEFAULT_ASPERA_CONF, :EXE_FILES, :SDK_FILES, :TRANSFERD_ARCHIVE_LOCATION_URL
|
|
@@ -305,8 +305,8 @@ module Aspera
|
|
|
305
305
|
# @param name [String] Field name
|
|
306
306
|
def field_snake_to_native(name)
|
|
307
307
|
field = name.delete('_')
|
|
308
|
-
result = PARAMETERS.find{ |w| w.casecmp?(field)}
|
|
309
|
-
Aspera.assert(!result.nil?){"No such field: #{name}"}
|
|
308
|
+
result = PARAMETERS.find { |w| w.casecmp?(field) }
|
|
309
|
+
Aspera.assert(!result.nil?) { "No such field: #{name}" }
|
|
310
310
|
result
|
|
311
311
|
end
|
|
312
312
|
|
|
@@ -327,7 +327,7 @@ module Aspera
|
|
|
327
327
|
# @return [String] frame to send on management port
|
|
328
328
|
def command_to_stream(data)
|
|
329
329
|
data
|
|
330
|
-
.map{ |key, value| "#{field_snake_to_native(key)}: #{value}"}
|
|
330
|
+
.map { |key, value| "#{field_snake_to_native(key)}: #{value}" }
|
|
331
331
|
.unshift(MGT_HEADER)
|
|
332
332
|
.push(MGT_FRAME_SEPARATOR, '')
|
|
333
333
|
.join("\n")
|
|
@@ -352,15 +352,15 @@ module Aspera
|
|
|
352
352
|
# begin event
|
|
353
353
|
@event_build = {}
|
|
354
354
|
when /^([^:]+): (.*)$/
|
|
355
|
-
Aspera.assert_type(@event_build, Hash){'mgt port: unexpected line: data without header'}
|
|
355
|
+
Aspera.assert_type(@event_build, Hash) { 'mgt port: unexpected line: data without header' }
|
|
356
356
|
# event field
|
|
357
357
|
@event_build[Regexp.last_match(1)] = Regexp.last_match(2)
|
|
358
358
|
when MGT_FRAME_SEPARATOR
|
|
359
|
-
Aspera.assert_type(@event_build, Hash){'mgt port: unexpected line: end frame without header'}
|
|
359
|
+
Aspera.assert_type(@event_build, Hash) { 'mgt port: unexpected line: end frame without header' }
|
|
360
360
|
@last_event = @event_build
|
|
361
361
|
@event_build = nil
|
|
362
362
|
return @last_event
|
|
363
|
-
else Aspera.error_unexpected_value(line){'mgt port'}
|
|
363
|
+
else Aspera.error_unexpected_value(line) { 'mgt port' }
|
|
364
364
|
end
|
|
365
365
|
return
|
|
366
366
|
end
|