razorrisk-cassini-common 0.26.24
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 +7 -0
- data/CHANGELOG.md +22 -0
- data/LICENSE +5 -0
- data/README.md +2 -0
- data/Rakefile +102 -0
- data/lib/razor_risk/cassini/applications/microservice.rb +318 -0
- data/lib/razor_risk/cassini/applications/rest_framework/route_verb_dispatcher.rb +120 -0
- data/lib/razor_risk/cassini/applications/rest_framework/verb_handler.rb +117 -0
- data/lib/razor_risk/cassini/applications/route_verb_adaptors/utilities/collection_get_helper.rb +86 -0
- data/lib/razor_risk/cassini/applications/securable_microservice.rb +164 -0
- data/lib/razor_risk/cassini/applications/secured_microservice.rb +63 -0
- data/lib/razor_risk/cassini/applications/unsecured_microservice.rb +77 -0
- data/lib/razor_risk/cassini/authorisation/header_helpers.rb +271 -0
- data/lib/razor_risk/cassini/authorisation/security_model_helpers.rb +93 -0
- data/lib/razor_risk/cassini/authorisation.rb +27 -0
- data/lib/razor_risk/cassini/cli.rb +19 -0
- data/lib/razor_risk/cassini/common/version.rb +44 -0
- data/lib/razor_risk/cassini/common.rb +32 -0
- data/lib/razor_risk/cassini/constants.rb +68 -0
- data/lib/razor_risk/cassini/diagnostics/util_functions.rb +248 -0
- data/lib/razor_risk/cassini/diagnostics/zeroth_include.rb +35 -0
- data/lib/razor_risk/cassini/extensions/libclimate/common_options.rb +267 -0
- data/lib/razor_risk/cassini/extensions/libclimate.rb +26 -0
- data/lib/razor_risk/cassini/header_functions.rb +59 -0
- data/lib/razor_risk/cassini/main.rb +238 -0
- data/lib/razor_risk/cassini/mixin/razor_response_validator.rb +176 -0
- data/lib/razor_risk/cassini/testing/suppress_pantheios_logging.rb +31 -0
- data/lib/razor_risk/cassini/util/conversion_util.rb +176 -0
- data/lib/razor_risk/cassini/util/program_execution_util.rb +379 -0
- data/lib/razor_risk/cassini/util/secrets_util.rb +229 -0
- data/lib/razor_risk/cassini/util/version_util.rb +88 -0
- data/lib/razor_risk/sinatra/helpers/check_auth_helper.rb +209 -0
- data/lib/razor_risk/sinatra/helpers/validate_accept_helper.rb +69 -0
- data/lib/razor_risk/sinatra/helpers/validate_content_type_helper.rb +74 -0
- data/lib/razor_risk/sinatra/helpers/validate_query_parameters_helper.rb +198 -0
- data/test/scratch/cassini/util/convert_XML.rb +54 -0
- data/test/unit/applications/route_verb_adaptors/utilities/tc_collection_get_helper.rb +236 -0
- data/test/unit/applications/tc_verb_handler.rb +130 -0
- data/test/unit/mixin/tc_razor_response_validator.rb +328 -0
- data/test/unit/sinatra/helpers/tc_validate_query_parameters_helper.rb +134 -0
- data/test/unit/tc_authorisation_util.rb +265 -0
- data/test/unit/tc_load_secrets.rb +95 -0
- data/test/unit/util/tc_conversion_util.rb +393 -0
- data/test/unit/util/tc_program_execution_util.rb +462 -0
- metadata +380 -0
@@ -0,0 +1,134 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$:.unshift File.join(File.dirname(__FILE__), *(['..'] * 4), 'lib')
|
4
|
+
|
5
|
+
require 'simplecov'
|
6
|
+
|
7
|
+
require 'razor_risk/cassini/testing/suppress_pantheios_logging' unless $DEBUG
|
8
|
+
|
9
|
+
require 'razor_risk/sinatra/helpers/validate_query_parameters_helper'
|
10
|
+
|
11
|
+
require 'xqsr3/extensions/test/unit'
|
12
|
+
require 'test/unit'
|
13
|
+
|
14
|
+
class Test_QueryParametersHelper < Test::Unit::TestCase
|
15
|
+
|
16
|
+
include ::RazorRisk::Sinatra::Helpers::ValidateQueryParametersHelper
|
17
|
+
|
18
|
+
# mocking
|
19
|
+
|
20
|
+
class StopException < RuntimeError; end
|
21
|
+
|
22
|
+
def halt code, headers, body_lines
|
23
|
+
|
24
|
+
body_lines = [ body_lines ] unless body_lines.nil? || ::Array === body_lines
|
25
|
+
|
26
|
+
@code = code
|
27
|
+
@headers = headers
|
28
|
+
@body_lines = body_lines
|
29
|
+
|
30
|
+
raise StopException, 'stopped!' if code
|
31
|
+
end
|
32
|
+
|
33
|
+
attr_reader :code
|
34
|
+
attr_reader :headers
|
35
|
+
attr_reader :body_lines
|
36
|
+
|
37
|
+
# tests
|
38
|
+
|
39
|
+
def test_valid_cases
|
40
|
+
|
41
|
+
spec = %w{
|
42
|
+
|
43
|
+
page-base
|
44
|
+
page-extent
|
45
|
+
}
|
46
|
+
|
47
|
+
halt nil, nil, nil
|
48
|
+
assert validate_query_parameters Hash[], spec
|
49
|
+
assert_nil code
|
50
|
+
assert_nil headers
|
51
|
+
assert_nil body_lines
|
52
|
+
|
53
|
+
halt nil, nil, nil
|
54
|
+
assert validate_query_parameters Hash[ [ [ 'domain', 'domain' ] ] ], spec, [ 'domain' ]
|
55
|
+
assert_nil code
|
56
|
+
assert_nil headers
|
57
|
+
assert_nil body_lines
|
58
|
+
|
59
|
+
halt nil, nil, nil
|
60
|
+
assert validate_query_parameters Hash[ [ [ 'page-base', '0' ] ] ], spec
|
61
|
+
assert_nil code
|
62
|
+
assert_nil headers
|
63
|
+
assert_nil body_lines
|
64
|
+
|
65
|
+
halt nil, nil, nil
|
66
|
+
assert validate_query_parameters Hash[ [ [ 'page-base', '0' ], [ 'page-extent', '100' ] ] ], spec
|
67
|
+
assert_nil code
|
68
|
+
assert_nil headers
|
69
|
+
assert_nil body_lines
|
70
|
+
|
71
|
+
|
72
|
+
halt nil, nil, nil
|
73
|
+
assert validate_query_parameters Hash[ [ [ 'page-base', '0' ], [ 'page-extent', '100' ], [ 'blah', '*' ] ] ], spec, [ 'blah' ]
|
74
|
+
assert_nil code
|
75
|
+
assert_nil headers
|
76
|
+
assert_nil body_lines
|
77
|
+
|
78
|
+
halt nil, nil, nil
|
79
|
+
assert_raise_with_message(StopException, 'stopped!') { validate_query_parameters(Hash[ [ [ 'some-other' => 'xxx' ] ] ], spec) }
|
80
|
+
assert_equal 422, code
|
81
|
+
assert_equal Hash.new, headers
|
82
|
+
assert_kind_of ::Array, body_lines
|
83
|
+
assert_not_empty body_lines
|
84
|
+
assert_match /One or more parameters.*some-other.*are not supported on this route/, body_lines[0]
|
85
|
+
end
|
86
|
+
|
87
|
+
def test_valid_cases_with_ignoring_Sinatra_built_ins
|
88
|
+
|
89
|
+
spec = %w{
|
90
|
+
|
91
|
+
page-base
|
92
|
+
page-extent
|
93
|
+
}
|
94
|
+
|
95
|
+
halt nil, nil, nil
|
96
|
+
assert validate_query_parameters Hash[], spec
|
97
|
+
assert_nil code
|
98
|
+
assert_nil headers
|
99
|
+
assert_nil body_lines
|
100
|
+
|
101
|
+
halt nil, nil, nil
|
102
|
+
assert validate_query_parameters Hash[ [ [ 'splat', '1' ] ] ], spec
|
103
|
+
assert_nil code
|
104
|
+
assert_nil headers
|
105
|
+
assert_nil body_lines
|
106
|
+
|
107
|
+
halt nil, nil, nil
|
108
|
+
assert validate_query_parameters Hash[ [ [ 'captures', // ] ] ], spec
|
109
|
+
assert_nil code
|
110
|
+
assert_nil headers
|
111
|
+
assert_nil body_lines
|
112
|
+
|
113
|
+
halt nil, nil, nil
|
114
|
+
assert validate_query_parameters Hash[ [ [ 'page-base', '0' ], [ 'splat', '*' ] ] ], spec
|
115
|
+
assert_nil code
|
116
|
+
assert_nil headers
|
117
|
+
assert_nil body_lines
|
118
|
+
|
119
|
+
halt nil, nil, nil
|
120
|
+
assert validate_query_parameters Hash[ [ [ 'page-base', '0' ], [ 'splat', '*' ], [ 'page-extent', '100' ] ] ], spec
|
121
|
+
assert_nil code
|
122
|
+
assert_nil headers
|
123
|
+
assert_nil body_lines
|
124
|
+
|
125
|
+
halt nil, nil, nil
|
126
|
+
assert_raise_with_message(StopException, 'stopped!') { validate_query_parameters(Hash[ [ [ 'some-other' => 'xxx' ] ] ], spec) }
|
127
|
+
assert_equal 422, code
|
128
|
+
assert_equal Hash.new, headers
|
129
|
+
assert_kind_of ::Array, body_lines
|
130
|
+
assert_not_empty body_lines
|
131
|
+
assert_match /One or more parameters.*some-other.*are not supported on this route/, body_lines[0]
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
@@ -0,0 +1,265 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
|
3
|
+
$:.unshift File.join(File.dirname(__FILE__), '../../lib')
|
4
|
+
|
5
|
+
require 'simplecov'
|
6
|
+
|
7
|
+
require 'razor_risk/cassini/testing/suppress_pantheios_logging' unless $DEBUG
|
8
|
+
|
9
|
+
require 'razor_risk/cassini/authorisation'
|
10
|
+
|
11
|
+
require 'xqsr3/extensions/test/unit'
|
12
|
+
|
13
|
+
require 'test/unit'
|
14
|
+
|
15
|
+
require 'base64'
|
16
|
+
|
17
|
+
class Test_Authorisation_Util_with_Basic < Test::Unit::TestCase
|
18
|
+
|
19
|
+
include ::RazorRisk::Cassini::Authorisation::HeaderHelpers
|
20
|
+
|
21
|
+
def test_check_Basic_invalid
|
22
|
+
|
23
|
+
invalid_strings = [
|
24
|
+
|
25
|
+
'',
|
26
|
+
'Basic',
|
27
|
+
'Basic ',
|
28
|
+
'Basic **',
|
29
|
+
'Basic *:*',
|
30
|
+
]
|
31
|
+
|
32
|
+
invalid_strings.each do |s|
|
33
|
+
|
34
|
+
assert_nil credentials_from_Basic s, nil: true
|
35
|
+
|
36
|
+
cr = credentials_from_Basic s, nil: false
|
37
|
+
assert_not_nil cr
|
38
|
+
assert_nil cr[0]
|
39
|
+
assert_nil cr[1]
|
40
|
+
assert_nil cr[2]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_check_Basic_1
|
45
|
+
|
46
|
+
username = 'some-user'
|
47
|
+
password = 'e723f734374f3iuf3498f34'
|
48
|
+
|
49
|
+
auth_x = Basic_from_credentials username, password
|
50
|
+
|
51
|
+
auth = 'Basic ' + [ "#{username}:#{password}" ].pack('m').chomp
|
52
|
+
|
53
|
+
assert_equal auth_x, auth
|
54
|
+
|
55
|
+
credentials = credentials_from_Basic auth
|
56
|
+
|
57
|
+
assert_not_nil credentials
|
58
|
+
|
59
|
+
assert_equal username, credentials[0]
|
60
|
+
assert_equal password, credentials[1]
|
61
|
+
assert_nil credentials[2]
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_check_Basic_2
|
65
|
+
|
66
|
+
username = 'some-user'
|
67
|
+
password = 'e723f734374f3iuf3498f34'
|
68
|
+
domain = 'HERE'
|
69
|
+
|
70
|
+
auth_x = Basic_from_credentials username, password, domain
|
71
|
+
|
72
|
+
auth = 'Basic ' + [ "#{domain}\\#{username}:#{password}" ].pack('m').chomp
|
73
|
+
|
74
|
+
credentials = credentials_from_Basic auth
|
75
|
+
|
76
|
+
assert_equal auth_x, auth
|
77
|
+
|
78
|
+
assert_not_nil credentials
|
79
|
+
|
80
|
+
assert_equal username, credentials[0]
|
81
|
+
assert_equal password, credentials[1]
|
82
|
+
assert_equal domain, credentials[2]
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
class Test_Authorisation_Util_with_AuthorisationOnly < Test::Unit::TestCase
|
88
|
+
|
89
|
+
include ::RazorRisk::Cassini::Authorisation::HeaderHelpers
|
90
|
+
|
91
|
+
def test_check_AuthorisationOnly_invalid
|
92
|
+
|
93
|
+
invalid_strings = [
|
94
|
+
|
95
|
+
'',
|
96
|
+
'RazorRisk',
|
97
|
+
'RazorRisk.',
|
98
|
+
'RazorRisk.Cassini',
|
99
|
+
'RazorRisk.Cassini.',
|
100
|
+
'RazorRisk.Cassini.AuthorisationOnly',
|
101
|
+
'RazorRisk.Cassini.AuthorisationOnly',
|
102
|
+
'RazorRisk.Cassini.AuthorisationOnly ',
|
103
|
+
]
|
104
|
+
|
105
|
+
invalid_strings.each do |s|
|
106
|
+
|
107
|
+
assert_nil credentials_from_AuthorisationOnly s, nil: true
|
108
|
+
|
109
|
+
cr = credentials_from_AuthorisationOnly s, nil: false
|
110
|
+
assert_not_nil cr
|
111
|
+
assert_nil cr[0]
|
112
|
+
assert_nil cr[1]
|
113
|
+
assert_nil cr[2]
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def test_check_AuthorisationOnly_1
|
118
|
+
|
119
|
+
username = 'some-user'
|
120
|
+
|
121
|
+
auth_x = AuthorisationOnly_from_credentials username
|
122
|
+
|
123
|
+
auth = 'RazorRisk.Cassini.AuthorisationOnly ' + [ username ].pack('m')
|
124
|
+
|
125
|
+
assert_equal auth_x, auth
|
126
|
+
|
127
|
+
credentials = credentials_from_AuthorisationOnly auth
|
128
|
+
|
129
|
+
assert_not_nil credentials
|
130
|
+
|
131
|
+
assert_equal username, credentials[0]
|
132
|
+
assert_nil credentials[1]
|
133
|
+
assert_nil credentials[2]
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
|
138
|
+
class Test_Authorisation_Util_with_JWT < Test::Unit::TestCase
|
139
|
+
|
140
|
+
include ::RazorRisk::Cassini::Authorisation::HeaderHelpers
|
141
|
+
|
142
|
+
include ::Pantheios
|
143
|
+
|
144
|
+
def test_check_JWT
|
145
|
+
|
146
|
+
trace
|
147
|
+
|
148
|
+
session_id = 'e723f734374f3iuf3498f34'
|
149
|
+
user_id = 'some-user'
|
150
|
+
user_name = 'Some User'
|
151
|
+
|
152
|
+
jwt_algo = 'HS256'
|
153
|
+
jwt_secret = '0123456789'
|
154
|
+
|
155
|
+
jwt = JWT_from_credentials session_id, user_id, user_name, jwt_algo, jwt_secret
|
156
|
+
|
157
|
+
assert_kind_of ::String, jwt
|
158
|
+
|
159
|
+
auth = "Bearer #{jwt}"
|
160
|
+
|
161
|
+
credentials = credentials_from_JWT auth, jwt_secret
|
162
|
+
|
163
|
+
assert_equal session_id, credentials.shift
|
164
|
+
assert_equal user_id, credentials.shift
|
165
|
+
assert_equal user_name, credentials.shift
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
class Test_SecurityModelHelpers < Test::Unit::TestCase
|
170
|
+
|
171
|
+
include ::RazorRisk::Cassini::Authorisation::SecurityModelHelpers
|
172
|
+
|
173
|
+
include ::Pantheios
|
174
|
+
|
175
|
+
def test_auth_only
|
176
|
+
|
177
|
+
username = 'some-user'
|
178
|
+
password = 'e723f734374f3iuf3498f34'
|
179
|
+
domain = 'HERE'
|
180
|
+
scheme = :authorisation_only
|
181
|
+
cred = [
|
182
|
+
username,
|
183
|
+
]
|
184
|
+
opt = {}
|
185
|
+
|
186
|
+
cr = razor_requester_credentials_options scheme, cred, **opt
|
187
|
+
|
188
|
+
assert_kind_of Hash, cr
|
189
|
+
assert_equal username, cr[:impersonatee]
|
190
|
+
end
|
191
|
+
|
192
|
+
def test_basic
|
193
|
+
|
194
|
+
username = 'some-user'
|
195
|
+
password = 'e723f734374f3iuf3498f34'
|
196
|
+
domain = 'HERE'
|
197
|
+
scheme = :basic
|
198
|
+
cred = [
|
199
|
+
username,
|
200
|
+
password,
|
201
|
+
domain,
|
202
|
+
]
|
203
|
+
opt = {}
|
204
|
+
|
205
|
+
cr = razor_requester_credentials_options scheme, cred, **opt
|
206
|
+
|
207
|
+
assert_kind_of Hash, cr
|
208
|
+
assert_equal username, cr[:username]
|
209
|
+
assert_equal password, cr[:password]
|
210
|
+
assert_equal domain, cr[:domain]
|
211
|
+
end
|
212
|
+
|
213
|
+
def test_jwt
|
214
|
+
|
215
|
+
session_id = 'e723f734374f3iuf3498f34'
|
216
|
+
scheme = :jwt
|
217
|
+
cred = [
|
218
|
+
session_id,
|
219
|
+
]
|
220
|
+
opt = {}
|
221
|
+
|
222
|
+
cr = razor_requester_credentials_options scheme, cred, **opt
|
223
|
+
|
224
|
+
assert_kind_of Hash, cr
|
225
|
+
assert_equal session_id, cr[:session_id]
|
226
|
+
end
|
227
|
+
|
228
|
+
def test_basic_with_auth_test_mode
|
229
|
+
|
230
|
+
username = 'some-user'
|
231
|
+
password = 'e723f734374f3iuf3498f34'
|
232
|
+
domain = 'HERE'
|
233
|
+
scheme = :basic
|
234
|
+
cred = [
|
235
|
+
username,
|
236
|
+
password,
|
237
|
+
domain,
|
238
|
+
]
|
239
|
+
opt = {
|
240
|
+
auth_test_mode: true
|
241
|
+
}
|
242
|
+
|
243
|
+
cr = razor_requester_credentials_options scheme, cred, **opt
|
244
|
+
|
245
|
+
assert_kind_of Hash, cr
|
246
|
+
assert_equal username, cr[:impersonatee]
|
247
|
+
end
|
248
|
+
|
249
|
+
def test_jwt_with_auth_test_mode
|
250
|
+
|
251
|
+
session_id = 'e723f734374f3iuf3498f34'
|
252
|
+
scheme = :jwt
|
253
|
+
cred = [
|
254
|
+
session_id,
|
255
|
+
]
|
256
|
+
opt = {
|
257
|
+
auth_test_mode: true
|
258
|
+
}
|
259
|
+
|
260
|
+
cr = razor_requester_credentials_options scheme, cred, **opt
|
261
|
+
|
262
|
+
assert_kind_of Hash, cr
|
263
|
+
assert_equal session_id, cr[:session_id]
|
264
|
+
end
|
265
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
|
3
|
+
$:.unshift File.join(File.dirname(__FILE__), '../../lib')
|
4
|
+
|
5
|
+
require 'simplecov'
|
6
|
+
|
7
|
+
require 'razor_risk/cassini/testing/suppress_pantheios_logging' unless $DEBUG
|
8
|
+
|
9
|
+
require 'razor_risk/cassini/util/secrets_util'
|
10
|
+
|
11
|
+
require 'xqsr3/extensions/test/unit'
|
12
|
+
|
13
|
+
require 'test/unit'
|
14
|
+
|
15
|
+
require 'tempfile'
|
16
|
+
require 'yaml'
|
17
|
+
|
18
|
+
class Test_SecretsUtil_load_secrets < Test::Unit::TestCase
|
19
|
+
|
20
|
+
include ::RazorRisk::Cassini::Util::SecretsUtil
|
21
|
+
|
22
|
+
def test_load_from_YAML_string
|
23
|
+
|
24
|
+
s = <<END_OF_yaml_string
|
25
|
+
|
26
|
+
secrets:
|
27
|
+
all:
|
28
|
+
SHA256: abcdefghijkl
|
29
|
+
HS256: ABCDEFGHIJKL
|
30
|
+
special:
|
31
|
+
SHA256: mnopqrstuvwxyz
|
32
|
+
HS256: MNOPQRSTUVWXYZ
|
33
|
+
END_OF_yaml_string
|
34
|
+
|
35
|
+
yaml = YAML.load s
|
36
|
+
|
37
|
+
assert_nil get_secret_from_hash(yaml, nil, 'xyz')
|
38
|
+
assert_equal 'abcdefghijkl', get_secret_from_hash(yaml, nil, 'SHA256')
|
39
|
+
|
40
|
+
assert_equal 'abcdefghijkl', get_secret_from_hash(yaml, 'all', 'SHA256')
|
41
|
+
assert_nil get_secret_from_hash(yaml, 'all', 'abc')
|
42
|
+
|
43
|
+
assert_equal 'abcdefghijkl', get_secret_from_hash(yaml, 'unknown-category', 'SHA256')
|
44
|
+
assert_nil get_secret_from_hash(yaml, 'unknown-category', '')
|
45
|
+
|
46
|
+
assert_equal 'mnopqrstuvwxyz', get_secret_from_hash(yaml, 'special', 'SHA256')
|
47
|
+
assert_nil get_secret_from_hash(yaml, 'special', '*')
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_load_from_YAML_file
|
51
|
+
|
52
|
+
s = <<END_OF_yaml_string
|
53
|
+
|
54
|
+
secrets:
|
55
|
+
all:
|
56
|
+
SHA256: abcdefghijkl
|
57
|
+
HS256: ABCDEFGHIJKL
|
58
|
+
special:
|
59
|
+
SHA256: mnopqrstuvwxyz
|
60
|
+
HS256: MNOPQRSTUVWXYZ
|
61
|
+
END_OF_yaml_string
|
62
|
+
|
63
|
+
|
64
|
+
f = nil
|
65
|
+
|
66
|
+
begin
|
67
|
+
|
68
|
+
f = Tempfile.new('secrets-test-file')
|
69
|
+
|
70
|
+
f.write s
|
71
|
+
f.close
|
72
|
+
|
73
|
+
algorithms = [
|
74
|
+
|
75
|
+
'HS256',
|
76
|
+
'SHA256',
|
77
|
+
'blahblah',
|
78
|
+
]
|
79
|
+
|
80
|
+
secrets = load_secrets f.path, *algorithms
|
81
|
+
|
82
|
+
assert_not_nil secrets
|
83
|
+
assert_not_empty secrets
|
84
|
+
|
85
|
+
assert_equal 'abcdefghijkl', secrets['sha256']
|
86
|
+
assert_equal 'ABCDEFGHIJKL', secrets['hs256']
|
87
|
+
assert_nil secrets['xxx']
|
88
|
+
ensure
|
89
|
+
|
90
|
+
f.unlink unless f.nil?
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
|