apidragon 1.2.0 → 1.2.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
- data/README.md +6 -5
- data/VERSION +1 -1
- data/apidragon.gemspec +3 -4
- data/lib/apidragon/api.rb +3 -6
- data/lib/apidragon/log.rb +3 -11
- data/lib/apidragon/macro.rb +55 -66
- data/lib/apidragon/parser.rb +4 -4
- metadata +2 -3
- data/Gemfile.lock +0 -162
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 76ab796cc1ebae10fc5863169dc3c3a4f5f73f83
|
4
|
+
data.tar.gz: a2eff6d0afb2817fd23855cdcf7943a837dce1d3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b73ff42605b6ca031cd0eaa53bf6ca6ff8de6de87f09ca594c32610dcedfd5520dbfe96ab7b027e2ea67325736f66d221a1d7f318c0998576381bb2b7e9f6e5e
|
7
|
+
data.tar.gz: 481669fc952dab9f24e432f17395558ab1f58d0e6c3040f95d2e72bf9ec8adb76d6fdec4389688e17625ee610984be10e3b78411d025349d6b92c45d48bc921f
|
data/README.md
CHANGED
@@ -83,7 +83,7 @@ Further, for more specific output parsing, you can specify a qualifier variable.
|
|
83
83
|
So if the call returns a list like this: `[{username => bob, id => 1234}, {username => alice, id => 5678}]`, you can always return the `id` associated with the `username` variable defined in the `vars` section.
|
84
84
|
|
85
85
|
## logging
|
86
|
-
For each call, set `logging` to `true` to enable or `false` to disable.
|
86
|
+
For each call, set `logging` to `true` to enable or `false` to disable. (Note: must explicitly set `logging: false` if a previous call has it set to `true`. Still trying to figure out why this is)
|
87
87
|
|
88
88
|
- e.g.:
|
89
89
|
```yaml
|
@@ -106,10 +106,11 @@ Each call can refer to a plugin file. The Plugin class should follow the structu
|
|
106
106
|
end
|
107
107
|
end
|
108
108
|
```
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
109
|
+
- apidragonconf.yaml
|
110
|
+
```yaml
|
111
|
+
testcall:
|
112
|
+
plugin: example #name of file minus .rb extension
|
113
|
+
```
|
113
114
|
|
114
115
|
|
115
116
|
# Planned Features
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.2.
|
1
|
+
1.2.1
|
data/apidragon.gemspec
CHANGED
@@ -2,16 +2,16 @@
|
|
2
2
|
# DO NOT EDIT THIS FILE DIRECTLY
|
3
3
|
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
4
|
# -*- encoding: utf-8 -*-
|
5
|
-
# stub: apidragon 1.2.
|
5
|
+
# stub: apidragon 1.2.1 ruby lib
|
6
6
|
|
7
7
|
Gem::Specification.new do |s|
|
8
8
|
s.name = "apidragon"
|
9
|
-
s.version = "1.2.
|
9
|
+
s.version = "1.2.1"
|
10
10
|
|
11
11
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
12
12
|
s.require_paths = ["lib"]
|
13
13
|
s.authors = ["isaiah thiessen"]
|
14
|
-
s.date = "2015-12-
|
14
|
+
s.date = "2015-12-02"
|
15
15
|
s.description = "Ruby based CLI for accessing veracode's api"
|
16
16
|
s.email = "isaiah.thiessen@live.ca"
|
17
17
|
s.executables = ["apidragon"]
|
@@ -23,7 +23,6 @@ Gem::Specification.new do |s|
|
|
23
23
|
s.files = [
|
24
24
|
".gitignore",
|
25
25
|
"Gemfile",
|
26
|
-
"Gemfile.lock",
|
27
26
|
"LICENSE.txt",
|
28
27
|
"README.md",
|
29
28
|
"README.rdoc",
|
data/lib/apidragon/api.rb
CHANGED
@@ -15,6 +15,7 @@ class Api < ArgBucket
|
|
15
15
|
# so that they can be used as parameters for function calls.
|
16
16
|
|
17
17
|
def initialize(macro_name)
|
18
|
+
`mkdir #{PLUGINS}` unless Dir.exist? PLUGINS
|
18
19
|
@arg_bucket = {}
|
19
20
|
@config = load_config
|
20
21
|
import @config['vars']
|
@@ -27,8 +28,8 @@ class Api < ArgBucket
|
|
27
28
|
end
|
28
29
|
|
29
30
|
def go
|
30
|
-
@macro.each_pair do |
|
31
|
-
call = Call.new(value
|
31
|
+
@macro.each_pair do |_key, value|
|
32
|
+
call = Call.new(value, @arg_bucket)
|
32
33
|
@arg_bucket = call.run
|
33
34
|
if !value['record'].nil?
|
34
35
|
value['record'].each do |var|
|
@@ -38,10 +39,6 @@ class Api < ArgBucket
|
|
38
39
|
end
|
39
40
|
end
|
40
41
|
|
41
|
-
def rest_get_request(url, **params)
|
42
|
-
RestClient.get url, params: params
|
43
|
-
end
|
44
|
-
|
45
42
|
def import(args)
|
46
43
|
args.each_pair do |key, value|
|
47
44
|
set key, value
|
data/lib/apidragon/log.rb
CHANGED
@@ -1,18 +1,10 @@
|
|
1
1
|
class ResponseLogger
|
2
2
|
|
3
|
-
def object_log(
|
3
|
+
def object_log(code=nil, body)
|
4
4
|
log = File.open "#{LOG}", 'a+'
|
5
5
|
log.write "call @ #{timestamp}"
|
6
|
-
log.write "HTTP #{code}\n"
|
7
|
-
log.write body
|
8
|
-
log.write "\n"
|
9
|
-
log.close
|
10
|
-
end
|
11
|
-
|
12
|
-
def string_log(function, response)
|
13
|
-
log = File.open "#{LOG}", 'a+'
|
14
|
-
log.write "call @ #{timestamp}"
|
15
|
-
log.write response
|
6
|
+
log.write "HTTP #{code}\n" unless code==nil
|
7
|
+
log.write JSON.pretty_generate body
|
16
8
|
log.write "\n"
|
17
9
|
log.close
|
18
10
|
end
|
data/lib/apidragon/macro.rb
CHANGED
@@ -4,31 +4,28 @@ require_relative 'log'
|
|
4
4
|
|
5
5
|
class Call < ArgBucket
|
6
6
|
|
7
|
-
def initialize(
|
7
|
+
def initialize(value, args)
|
8
8
|
@arg_bucket = args
|
9
|
-
@plugin = plugin
|
9
|
+
@plugin = value['plugin']
|
10
10
|
begin
|
11
|
-
require_relative "#{PLUGINS}#{plugin}" unless plugin.nil?
|
11
|
+
require_relative "#{PLUGINS}#{@plugin}" unless @plugin.nil?
|
12
12
|
rescue LoadError
|
13
13
|
puts 'Plugin failed to load. Custom functions will not be run.'
|
14
14
|
@plugin = nil
|
15
15
|
end
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
@
|
22
|
-
|
23
|
-
@input = input
|
24
|
-
@logging = logging
|
25
|
-
set_call_args unless input.nil?
|
16
|
+
@function = value['function']
|
17
|
+
credential_sub get('username'), get('password')
|
18
|
+
@mode = value['mode']
|
19
|
+
check_constraints
|
20
|
+
@output = value['output']
|
21
|
+
@logging = value['logging']
|
22
|
+
set_call_args value['input'] unless value['input'].nil?
|
26
23
|
end
|
27
24
|
|
28
|
-
def check_constraints
|
29
|
-
if mode.nil? then fail '"mode" not set. syntax= mode: get/post/put/delete.' end
|
30
|
-
if function.nil? then fail '"function" not set. syntax= function: api-url.' end
|
31
|
-
puts "WARNING: 'function' should have basic auth. (example syntax= function: username:password@example.com/apicall)" unless function.include?('username') && function.include?('password')
|
25
|
+
def check_constraints
|
26
|
+
if @mode.nil? then fail '"mode" not set. syntax= mode: get/post/put/delete.' end
|
27
|
+
if @function.nil? then fail '"function" not set. syntax= function: api-url.' end
|
28
|
+
puts "WARNING: 'function' should have basic auth. (example syntax= function: username:password@example.com/apicall)" unless @function.include?('username') && @function.include?('password')
|
32
29
|
end
|
33
30
|
|
34
31
|
def credential_sub(username, password)
|
@@ -36,52 +33,32 @@ class Call < ArgBucket
|
|
36
33
|
@function.gsub!('password', password) unless password.nil?
|
37
34
|
end
|
38
35
|
|
39
|
-
def set_call_args
|
40
|
-
input = @input
|
36
|
+
def set_call_args(input)
|
41
37
|
@input = {}
|
42
38
|
input.each do |arg|
|
43
39
|
value = get arg
|
44
|
-
if arg.nil? then puts
|
40
|
+
if arg.nil? then puts "WARNING: required argument '#{arg}' not set." end
|
45
41
|
@input[arg] = value
|
46
42
|
end
|
47
43
|
end
|
48
44
|
|
49
45
|
def run
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
puts "making a delete: #{@function} with #{@input}"
|
63
|
-
@response = RestClient.delete @function, params: @input
|
64
|
-
when 'curl_get'
|
65
|
-
command = "curl -v --url #{@function}"
|
66
|
-
@input.each_pair do |key, value|
|
67
|
-
command << " -F #{key}='#{value}'"
|
68
|
-
end
|
69
|
-
@response = `#{command}`
|
70
|
-
when 'curl_post'
|
71
|
-
command = "curl -v -X POST --url #{@function}"
|
72
|
-
@input.each_pair do |key, value|
|
73
|
-
command << " -F #{key}='#{value}'"
|
74
|
-
end
|
75
|
-
@response = `#{command}`
|
76
|
-
else
|
77
|
-
puts "#{@mode} not supported."
|
78
|
-
end
|
79
|
-
rescue RestClient::Unauthorized
|
80
|
-
puts 'Username or password was invalid.'
|
81
|
-
rescue RestClient::Exception
|
82
|
-
puts 'Internal Error or requested resource was not found.'
|
46
|
+
case @mode
|
47
|
+
when 'get', 'put', 'post', 'delete'
|
48
|
+
rest_request
|
49
|
+
when 'curl_get'
|
50
|
+
command = "curl -v --url #{@function}"
|
51
|
+
curl_request command
|
52
|
+
when 'curl_post'
|
53
|
+
command = "curl -v -X POST --url #{@function}"
|
54
|
+
curl_request command
|
55
|
+
@response = `#{command}`
|
56
|
+
else
|
57
|
+
puts "#{@mode} not supported."
|
83
58
|
end
|
84
59
|
if !@response.nil?
|
60
|
+
@code = @response.code unless @response.is_a? String
|
61
|
+
xml_to_json
|
85
62
|
run_plugin_script unless @plugin.nil?
|
86
63
|
log_response unless @logging == false
|
87
64
|
parse_response unless @output.nil?
|
@@ -89,30 +66,42 @@ class Call < ArgBucket
|
|
89
66
|
return @arg_bucket
|
90
67
|
end
|
91
68
|
|
92
|
-
def
|
93
|
-
|
94
|
-
|
95
|
-
else
|
96
|
-
custom = Plugin.new(@response).run
|
69
|
+
def curl_request(command)
|
70
|
+
@input.each_pair do |key, value|
|
71
|
+
command << " -F #{key}='#{value}'"
|
97
72
|
end
|
73
|
+
@response = `#{command}`
|
74
|
+
end
|
75
|
+
|
76
|
+
def rest_request
|
77
|
+
puts "Making a #{@mode}: #{@function} with #{@input}"
|
78
|
+
@response = RestClient::Request.execute(method: @mode, url: @function, headers: {params: @input}) { |response, request, result, &block|
|
79
|
+
if [301, 302, 307].include? response.code
|
80
|
+
response.follow_redirection(request, result, &block)
|
81
|
+
else
|
82
|
+
response.return!(request, result, &block)
|
83
|
+
end
|
84
|
+
}
|
85
|
+
rescue RestClient::Unauthorized
|
86
|
+
puts 'Username or password was invalid.'
|
87
|
+
rescue RestClient::Exception
|
88
|
+
puts 'Internal Error or requested resource was not found.'
|
89
|
+
end
|
90
|
+
|
91
|
+
def run_plugin_script
|
92
|
+
Plugin.new(@response).run
|
98
93
|
end
|
99
94
|
|
100
95
|
def log_response
|
101
96
|
logger = ResponseLogger.new
|
102
|
-
|
103
|
-
logger.object_log @function, @response.code, @response.body
|
104
|
-
else
|
105
|
-
logger.string_log @function, @response
|
106
|
-
end
|
97
|
+
logger.object_log @code, @response
|
107
98
|
end
|
108
99
|
|
109
100
|
def xml_to_json
|
110
|
-
@response = Hash.from_xml(@response).to_json unless !@response.include?('<?xml')
|
101
|
+
@response = JSON.parse(Hash.from_xml(@response).to_json) unless !@response.include?('<?xml')
|
111
102
|
end
|
112
103
|
|
113
104
|
def parse_response
|
114
|
-
xml_to_json
|
115
|
-
@response = JSON.parse @response
|
116
105
|
@output.each do |var|
|
117
106
|
if var.is_a? String
|
118
107
|
parser = Parser.new(var, nil, @response)
|
data/lib/apidragon/parser.rb
CHANGED
@@ -20,13 +20,13 @@ class Parser
|
|
20
20
|
if object.has_key? @field_name
|
21
21
|
@output = object[@field_name]
|
22
22
|
else
|
23
|
-
object.each_pair do |
|
24
|
-
|
23
|
+
object.each_pair do |_key, value|
|
24
|
+
recurse value
|
25
25
|
end
|
26
26
|
end
|
27
27
|
elsif object.is_a? Array
|
28
28
|
object.each do |element|
|
29
|
-
|
29
|
+
recurse element
|
30
30
|
end
|
31
31
|
end
|
32
32
|
end
|
@@ -36,7 +36,7 @@ class Parser
|
|
36
36
|
if object.has_value? @qualifier_value
|
37
37
|
@output = object[@field_name]
|
38
38
|
else
|
39
|
-
object.each_pair do |
|
39
|
+
object.each_pair do |_key, value|
|
40
40
|
qualified_recurse value
|
41
41
|
end
|
42
42
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: apidragon
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.2.
|
4
|
+
version: 1.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- isaiah thiessen
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-12-
|
11
|
+
date: 2015-12-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -246,7 +246,6 @@ extra_rdoc_files:
|
|
246
246
|
files:
|
247
247
|
- ".gitignore"
|
248
248
|
- Gemfile
|
249
|
-
- Gemfile.lock
|
250
249
|
- LICENSE.txt
|
251
250
|
- README.md
|
252
251
|
- README.rdoc
|
data/Gemfile.lock
DELETED
@@ -1,162 +0,0 @@
|
|
1
|
-
GEM
|
2
|
-
remote: http://rubygems.org/
|
3
|
-
specs:
|
4
|
-
abstract_type (0.0.7)
|
5
|
-
activesupport (4.2.5)
|
6
|
-
i18n (~> 0.7)
|
7
|
-
json (~> 1.7, >= 1.7.7)
|
8
|
-
minitest (~> 5.1)
|
9
|
-
thread_safe (~> 0.3, >= 0.3.4)
|
10
|
-
tzinfo (~> 1.1)
|
11
|
-
adamantium (0.2.0)
|
12
|
-
ice_nine (~> 0.11.0)
|
13
|
-
memoizable (~> 0.4.0)
|
14
|
-
addressable (2.3.8)
|
15
|
-
ast (2.1.0)
|
16
|
-
astrolabe (1.3.1)
|
17
|
-
parser (~> 2.2)
|
18
|
-
builder (3.2.2)
|
19
|
-
commander (4.3.5)
|
20
|
-
highline (~> 1.7.2)
|
21
|
-
concord (0.1.5)
|
22
|
-
adamantium (~> 0.2.0)
|
23
|
-
equalizer (~> 0.0.9)
|
24
|
-
contracts (0.12.0)
|
25
|
-
descendants_tracker (0.0.4)
|
26
|
-
thread_safe (~> 0.3, >= 0.3.1)
|
27
|
-
diff-lcs (1.2.5)
|
28
|
-
docile (1.1.5)
|
29
|
-
domain_name (0.5.25)
|
30
|
-
unf (>= 0.0.5, < 1.0.0)
|
31
|
-
equalizer (0.0.11)
|
32
|
-
faraday (0.9.2)
|
33
|
-
multipart-post (>= 1.2, < 3)
|
34
|
-
git (1.2.9.1)
|
35
|
-
github_api (0.13.0)
|
36
|
-
addressable (~> 2.3)
|
37
|
-
descendants_tracker (~> 0.0.4)
|
38
|
-
faraday (~> 0.8, < 0.10)
|
39
|
-
hashie (>= 3.4)
|
40
|
-
multi_json (>= 1.7.5, < 2.0)
|
41
|
-
nokogiri (~> 1.6.6)
|
42
|
-
oauth2
|
43
|
-
hashie (3.4.3)
|
44
|
-
highline (1.7.8)
|
45
|
-
http-cookie (1.0.2)
|
46
|
-
domain_name (~> 0.5)
|
47
|
-
i18n (0.7.0)
|
48
|
-
ice_nine (0.11.1)
|
49
|
-
jeweler (2.0.1)
|
50
|
-
builder
|
51
|
-
bundler (>= 1.0)
|
52
|
-
git (>= 1.2.5)
|
53
|
-
github_api
|
54
|
-
highline (>= 1.6.15)
|
55
|
-
nokogiri (>= 1.5.10)
|
56
|
-
rake
|
57
|
-
rdoc
|
58
|
-
json (1.8.3)
|
59
|
-
jwt (1.5.2)
|
60
|
-
memoizable (0.4.2)
|
61
|
-
thread_safe (~> 0.3, >= 0.3.1)
|
62
|
-
mime-types (2.99)
|
63
|
-
mini_portile (0.6.2)
|
64
|
-
minitest (5.8.3)
|
65
|
-
multi_json (1.11.2)
|
66
|
-
multi_xml (0.5.5)
|
67
|
-
multipart-post (2.0.0)
|
68
|
-
netrc (0.11.0)
|
69
|
-
nokogiri (1.6.6.4)
|
70
|
-
mini_portile (~> 0.6.0)
|
71
|
-
oauth2 (1.0.0)
|
72
|
-
faraday (>= 0.8, < 0.10)
|
73
|
-
jwt (~> 1.0)
|
74
|
-
multi_json (~> 1.3)
|
75
|
-
multi_xml (~> 0.5)
|
76
|
-
rack (~> 1.2)
|
77
|
-
parser (2.2.3.0)
|
78
|
-
ast (>= 1.1, < 3.0)
|
79
|
-
powerpack (0.1.1)
|
80
|
-
procto (0.0.2)
|
81
|
-
rack (1.6.4)
|
82
|
-
rainbow (2.0.0)
|
83
|
-
rake (10.4.2)
|
84
|
-
rdoc (3.12.2)
|
85
|
-
json (~> 1.4)
|
86
|
-
reek (1.6.6)
|
87
|
-
parser (~> 2.2.0.pre.7)
|
88
|
-
rainbow (>= 1.99, < 3.0)
|
89
|
-
unparser (~> 0.2.2)
|
90
|
-
rest-client (1.8.0)
|
91
|
-
http-cookie (>= 1.0.2, < 2.0)
|
92
|
-
mime-types (>= 1.16, < 3.0)
|
93
|
-
netrc (~> 0.7)
|
94
|
-
roodi (2.2.0)
|
95
|
-
ruby_parser (~> 2.3.0)
|
96
|
-
rubocop (0.35.1)
|
97
|
-
astrolabe (~> 1.3)
|
98
|
-
parser (>= 2.2.3.0, < 3.0)
|
99
|
-
powerpack (~> 0.1)
|
100
|
-
rainbow (>= 1.99.1, < 3.0)
|
101
|
-
ruby-progressbar (~> 1.7)
|
102
|
-
tins (<= 1.6.0)
|
103
|
-
ruby-progressbar (1.7.5)
|
104
|
-
ruby_parser (2.3.1)
|
105
|
-
sexp_processor (~> 3.0)
|
106
|
-
settingslogic (2.0.9)
|
107
|
-
sexp_processor (3.2.0)
|
108
|
-
shoulda (3.5.0)
|
109
|
-
shoulda-context (~> 1.0, >= 1.0.1)
|
110
|
-
shoulda-matchers (>= 1.4.1, < 3.0)
|
111
|
-
shoulda-context (1.2.1)
|
112
|
-
shoulda-matchers (2.8.0)
|
113
|
-
activesupport (>= 3.0.0)
|
114
|
-
simplecov (0.10.0)
|
115
|
-
docile (~> 1.1.0)
|
116
|
-
json (~> 1.8)
|
117
|
-
simplecov-html (~> 0.10.0)
|
118
|
-
simplecov-html (0.10.0)
|
119
|
-
terminal-announce (1.0.0)
|
120
|
-
bundler
|
121
|
-
contracts
|
122
|
-
rainbow
|
123
|
-
thread_safe (0.3.5)
|
124
|
-
tins (1.6.0)
|
125
|
-
tzinfo (1.2.2)
|
126
|
-
thread_safe (~> 0.1)
|
127
|
-
unf (0.1.4)
|
128
|
-
unf_ext
|
129
|
-
unf_ext (0.0.7.1)
|
130
|
-
unparser (0.2.4)
|
131
|
-
abstract_type (~> 0.0.7)
|
132
|
-
adamantium (~> 0.2.0)
|
133
|
-
concord (~> 0.1.5)
|
134
|
-
diff-lcs (~> 1.2.5)
|
135
|
-
equalizer (~> 0.0.9)
|
136
|
-
parser (~> 2.2.2)
|
137
|
-
procto (~> 0.0.2)
|
138
|
-
yard (0.8.7.6)
|
139
|
-
|
140
|
-
PLATFORMS
|
141
|
-
ruby
|
142
|
-
|
143
|
-
DEPENDENCIES
|
144
|
-
activesupport (~> 4.2)
|
145
|
-
bundler (~> 1.0)
|
146
|
-
commander (~> 4.3)
|
147
|
-
jeweler (~> 2.0)
|
148
|
-
json (~> 1.8)
|
149
|
-
nokogiri (~> 1.6)
|
150
|
-
rdoc (~> 3.12)
|
151
|
-
reek (~> 1.2)
|
152
|
-
rest-client (~> 1.8)
|
153
|
-
roodi (~> 2.1)
|
154
|
-
rubocop (~> 0.32)
|
155
|
-
settingslogic (~> 2.0)
|
156
|
-
shoulda (~> 3.5)
|
157
|
-
simplecov (~> 0.10)
|
158
|
-
terminal-announce (~> 1.0)
|
159
|
-
yard (~> 0.7)
|
160
|
-
|
161
|
-
BUNDLED WITH
|
162
|
-
1.10.6
|