aspera-cli 4.27.2 → 4.27.4
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 +133 -0
- data/CONTRIBUTING.md +5 -2
- data/bin/ascli +3 -1
- data/docs/README.md +1006 -766
- data/lib/aspera/agent/base.rb +7 -2
- data/lib/aspera/agent/connect.rb +6 -8
- data/lib/aspera/agent/desktop.rb +2 -6
- data/lib/aspera/agent/direct.rb +52 -22
- data/lib/aspera/agent/node.rb +9 -8
- data/lib/aspera/agent/transferd.rb +2 -2
- data/lib/aspera/api/alee.rb +1 -1
- data/lib/aspera/api/aoc.rb +14 -12
- data/lib/aspera/api/ats.rb +1 -1
- data/lib/aspera/api/cos_node.rb +2 -2
- data/lib/aspera/api/faspex.rb +11 -7
- data/lib/aspera/api/httpgw.rb +38 -35
- data/lib/aspera/api/node.rb +39 -34
- data/lib/aspera/ascmd.rb +3 -1
- data/lib/aspera/ascp/installation.rb +63 -28
- data/lib/aspera/ascp/management.rb +1 -0
- data/lib/aspera/assert.rb +4 -0
- data/lib/aspera/cli/ascp_actions.rb +20 -41
- data/lib/aspera/cli/async_transfer_store.rb +12 -11
- data/lib/aspera/cli/bootstrapper.rb +14 -16
- data/lib/aspera/cli/command_line.rb +252 -0
- data/lib/aspera/cli/command_registry.rb +215 -37
- data/lib/aspera/cli/command_spec.rb +104 -15
- data/lib/aspera/cli/completion/ascli.bash +12 -0
- data/lib/aspera/cli/completion/ascli.fish +16 -0
- data/lib/aspera/cli/completion/ascli.zsh +19 -0
- data/lib/aspera/cli/context.rb +3 -0
- data/lib/aspera/cli/deprecation.rb +37 -0
- data/lib/aspera/cli/extended_value.rb +6 -3
- data/lib/aspera/cli/formatter.rb +94 -80
- data/lib/aspera/cli/gem_checker.rb +1 -1
- data/lib/aspera/cli/hints.rb +7 -6
- data/lib/aspera/cli/http.rb +22 -34
- data/lib/aspera/cli/info.rb +3 -0
- data/lib/aspera/cli/mcp_tool.rb +47 -83
- data/lib/aspera/cli/option_declarator.rb +33 -42
- data/lib/aspera/cli/option_registry.rb +69 -0
- data/lib/aspera/cli/option_types.rb +105 -0
- data/lib/aspera/cli/option_value.rb +278 -0
- data/lib/aspera/cli/options.schema.yaml +124 -15
- data/lib/aspera/cli/parser.rb +333 -862
- data/lib/aspera/cli/plugins/alee.rb +7 -4
- data/lib/aspera/cli/plugins/aoc.rb +545 -518
- data/lib/aspera/cli/plugins/ats.rb +59 -80
- data/lib/aspera/cli/plugins/base.rb +221 -265
- data/lib/aspera/cli/plugins/basic_auth.rb +2 -10
- data/lib/aspera/cli/plugins/config.rb +263 -184
- data/lib/aspera/cli/plugins/console.rb +103 -39
- data/lib/aspera/cli/plugins/cos.rb +6 -23
- data/lib/aspera/cli/plugins/factory.rb +3 -0
- data/lib/aspera/cli/plugins/faspex5.rb +204 -182
- data/lib/aspera/cli/plugins/faspio.rb +6 -11
- data/lib/aspera/cli/plugins/httpgw.rb +8 -11
- data/lib/aspera/cli/plugins/mcp.rb +20 -55
- data/lib/aspera/cli/plugins/node.rb +300 -327
- data/lib/aspera/cli/plugins/orchestrator.rb +152 -110
- data/lib/aspera/cli/plugins/preview.rb +96 -105
- data/lib/aspera/cli/plugins/server.rb +78 -53
- data/lib/aspera/cli/plugins/shares.rb +80 -131
- data/lib/aspera/cli/preset_actions.rb +44 -27
- data/lib/aspera/cli/preset_manager.rb +44 -19
- data/lib/aspera/cli/prompt.rb +36 -0
- data/lib/aspera/cli/result.rb +42 -36
- data/lib/aspera/cli/runner.rb +32 -59
- data/lib/aspera/cli/special_values.rb +5 -0
- data/lib/aspera/cli/sync_actions.rb +51 -46
- data/lib/aspera/cli/terminal_formatter.rb +9 -3
- data/lib/aspera/cli/transfer_actions.rb +14 -9
- data/lib/aspera/cli/transfer_agent.rb +34 -38
- data/lib/aspera/cli/transfer_progress.rb +290 -55
- data/lib/aspera/cli/vault_manager.rb +0 -17
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/cli/wizard.rb +5 -3
- data/lib/aspera/coverage.rb +1 -1
- data/lib/aspera/environment.rb +35 -5
- data/lib/aspera/faspex_gw.rb +2 -1
- data/lib/aspera/faspex_postproc.rb +1 -0
- data/lib/aspera/graphql.rb +5 -5
- data/lib/aspera/json_rpc/client.rb +5 -5
- data/lib/aspera/keychain/encrypted_hash.rb +2 -2
- data/lib/aspera/keychain/factory.rb +2 -1
- data/lib/aspera/keychain/one_password_api.rb +1 -1
- data/lib/aspera/link_header.rb +2 -2
- data/lib/aspera/log.rb +47 -27
- data/lib/aspera/markdown.rb +2 -0
- data/lib/aspera/mime.rb +25 -0
- data/lib/aspera/node_emulator.rb +759 -0
- data/lib/aspera/oauth/base.rb +37 -26
- data/lib/aspera/oauth/factory.rb +7 -3
- data/lib/aspera/oauth/generic.rb +1 -1
- data/lib/aspera/oauth/json_credentials.rb +34 -0
- data/lib/aspera/oauth/jwt.rb +4 -5
- data/lib/aspera/oauth/web.rb +9 -8
- data/lib/aspera/oauth.rb +1 -0
- data/lib/aspera/persistency_folder.rb +1 -3
- data/lib/aspera/preview/file_types.rb +4 -4
- data/lib/aspera/preview/generator.rb +11 -1
- data/lib/aspera/preview/options.schema.yaml +119 -0
- data/lib/aspera/preview/terminal.rb +4 -3
- data/lib/aspera/preview/utils.rb +9 -6
- data/lib/aspera/products/connect.rb +1 -1
- data/lib/aspera/rainbow.rb +7 -0
- data/lib/aspera/rest/aspera_errors.rb +72 -0
- data/lib/aspera/rest/call_error.rb +27 -0
- data/lib/aspera/rest/client.rb +523 -0
- data/lib/aspera/rest/error_analyzer.rb +113 -0
- data/lib/aspera/rest/list.rb +149 -0
- data/lib/aspera/rest/parameters.rb +55 -0
- data/lib/aspera/rest/util.rb +176 -0
- data/lib/aspera/rest.rb +7 -621
- data/lib/aspera/schema/IBM Aspera Console-enhanced.yaml +1125 -0
- data/lib/aspera/schema/IBM Aspera Faspex API-5.0-enhanced.yaml +0 -20
- data/lib/aspera/schema/IBM Aspera Orchestrator API-v1.yaml +1784 -0
- data/lib/aspera/schema/IBM Aspera on Cloud API-0.2.6-enhanced.yaml +1230 -137
- data/lib/aspera/schema/IBM Aspera on Cloud Automation API-1.0.5-enhanced.yaml +2395 -0
- data/lib/aspera/schema/IBM_Aspera_Shares.yaml +14 -13
- data/lib/aspera/schema/documentation.rb +13 -3
- data/lib/aspera/schema/reader.rb +12 -18
- data/lib/aspera/schema/registry.rb +23 -1
- data/lib/aspera/schema/validator.rb +92 -0
- data/lib/aspera/secret_hider.rb +36 -25
- data/lib/aspera/string_ext.rb +15 -0
- data/lib/aspera/temp_file_manager.rb +6 -5
- data/lib/aspera/transfer/parameters.rb +2 -0
- data/lib/aspera/transfer/spec.rb +1 -0
- data/lib/aspera/uri_reader.rb +11 -11
- data/lib/aspera/web_auth/index.html +147 -0
- data/lib/aspera/web_auth/server.rb +81 -0
- data.tar.gz.sig +0 -0
- metadata +43 -9
- metadata.gz.sig +0 -0
- data/lib/aspera/colors.rb +0 -79
- data/lib/aspera/node_simulator.rb +0 -344
- data/lib/aspera/preview/options.rb +0 -45
- data/lib/aspera/rest_call_error.rb +0 -25
- data/lib/aspera/rest_error_analyzer.rb +0 -111
- data/lib/aspera/rest_errors_aspera.rb +0 -58
- data/lib/aspera/rest_list.rb +0 -136
- data/lib/aspera/web_auth.rb +0 -211
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'aspera/rest/parameters'
|
|
4
|
+
require 'aspera/mime'
|
|
5
|
+
require 'aspera/assert'
|
|
6
|
+
require 'aspera/log'
|
|
7
|
+
|
|
8
|
+
module Aspera
|
|
9
|
+
module Rest
|
|
10
|
+
# List and lookup methods for Rest::Client
|
|
11
|
+
# To be included in classes inheriting Rest::Client that require those methods.
|
|
12
|
+
module List
|
|
13
|
+
# `max`: special query parameter: max number of items for list command
|
|
14
|
+
MAX_ITEMS = 'max'
|
|
15
|
+
# `pmax`: special query parameter: max number of pages for list command
|
|
16
|
+
MAX_PAGES = 'pmax'
|
|
17
|
+
|
|
18
|
+
# Query entity by general search (read with parameter `q`)
|
|
19
|
+
#
|
|
20
|
+
# @param entity [String] Path of entity in API
|
|
21
|
+
# @param value [String] Value of field of searched entity
|
|
22
|
+
# @param field [String] Field of searched entity
|
|
23
|
+
# @param query [Hash] Optional additional search query parameters
|
|
24
|
+
# @return [Hash] A single entity matching the search, or an exception if not found or multiple found
|
|
25
|
+
def lookup_with_q(entity, value:, field: 'name', query: {})
|
|
26
|
+
Aspera.assert_type(query, Hash) { 'query' }
|
|
27
|
+
Aspera.assert_type(field, String) { 'field' }
|
|
28
|
+
# returns entities matching the query (it matches against several fields in case insensitive way)
|
|
29
|
+
# We don't do paging, as anyway, we look for only one match
|
|
30
|
+
matching_items = read(entity, query.merge({'q' => value}))
|
|
31
|
+
# API style: {totalcount:, ...} cspell: disable-line: TODO: is that total_count ?
|
|
32
|
+
# @type [Array<Hash{String => String}>]
|
|
33
|
+
matching_items = matching_items[entity] if matching_items.is_a?(Hash)
|
|
34
|
+
Aspera.assert_type(matching_items, Array)
|
|
35
|
+
case matching_items.length
|
|
36
|
+
when 1 then return matching_items.first
|
|
37
|
+
when 0 then raise EntityNotFound, %Q{No such #{entity}: "#{value}"}
|
|
38
|
+
else
|
|
39
|
+
# multiple case insensitive partial matches, try case insensitive full match
|
|
40
|
+
# (AoC does not allow 2 entities with same case insensitive name, except for some, e.g. packages)
|
|
41
|
+
value_matches = matching_items.select { |i| i[field].casecmp?(value) }
|
|
42
|
+
case value_matches.length
|
|
43
|
+
when 1 then return value_matches.first
|
|
44
|
+
when 0 then raise Error, "#{entity}: Multiple case insensitive partial match for: \"#{value}\" in #{matching_items.map { |i| i[field] }.join(', ')} but no case insensitive full match. Please be more specific or give exact #{field}."
|
|
45
|
+
else raise Error, "#{entity}: Multiple entities with #{field}: \"#{value}\". Please use the identifier instead."
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Get a (full or partial) list of all entities of a given type.
|
|
51
|
+
# Using query: `offset` + `limit`
|
|
52
|
+
# And response `total_count`
|
|
53
|
+
# @param entity [String,Symbol] API endpoint of entity to list
|
|
54
|
+
# @param items_key [String] Key in the result to get the list of items (Default: same as `entity`)
|
|
55
|
+
# @param query [Hash,nil] Additional query parameters
|
|
56
|
+
# @return [Array<(Array<Hash>, Integer)>] items, total_count
|
|
57
|
+
def list_entities_limit_offset_total_count(
|
|
58
|
+
entity:,
|
|
59
|
+
operation: 'GET',
|
|
60
|
+
items_key: nil,
|
|
61
|
+
query: nil
|
|
62
|
+
)
|
|
63
|
+
entity = entity.to_s if entity.is_a?(Symbol)
|
|
64
|
+
items_key = entity.split('/').last if items_key.nil?
|
|
65
|
+
query = {} if query.nil?
|
|
66
|
+
Aspera.assert_type(entity, String)
|
|
67
|
+
Aspera.assert_type(items_key, String)
|
|
68
|
+
Aspera.assert_type(query, Hash)
|
|
69
|
+
Log.log.debug { "list_entities t=#{entity} k=#{items_key}" }
|
|
70
|
+
Log.dump(:query, query)
|
|
71
|
+
result = []
|
|
72
|
+
offset = 0
|
|
73
|
+
# Special parameters are not sent to the API (the caller's query is not modified)
|
|
74
|
+
query = query.dup
|
|
75
|
+
max_items = query.delete(MAX_ITEMS)&.to_i
|
|
76
|
+
remain_pages = query.delete(MAX_PAGES)&.to_i
|
|
77
|
+
# Merge default parameters
|
|
78
|
+
query = {'limit'=> PER_PAGE_DEFAULT}.merge(query)
|
|
79
|
+
total_count = nil
|
|
80
|
+
call_args = {
|
|
81
|
+
operation: operation,
|
|
82
|
+
subpath: entity,
|
|
83
|
+
query: query,
|
|
84
|
+
headers: {'Accept' => Mime::JSON}
|
|
85
|
+
}
|
|
86
|
+
loop do
|
|
87
|
+
query['offset'] = offset
|
|
88
|
+
page_result = call(**call_args)
|
|
89
|
+
page_items = page_result[items_key]
|
|
90
|
+
Aspera.assert_type(page_items, Array)
|
|
91
|
+
result.concat(page_items)
|
|
92
|
+
# Reach the limit set by user ?
|
|
93
|
+
if !max_items.nil? && (result.length >= max_items)
|
|
94
|
+
result = result.slice(0, max_items)
|
|
95
|
+
break
|
|
96
|
+
end
|
|
97
|
+
total_count ||= page_result['total_count']
|
|
98
|
+
# An empty page ends the list, even if the total count is not reached (e.g. items deleted meanwhile)
|
|
99
|
+
break if page_items.empty?
|
|
100
|
+
# Without total count, a partial page is the last one
|
|
101
|
+
break if total_count.nil? ? page_items.length < query['limit'].to_i : result.length >= total_count
|
|
102
|
+
remain_pages -= 1 unless remain_pages.nil?
|
|
103
|
+
break if !remain_pages.nil? && remain_pages <= 0
|
|
104
|
+
offset += page_items.length
|
|
105
|
+
Parameters.instance.spinner_cb.call("#{result.length} / #{total_count || '?'}")
|
|
106
|
+
end
|
|
107
|
+
Parameters.instance.spinner_cb.call(action: :success)
|
|
108
|
+
return result, total_count
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Lookup an entity id from its name.
|
|
112
|
+
# Uses query `q` if `query` is `:default` and `field` is `name`.
|
|
113
|
+
# @param entity [String] Type of entity to lookup, by default it is the path, and it is also the field name in result
|
|
114
|
+
# @param value [String] Value to lookup
|
|
115
|
+
# @param field [String] Field to match, by default it is `'name'`
|
|
116
|
+
# @param items_key [String] Key in the result to get the list of items (override entity)
|
|
117
|
+
# @param query [Hash] Additional query parameters (Default: `:default`)
|
|
118
|
+
def lookup_entity_by_field(entity:, value:, field: 'name', items_key: nil, query: :default)
|
|
119
|
+
if query.eql?(:default)
|
|
120
|
+
Aspera.assert_values(field, ['name']) { 'Default query field' }
|
|
121
|
+
query = {'q'=> value}
|
|
122
|
+
end
|
|
123
|
+
lookup_entity_generic(entity: entity, field: field, value: value) { list_entities_limit_offset_total_count(entity: entity, items_key: items_key, query: query).first }
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Lookup entity by field and value.
|
|
127
|
+
# Extracts a single result from the list returned by the block.
|
|
128
|
+
#
|
|
129
|
+
# @param entity [String] Type of entity to lookup (path, and by default it is also the field name in result)
|
|
130
|
+
# @param value [String] Value to match against the field.
|
|
131
|
+
# @param field [String] Field to match in the hashes (defaults to 'name').
|
|
132
|
+
# @yield [] A mandatory block that returns an Array of Hashes.
|
|
133
|
+
# @return [Hash] The unique matching object.
|
|
134
|
+
# @raise [Cli::BadIdentifier] If 0 or >1 matches are found.
|
|
135
|
+
def lookup_entity_generic(entity:, value:, field: 'name')
|
|
136
|
+
Aspera.assert(block_given?, 'block required for lookup_entity_generic')
|
|
137
|
+
found = yield
|
|
138
|
+
Aspera.assert_array_all(found, Hash)
|
|
139
|
+
found = found.select { |i| i[field].eql?(value) }
|
|
140
|
+
return found.first if found.length.eql?(1)
|
|
141
|
+
raise Cli::BadIdentifier.new(entity, value, field: field, count: found.length)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
PER_PAGE_DEFAULT = 1000
|
|
145
|
+
private_constant :PER_PAGE_DEFAULT
|
|
146
|
+
module_function :lookup_entity_generic
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'singleton'
|
|
4
|
+
|
|
5
|
+
module Aspera
|
|
6
|
+
module Rest
|
|
7
|
+
# Global settings for Rest::Client
|
|
8
|
+
# For example to remove certificate verification globally:
|
|
9
|
+
# `Parameters.instance.session_cb = lambda{|http|http.verify_mode=OpenSSL::SSL::VERIFY_NONE}`
|
|
10
|
+
#
|
|
11
|
+
# @!method self.instance
|
|
12
|
+
# Returns the singleton instance of Parameters
|
|
13
|
+
# @return [Parameters] the singleton instance
|
|
14
|
+
class Parameters
|
|
15
|
+
include Singleton
|
|
16
|
+
|
|
17
|
+
# @return [String] HTTP request header: `User-Agent`
|
|
18
|
+
attr_accessor :user_agent
|
|
19
|
+
# @return [String] Suffix of file being downloaded, removed when download is complete
|
|
20
|
+
attr_accessor :download_partial_suffix
|
|
21
|
+
# @return [Boolean] Retry on any error (network or HTTP)
|
|
22
|
+
attr_accessor :retry_on_error
|
|
23
|
+
# @return [Boolean] Retry on connection timeout
|
|
24
|
+
attr_accessor :retry_on_timeout
|
|
25
|
+
# @return [Boolean] Retry on HTTP code 503 (service unavailable)
|
|
26
|
+
attr_accessor :retry_on_unavailable
|
|
27
|
+
# @return [Integer] Maximum number of retries on error (first call not included)
|
|
28
|
+
attr_accessor :retry_max
|
|
29
|
+
# @return [Integer] Seconds to wait before retry
|
|
30
|
+
attr_accessor :retry_sleep
|
|
31
|
+
# @return [Proc, nil] Called on new HTTP session, with the `Net::HTTP` as argument, e.g. to set timeouts or certificate verification
|
|
32
|
+
attr_accessor :session_cb
|
|
33
|
+
# @return [Object, nil] Progress bar, receives `event` calls during download
|
|
34
|
+
attr_accessor :progress_bar
|
|
35
|
+
# @return [Proc] Called with `(title = nil, action: :spin)` to display progress of long operations (default: no display)
|
|
36
|
+
attr_accessor :spinner_cb
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
# Set default values
|
|
41
|
+
def initialize
|
|
42
|
+
@user_agent = 'RubyAsperaRest'
|
|
43
|
+
@download_partial_suffix = '.http_partial'
|
|
44
|
+
@retry_on_error = false
|
|
45
|
+
@retry_on_timeout = true
|
|
46
|
+
@retry_on_unavailable = true
|
|
47
|
+
@retry_max = 1
|
|
48
|
+
@retry_sleep = 4
|
|
49
|
+
@session_cb = nil
|
|
50
|
+
@progress_bar = nil
|
|
51
|
+
@spinner_cb = ->(_title = nil, action: :spin) {}
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'aspera/rest/parameters'
|
|
4
|
+
require 'aspera/log'
|
|
5
|
+
require 'aspera/assert'
|
|
6
|
+
require 'net/http'
|
|
7
|
+
require 'net/https'
|
|
8
|
+
require 'uri'
|
|
9
|
+
require 'base64'
|
|
10
|
+
|
|
11
|
+
module Aspera
|
|
12
|
+
# HTTP REST client and helpers
|
|
13
|
+
module Rest
|
|
14
|
+
class << self
|
|
15
|
+
# Build a Basic authentication header value
|
|
16
|
+
# @param user [String] Username
|
|
17
|
+
# @param pass [String] Password
|
|
18
|
+
# @return [String] Basic auth token
|
|
19
|
+
def basic_authorization(user, pass) = "Basic #{Base64.strict_encode64("#{user}:#{pass}")}"
|
|
20
|
+
|
|
21
|
+
# Indicate that the given Hash query uses php style for array parameters
|
|
22
|
+
# @param query [Hash] A key can have Array value and result will use PHP format: a[]=1&a[]=2
|
|
23
|
+
# @return [Hash] The query parameters.
|
|
24
|
+
def php_style(query)
|
|
25
|
+
Aspera.assert_type(query, Hash) { 'query' }
|
|
26
|
+
query[:x_array_php_style] = true
|
|
27
|
+
query
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Build URI from URL and parameters and check it is `http` or `https`.
|
|
31
|
+
# Check if php style is specified.
|
|
32
|
+
# `nil` values in query result in key without value, e.g. `?a`, while empty string values result in `?a=`.
|
|
33
|
+
# @param url [String] The URL without query.
|
|
34
|
+
# @param query [Hash,Array,String] The query parameters.
|
|
35
|
+
# @return [URI] The built URI.
|
|
36
|
+
def build_uri(url, query)
|
|
37
|
+
uri = URI.parse(url)
|
|
38
|
+
Aspera.assert_values(uri.scheme, %w[http https]) { 'URI scheme' }
|
|
39
|
+
return uri if query.nil? || query.respond_to?(:empty?) && query.empty?
|
|
40
|
+
Log.dump(:query, query)
|
|
41
|
+
uri.query =
|
|
42
|
+
case query
|
|
43
|
+
when String
|
|
44
|
+
query
|
|
45
|
+
when Hash
|
|
46
|
+
URI.encode_www_form(h_to_query_array(query))
|
|
47
|
+
when Array
|
|
48
|
+
Aspera.assert(query.all? { |i| i.is_a?(Array) && i.length.eql?(2) }, 'Query must be array of arrays of 2 elements')
|
|
49
|
+
URI.encode_www_form(query) # remove nil values
|
|
50
|
+
else Aspera.error_unexpected_value(query.class) { 'query type' }
|
|
51
|
+
end.gsub('%5B%5D=', '[]=')
|
|
52
|
+
# [] is allowed in url parameters
|
|
53
|
+
uri
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Support array for query parameter, there is no standard.
|
|
57
|
+
# Either p=1&p=2 (default)
|
|
58
|
+
# or p[]=1&p[]=2 (if `:x_array_php_style` is set to true in query)
|
|
59
|
+
# @param query [Hash] HTTP query as hash
|
|
60
|
+
# @return [Array<Array>] Array of [key, value] pairs suitable for URI.encode_www_form
|
|
61
|
+
def h_to_query_array(query)
|
|
62
|
+
Aspera.assert_type(query, Hash)
|
|
63
|
+
suffix = query[:x_array_php_style] ? '[]' : nil
|
|
64
|
+
query.each_with_object([]) do |(k, v), query_array|
|
|
65
|
+
next if k.eql?(:x_array_php_style)
|
|
66
|
+
case v
|
|
67
|
+
when Array
|
|
68
|
+
v.each do |e|
|
|
69
|
+
query_array.push(["#{k}#{suffix}", e])
|
|
70
|
+
end
|
|
71
|
+
else
|
|
72
|
+
query_array.push([k, v])
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Decode query string as Hash
|
|
78
|
+
# if parameter is only once, then it's a scalar
|
|
79
|
+
# if a parameter is several, then it's array
|
|
80
|
+
# if parameter has [] then it's an array, and [] is removed
|
|
81
|
+
# Support arrays in query string, e.g. PHP's way is p[]=1&p[]=2
|
|
82
|
+
# @param query [String] query string as in URI.query
|
|
83
|
+
# @return [Hash] decoded query
|
|
84
|
+
def query_to_h(query)
|
|
85
|
+
URI.decode_www_form(query).each_with_object({}) do |(key, value), h|
|
|
86
|
+
if key.end_with?('[]')
|
|
87
|
+
key = key[..-3]
|
|
88
|
+
h[key] = [] unless h.key?(key)
|
|
89
|
+
end
|
|
90
|
+
if h.key?(key)
|
|
91
|
+
h[key] = [h[key]] if !h[key].is_a?(Array)
|
|
92
|
+
h[key].push(value)
|
|
93
|
+
else
|
|
94
|
+
h[key] = value
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Start a HTTP/S session, also used for web sockets
|
|
100
|
+
# @param base_url [String] Base url of HTTP/S session
|
|
101
|
+
# @return [Net::HTTP] A started HTTP session
|
|
102
|
+
def start_http_session(base_url)
|
|
103
|
+
uri = URI.parse(base_url)
|
|
104
|
+
Aspera.assert_values(uri.scheme, %w[http https]) { 'URI scheme' }
|
|
105
|
+
# This honors http_proxy env var
|
|
106
|
+
http_session = Net::HTTP.new(uri.host, uri.port)
|
|
107
|
+
http_session.use_ssl = uri.scheme.eql?('https')
|
|
108
|
+
# Set http options in callback, such as timeout and cert. verification
|
|
109
|
+
Parameters.instance.session_cb&.call(http_session)
|
|
110
|
+
# Manually start session for keep alive (if supported by server, else, session is closed every time)
|
|
111
|
+
http_session.start
|
|
112
|
+
return http_session
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# get Net::HTTP underlying socket i/o
|
|
116
|
+
# little hack, handy because HTTP debug, proxy, etc... will be available
|
|
117
|
+
# used implement web sockets after `start_http_session`
|
|
118
|
+
# @param http_session [Net::HTTP] the session object
|
|
119
|
+
# @return [Net::BufferedIO] The underlying socket i/o
|
|
120
|
+
def io_http_session(http_session)
|
|
121
|
+
Aspera.assert_type(http_session, Net::HTTP)
|
|
122
|
+
# Net::BufferedIO in net/protocol.rb
|
|
123
|
+
result = http_session.instance_variable_get(:@socket)
|
|
124
|
+
Aspera.assert(!result.nil?) { "no socket for #{http_session}" }
|
|
125
|
+
return result
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Get certificate chain of remote server
|
|
129
|
+
# @param url [String] URL of server
|
|
130
|
+
# @param as_string [Boolean] `true` to return PEM string, `false` for certificate objects
|
|
131
|
+
# @return [String, Array<OpenSSL::X509::Certificate>] Certificates of remote server
|
|
132
|
+
def remote_certificate_chain(url, as_string: true)
|
|
133
|
+
result = []
|
|
134
|
+
# initiate a session to retrieve remote certificate
|
|
135
|
+
http_session = Rest.start_http_session(url)
|
|
136
|
+
begin
|
|
137
|
+
# retrieve underlying openssl socket
|
|
138
|
+
result = Rest.io_http_session(http_session).io.peer_cert_chain
|
|
139
|
+
rescue
|
|
140
|
+
result = http_session.peer_cert
|
|
141
|
+
ensure
|
|
142
|
+
http_session.finish
|
|
143
|
+
end
|
|
144
|
+
result = result.map(&:to_pem).join("\n") if as_string
|
|
145
|
+
return result
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Parses an HTTP Content-Type header string into its media type and parameters
|
|
149
|
+
# according to RFC 9110 and RFC 6838.
|
|
150
|
+
# TODO: use gem: content_type
|
|
151
|
+
#
|
|
152
|
+
# @param header [String] The Content-Type header string, e.g., "application/json; charset=utf-8"
|
|
153
|
+
# @return [Hash] A hash with :type and :parameters keys.
|
|
154
|
+
# Example:
|
|
155
|
+
# {
|
|
156
|
+
# type: "application/json",
|
|
157
|
+
# parameters: {
|
|
158
|
+
# charset: "utf-8",
|
|
159
|
+
# version: "1.0"
|
|
160
|
+
# }
|
|
161
|
+
# }
|
|
162
|
+
def parse_header(header)
|
|
163
|
+
parts = header.split(';').map(&:strip)
|
|
164
|
+
media_type = parts.shift.downcase
|
|
165
|
+
parameters = parts.filter_map do |param|
|
|
166
|
+
key, value = param.split('=', 2)
|
|
167
|
+
next unless key && value
|
|
168
|
+
key = key.strip.downcase.to_sym
|
|
169
|
+
value = value.strip.gsub(/\A"|"\z/, '')
|
|
170
|
+
[key, value]
|
|
171
|
+
end.to_h
|
|
172
|
+
{type: media_type, parameters: parameters}
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|