ronin 1.1.0 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/ChangeLog.md +23 -0
- data/README.md +19 -13
- data/Rakefile +2 -1
- data/gemspec.yml +18 -17
- data/lib/bond/completions/ronin.rb +147 -0
- data/lib/ronin/auto_load.rb +30 -28
- data/lib/ronin/database/migrations/1.0.0.rb +1 -0
- data/lib/ronin/model/has_authors.rb +92 -2
- data/lib/ronin/model/has_description.rb +54 -2
- data/lib/ronin/model/has_license.rb +101 -2
- data/lib/ronin/model/has_name.rb +72 -2
- data/lib/ronin/model/has_title.rb +52 -2
- data/lib/ronin/model/has_unique_name.rb +93 -2
- data/lib/ronin/model/has_version.rb +58 -2
- data/lib/ronin/model/model.rb +91 -52
- data/lib/ronin/os.rb +30 -15
- data/lib/ronin/repository.rb +1 -1
- data/lib/ronin/ronin.rb +0 -15
- data/lib/ronin/script/script.rb +257 -2
- data/lib/ronin/ui/console.rb +2 -199
- data/lib/ronin/ui/console/commands.rb +164 -0
- data/lib/ronin/ui/console/console.rb +215 -0
- data/lib/ronin/ui/console/context.rb +95 -0
- data/lib/ronin/version.rb +1 -1
- data/spec/os_spec.rb +18 -13
- metadata +206 -239
- data/lib/ronin/class_methods.rb +0 -49
- data/lib/ronin/model/class_methods.rb +0 -58
- data/lib/ronin/model/has_authors/class_methods.rb +0 -60
- data/lib/ronin/model/has_authors/has_authors.rb +0 -70
- data/lib/ronin/model/has_description/class_methods.rb +0 -49
- data/lib/ronin/model/has_description/has_description.rb +0 -49
- data/lib/ronin/model/has_license/class_methods.rb +0 -68
- data/lib/ronin/model/has_license/has_license.rb +0 -71
- data/lib/ronin/model/has_name/class_methods.rb +0 -48
- data/lib/ronin/model/has_name/has_name.rb +0 -62
- data/lib/ronin/model/has_title/class_methods.rb +0 -48
- data/lib/ronin/model/has_title/has_title.rb +0 -48
- data/lib/ronin/model/has_unique_name/class_methods.rb +0 -51
- data/lib/ronin/model/has_unique_name/has_unique_name.rb +0 -78
- data/lib/ronin/model/has_version/class_methods.rb +0 -54
- data/lib/ronin/model/has_version/has_version.rb +0 -48
- data/lib/ronin/script/class_methods.rb +0 -84
- data/lib/ronin/script/instance_methods.rb +0 -217
data/ChangeLog.md
CHANGED
@@ -1,3 +1,26 @@
|
|
1
|
+
### 1.2.0 / 2011-08-15
|
2
|
+
|
3
|
+
* Require dm-is-predefined ~> 0.4.
|
4
|
+
* Added {Ronin::UI::Console::Context}.
|
5
|
+
* Added custom tab-completion to {Ronin::UI::Console} for:
|
6
|
+
* {Ronin::IPAddress}
|
7
|
+
* {Ronin::HostName}
|
8
|
+
* {Ronin::EmailAddress}
|
9
|
+
* {Ronin::URL}
|
10
|
+
* Paths
|
11
|
+
* Commands
|
12
|
+
* Added the ability to run commands in Ronin Console, via the
|
13
|
+
`!command --args` syntax.
|
14
|
+
* Added custom `!command`s to the Ronin Console:
|
15
|
+
* `!edit` - Edits a Ruby tempfile and loads the contents afterwards.
|
16
|
+
* `!cd` - Changes the current working directory and updates
|
17
|
+
`ENV['OLDPWD']`.
|
18
|
+
* `!export` - Sets `ENV` variables.
|
19
|
+
* Added an index to {Ronin::OS.version}.
|
20
|
+
* Refactored {Ronin::OS.predefine} using dm-is-predefined.
|
21
|
+
* Fixed a bug in {Ronin::UI::Console.setup} where the wrong binding was
|
22
|
+
being passed to Ripl.
|
23
|
+
|
1
24
|
### 1.1.0 / 2011-07-04
|
2
25
|
|
3
26
|
* Require env ~> 0.2.
|
data/README.md
CHANGED
@@ -72,9 +72,9 @@ Mercurial or Git.
|
|
72
72
|
[ronin-support](http://github.com/ronin-ruby/ronin-support#readme).
|
73
73
|
* Provides a customized Ruby Console with:
|
74
74
|
* Syntax highlighting.
|
75
|
-
* Tab-completion
|
76
|
-
* Auto-indentation
|
77
|
-
* Pretty-
|
75
|
+
* Tab-completion.
|
76
|
+
* Auto-indentation.
|
77
|
+
* Pretty-Printing (`pp`).
|
78
78
|
* `print_info`, `print_error`, `print_warning` and `print_debug`
|
79
79
|
output helper methods with color-output.
|
80
80
|
* Provides an extensible command-line interface based on
|
@@ -110,7 +110,7 @@ Update a specific Repositories:
|
|
110
110
|
|
111
111
|
$ ronin repos --update repo-name
|
112
112
|
|
113
|
-
Uninstall
|
113
|
+
Uninstall a specific Repositories:
|
114
114
|
|
115
115
|
$ ronin repos --uninstall repo-name
|
116
116
|
|
@@ -131,22 +131,22 @@ Remove a Database:
|
|
131
131
|
* [Ruby](http://www.ruby-lang.org/) >= 1.8.7
|
132
132
|
* [DataMapper](http://datamapper.org/):
|
133
133
|
* [dm-sqlite-adapter](http://github.com/datamapper/dm-sqlite-adapter#readme)
|
134
|
-
~> 1.1
|
134
|
+
~> 1.1
|
135
135
|
* [libsqlite3](http://sqlite.org/)
|
136
136
|
* [dm-core](http://github.com/datamapper/dm-core#readme)
|
137
|
-
~> 1.1
|
137
|
+
~> 1.1
|
138
138
|
* [dm-types](http://github.com/datamapper/dm-types#readme)
|
139
|
-
~> 1.1
|
139
|
+
~> 1.1
|
140
140
|
* [dm-migrations](http://github.com/datamapper/dm-migrations#readme)
|
141
|
-
~> 1.1
|
141
|
+
~> 1.1
|
142
142
|
* [dm-validations](http://github.com/datamapper/dm-validations#readme)
|
143
|
-
~> 1.1
|
143
|
+
~> 1.1
|
144
144
|
* [dm-aggregates](http://github.com/datamapper/dm-aggregates#readme)
|
145
|
-
~> 1.1
|
145
|
+
~> 1.1
|
146
146
|
* [dm-timestamps](http://github.com/datamapper/dm-timestamps#readme)
|
147
|
-
~> 1.1
|
147
|
+
~> 1.1
|
148
148
|
* [dm-is-predefined](http://github.com/postmodern/dm-is-predefined#readme)
|
149
|
-
~> 0.
|
149
|
+
~> 0.4
|
150
150
|
* [uri-query_params](http://github.com/postmodern/uri-query_params#readme)
|
151
151
|
~> 0.5, >= 0.5.2
|
152
152
|
* [open_namespace](http://github.com/postmodern/open_namespace#readme)
|
@@ -162,7 +162,7 @@ Remove a Database:
|
|
162
162
|
* [pullr](http://github.com/postmodern/pullr#readme)
|
163
163
|
~> 0.1, >= 0.1.2
|
164
164
|
* [ripl](https://github.com/cldwalker/ripl#readme)
|
165
|
-
~> 0.3
|
165
|
+
~> 0.3
|
166
166
|
* [ripl-multi_line](https://github.com/janlelis/ripl-multi_line#readme)
|
167
167
|
~> 0.2
|
168
168
|
* [ripl-auto_indent](https://github.com/janlelis/ripl-auto_indent#readme)
|
@@ -205,6 +205,12 @@ functionality.
|
|
205
205
|
[Ronin Gen](http://github.com/ronin-ruby/ronin-gen#readme) is a Ruby library
|
206
206
|
for Ronin that provides various generators.
|
207
207
|
|
208
|
+
### Ronin Scanners
|
209
|
+
|
210
|
+
[Ronin Scanners](http://github.com/ronin-ruby/ronin-scanners#readme)
|
211
|
+
is a Ruby library for Ronin that provides Ruby interfaces to
|
212
|
+
various third-party security scanners.
|
213
|
+
|
208
214
|
### Ronin SQL
|
209
215
|
|
210
216
|
[Ronin SQL](http://github.com/ronin-ruby/ronin-sql#readme) is a Ruby library
|
data/Rakefile
CHANGED
@@ -46,7 +46,7 @@ DataMapper::Visualizer::Rake::GraphVizTask.new(
|
|
46
46
|
ronin/organization
|
47
47
|
ronin/os_guess
|
48
48
|
ronin/os
|
49
|
-
ronin/
|
49
|
+
ronin/script/path
|
50
50
|
ronin/repository
|
51
51
|
ronin/port
|
52
52
|
ronin/service
|
@@ -58,6 +58,7 @@ DataMapper::Visualizer::Rake::GraphVizTask.new(
|
|
58
58
|
ronin/tcp_port
|
59
59
|
ronin/udp_port
|
60
60
|
ronin/url_scheme
|
61
|
+
ronin/url_query_param_name
|
61
62
|
ronin/url_query_param
|
62
63
|
ronin/url
|
63
64
|
ronin/user_name
|
data/gemspec.yml
CHANGED
@@ -13,12 +13,13 @@ has_yard: true
|
|
13
13
|
post_install_message: |
|
14
14
|
*************************************************************************
|
15
15
|
|
16
|
-
Thank you for installing Ronin
|
17
|
-
|
16
|
+
Thank you for installing Ronin!
|
17
|
+
|
18
|
+
To list the available commands:
|
18
19
|
|
19
20
|
$ ronin help
|
20
21
|
|
21
|
-
To
|
22
|
+
To start the Ronin Console:
|
22
23
|
|
23
24
|
$ ronin
|
24
25
|
|
@@ -35,20 +36,20 @@ post_install_message: |
|
|
35
36
|
required_ruby_version: ">= 1.8.7"
|
36
37
|
|
37
38
|
dependencies:
|
38
|
-
# DataMapper adapters
|
39
|
-
dm-sqlite-adapter: ~> 1.1
|
40
|
-
# DataMapper dependencies
|
41
|
-
dm-core: ~> 1.1
|
42
|
-
dm-types: ~> 1.1
|
43
|
-
dm-constraints: ~> 1.1
|
44
|
-
dm-migrations: ~> 1.1
|
45
|
-
dm-validations: ~> 1.1
|
46
|
-
dm-serializer: ~> 1.1
|
47
|
-
dm-aggregates: ~> 1.1
|
48
|
-
dm-timestamps: ~> 1.1
|
49
|
-
# DataMapper plugins
|
50
|
-
dm-is-predefined: ~> 0.
|
51
|
-
# Library dependencies
|
39
|
+
# DataMapper adapters:
|
40
|
+
dm-sqlite-adapter: ~> 1.1
|
41
|
+
# DataMapper dependencies:
|
42
|
+
dm-core: ~> 1.1
|
43
|
+
dm-types: ~> 1.1
|
44
|
+
dm-constraints: ~> 1.1
|
45
|
+
dm-migrations: ~> 1.1
|
46
|
+
dm-validations: ~> 1.1
|
47
|
+
dm-serializer: ~> 1.1
|
48
|
+
dm-aggregates: ~> 1.1
|
49
|
+
dm-timestamps: ~> 1.1
|
50
|
+
# DataMapper plugins:
|
51
|
+
dm-is-predefined: ~> 0.4
|
52
|
+
# Library dependencies:
|
52
53
|
uri-query_params: ~> 0.5, >= 0.5.2
|
53
54
|
open_namespace: ~> 0.3
|
54
55
|
parameters: ~> 0.2, >= 0.2.3
|
@@ -0,0 +1,147 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2006-2011 Hal Brodigan (postmodern.mod3 at gmail.com)
|
3
|
+
#
|
4
|
+
# This file is part of Ronin.
|
5
|
+
#
|
6
|
+
# Ronin is free software: you can redistribute it and/or modify
|
7
|
+
# it under the terms of the GNU General Public License as published by
|
8
|
+
# the Free Software Foundation, either version 3 of the License, or
|
9
|
+
# (at your option) any later version.
|
10
|
+
#
|
11
|
+
# Ronin is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
# GNU General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU General Public License
|
17
|
+
# along with Ronin. If not, see <http://www.gnu.org/licenses/>.
|
18
|
+
#
|
19
|
+
|
20
|
+
require 'ronin/ui/console/commands'
|
21
|
+
require 'ronin/address'
|
22
|
+
require 'ronin/ip_address'
|
23
|
+
require 'ronin/host_name'
|
24
|
+
require 'ronin/email_address'
|
25
|
+
require 'ronin/url'
|
26
|
+
|
27
|
+
require 'set'
|
28
|
+
require 'env'
|
29
|
+
|
30
|
+
complete(:on => /^\![a-zA-Z]\w*/) do |cmd|
|
31
|
+
prefix = cmd[1..-1]
|
32
|
+
glob = "#{prefix}*"
|
33
|
+
paths = Set[]
|
34
|
+
|
35
|
+
# search through $PATH for similar program names
|
36
|
+
Env.paths.each do |dir|
|
37
|
+
Pathname.glob(dir.join(glob)) do |path|
|
38
|
+
if (path.file? && path.executable?)
|
39
|
+
paths << "!#{path.basename}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# add the black-listed keywords last
|
45
|
+
Ronin::UI::Console::Commands::BLACKLIST.each do |keyword|
|
46
|
+
paths << "!#{keyword}" if keyword.start_with?(prefix)
|
47
|
+
end
|
48
|
+
|
49
|
+
paths
|
50
|
+
end
|
51
|
+
|
52
|
+
#
|
53
|
+
# {URL} completion in the context of URLs.
|
54
|
+
#
|
55
|
+
#
|
56
|
+
# http://www.example.com/in[TAB][TAB] => http://www.example.com/index.html
|
57
|
+
#
|
58
|
+
complete(:anywhere => /[a-z]+:\/\/([^:\/\?]+(:\d+)?(\/[^\?;]*(\?[^\?;]*)?)?)?/) do |url|
|
59
|
+
match = url.match(/([a-z]+):\/\/([^:\/\?]+)(:\d+)?(\/[^\?;]*)?(\?[^\?;]*)?/)
|
60
|
+
|
61
|
+
query = Ronin::URL.all('scheme.name' => match[1])
|
62
|
+
|
63
|
+
if match[2]
|
64
|
+
unless (match[4] || match[3])
|
65
|
+
query = query.all('host_name.address.like' => "#{match[2]}%")
|
66
|
+
else
|
67
|
+
query = query.all('host_name.address' => match[2])
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
if match[3]
|
72
|
+
query = query.all('port.number' => match[3])
|
73
|
+
end
|
74
|
+
|
75
|
+
if match[4]
|
76
|
+
unless match[5]
|
77
|
+
query = query.all(:path.like => "#{match[4]}%")
|
78
|
+
else
|
79
|
+
query = query.all(:path => match[4])
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
if match[5]
|
84
|
+
params = URI::QueryParams.parse(match[5][1..-1]).to_a
|
85
|
+
|
86
|
+
params[0..-2].each do |name,value|
|
87
|
+
query = query.all(
|
88
|
+
'query_params.name.name' => name,
|
89
|
+
'query_params.value' => value
|
90
|
+
)
|
91
|
+
end
|
92
|
+
|
93
|
+
if (param = params.last)
|
94
|
+
if param[1].empty?
|
95
|
+
query = query.all('query_params.name.name.like' => "#{param[0]}%")
|
96
|
+
else
|
97
|
+
query = query.all(
|
98
|
+
'query_params.name.name' => param[0],
|
99
|
+
'query_params.value.like' => "#{param[1]}%"
|
100
|
+
)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
query
|
106
|
+
end
|
107
|
+
|
108
|
+
#
|
109
|
+
# {IPAddress} completion:
|
110
|
+
#
|
111
|
+
# 192.168.[TAB][TAB] => 192.168.0.1
|
112
|
+
#
|
113
|
+
complete(:anywhere => /(\d{1,3}\.){1,3}\d{,2}/) do |addr|
|
114
|
+
Ronin::Address.all(:address.like => "#{addr}%")
|
115
|
+
end
|
116
|
+
|
117
|
+
#
|
118
|
+
# {HostName} completion:
|
119
|
+
#
|
120
|
+
# www.[TAB][TAB] => www.example.com
|
121
|
+
#
|
122
|
+
complete(:anywhere => /[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]*/) do |host|
|
123
|
+
Ronin::HostName.all(:address.like => "#{host}%")
|
124
|
+
end
|
125
|
+
|
126
|
+
#
|
127
|
+
# {EmailAddress} completeion:
|
128
|
+
#
|
129
|
+
# alice@[TAB][TAB] => alice@example.com
|
130
|
+
#
|
131
|
+
complete(:anywhere => /[a-zA-Z0-9\._-]+@[a-zA-Z0-9\._-]*/) do |email|
|
132
|
+
user, host = email.split('@',2)
|
133
|
+
|
134
|
+
Ronin::EmailAddress.all(
|
135
|
+
'user_name.name' => user,
|
136
|
+
'host_name.address.like' => "#{host}%"
|
137
|
+
)
|
138
|
+
end
|
139
|
+
|
140
|
+
#
|
141
|
+
# Path completion.
|
142
|
+
#
|
143
|
+
# /etc/pa[TAB][TAB] => /etc/passwd
|
144
|
+
#
|
145
|
+
complete(:anywhere => /\/([^\/]+\/)*[^\/]*/) do |path|
|
146
|
+
Dir["#{path}*"]
|
147
|
+
end
|
data/lib/ronin/auto_load.rb
CHANGED
@@ -22,46 +22,48 @@ require 'open_namespace'
|
|
22
22
|
module Ronin
|
23
23
|
#
|
24
24
|
# When included into other namespaces, it allows for auto-loading Classes
|
25
|
-
# or Modules via {#const_missing}.
|
25
|
+
# or Modules via {ClassMethods#const_missing}.
|
26
26
|
#
|
27
27
|
# @since 1.1.0
|
28
28
|
#
|
29
29
|
module AutoLoad
|
30
30
|
def self.included(base)
|
31
31
|
base.send :include, OpenNamespace
|
32
|
-
base.send :extend,
|
32
|
+
base.send :extend, ClassMethods
|
33
33
|
end
|
34
34
|
|
35
|
-
|
35
|
+
module ClassMethods
|
36
|
+
protected
|
36
37
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
38
|
+
#
|
39
|
+
# Transparently auto-loads Classes and Modules from their respective
|
40
|
+
# files using [OpenNamespace](http://rubydoc.info/gems/open_namespace).
|
41
|
+
#
|
42
|
+
# @param [String, Symbol] name
|
43
|
+
# The name of the Class or Module to auto-load.
|
44
|
+
#
|
45
|
+
# @return [Class, Module]
|
46
|
+
# The loaded Class or Module.
|
47
|
+
#
|
48
|
+
# @raise [NameError]
|
49
|
+
# The Class or Module could not be found.
|
50
|
+
#
|
51
|
+
# @since 1.1.0
|
52
|
+
#
|
53
|
+
# @api public
|
54
|
+
#
|
55
|
+
def const_missing(name)
|
56
|
+
const = super(name)
|
56
57
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
58
|
+
if Object.const_defined?('DataMapper')
|
59
|
+
# if the loaded Class is a DataMapper Resource, re-finalize
|
60
|
+
if const < DataMapper::Resource
|
61
|
+
DataMapper.finalize
|
62
|
+
end
|
61
63
|
end
|
62
|
-
end
|
63
64
|
|
64
|
-
|
65
|
+
return const
|
66
|
+
end
|
65
67
|
end
|
66
68
|
end
|
67
69
|
end
|
@@ -17,5 +17,95 @@
|
|
17
17
|
# along with Ronin. If not, see <http://www.gnu.org/licenses/>.
|
18
18
|
#
|
19
19
|
|
20
|
-
require 'ronin/model
|
21
|
-
require 'ronin/
|
20
|
+
require 'ronin/model'
|
21
|
+
require 'ronin/author'
|
22
|
+
|
23
|
+
module Ronin
|
24
|
+
module Model
|
25
|
+
#
|
26
|
+
# Adds an `authors` relationship between a model and the {Author} model.
|
27
|
+
#
|
28
|
+
module HasAuthors
|
29
|
+
#
|
30
|
+
# Adds the `authors` relationship and {ClassMethods} to the model.
|
31
|
+
#
|
32
|
+
# @param [Class] base
|
33
|
+
# The model.
|
34
|
+
#
|
35
|
+
# @api semipublic
|
36
|
+
#
|
37
|
+
def self.included(base)
|
38
|
+
base.send :include, Model, InstanceMethods
|
39
|
+
base.send :extend, ClassMethods
|
40
|
+
|
41
|
+
base.module_eval do
|
42
|
+
# The authors associated with the model.
|
43
|
+
has 0..n, :authors, Ronin::Author, :through => DataMapper::Resource
|
44
|
+
|
45
|
+
Ronin::Author.has 0..n, self.relationship_name,
|
46
|
+
:through => DataMapper::Resource,
|
47
|
+
:model => self
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
#
|
52
|
+
# Class methods that are added when {HasAuthors} is included into a
|
53
|
+
# model.
|
54
|
+
#
|
55
|
+
module ClassMethods
|
56
|
+
#
|
57
|
+
# Finds all resources associated with a given author.
|
58
|
+
#
|
59
|
+
# @param [String] name
|
60
|
+
# The name of the author.
|
61
|
+
#
|
62
|
+
# @return [Array<Model>]
|
63
|
+
# The resources written by the author.
|
64
|
+
#
|
65
|
+
# @api public
|
66
|
+
#
|
67
|
+
def written_by(name)
|
68
|
+
all('authors.name.like' => "%#{name}%")
|
69
|
+
end
|
70
|
+
|
71
|
+
#
|
72
|
+
# Finds all resources associated with a given organization.
|
73
|
+
#
|
74
|
+
# @param [String] name
|
75
|
+
# The name of the organization.
|
76
|
+
#
|
77
|
+
# @return [Array<Model>]
|
78
|
+
# The resources associated with the organization.
|
79
|
+
#
|
80
|
+
# @api public
|
81
|
+
#
|
82
|
+
def written_for(name)
|
83
|
+
all('authors.organization.like' => "%#{name}%")
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
#
|
88
|
+
# Instance methods that are added when {HasAuthors} is included into a
|
89
|
+
# model.
|
90
|
+
#
|
91
|
+
module InstanceMethods
|
92
|
+
#
|
93
|
+
# Adds a new author to the resource.
|
94
|
+
#
|
95
|
+
# @param [Hash] attributes
|
96
|
+
# Additional attributes to create the new author.
|
97
|
+
#
|
98
|
+
# @example
|
99
|
+
# author :name => 'Anonymous',
|
100
|
+
# :email => 'anon@example.com',
|
101
|
+
# :organization => 'Anonymous LLC'
|
102
|
+
#
|
103
|
+
# @api public
|
104
|
+
#
|
105
|
+
def author(attributes)
|
106
|
+
self.authors.new(attributes)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|