webrick 1.3.1 → 1.6.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +22 -0
- data/README.md +63 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/webrick.rb +7 -7
- data/lib/webrick/accesslog.rb +12 -6
- data/lib/webrick/cgi.rb +58 -5
- data/lib/webrick/compat.rb +2 -1
- data/lib/webrick/config.rb +47 -10
- data/lib/webrick/cookie.rb +69 -7
- data/lib/webrick/htmlutils.rb +4 -2
- data/lib/webrick/httpauth.rb +6 -5
- data/lib/webrick/httpauth/authenticator.rb +13 -8
- data/lib/webrick/httpauth/basicauth.rb +16 -8
- data/lib/webrick/httpauth/digestauth.rb +35 -32
- data/lib/webrick/httpauth/htdigest.rb +12 -8
- data/lib/webrick/httpauth/htgroup.rb +10 -6
- data/lib/webrick/httpauth/htpasswd.rb +46 -9
- data/lib/webrick/httpauth/userdb.rb +1 -0
- data/lib/webrick/httpproxy.rb +93 -48
- data/lib/webrick/httprequest.rb +201 -31
- data/lib/webrick/httpresponse.rb +235 -70
- data/lib/webrick/https.rb +90 -2
- data/lib/webrick/httpserver.rb +45 -15
- data/lib/webrick/httpservlet.rb +6 -5
- data/lib/webrick/httpservlet/abstract.rb +5 -6
- data/lib/webrick/httpservlet/cgi_runner.rb +3 -2
- data/lib/webrick/httpservlet/cgihandler.rb +29 -11
- data/lib/webrick/httpservlet/erbhandler.rb +4 -3
- data/lib/webrick/httpservlet/filehandler.rb +136 -65
- data/lib/webrick/httpservlet/prochandler.rb +15 -1
- data/lib/webrick/httpstatus.rb +24 -14
- data/lib/webrick/httputils.rb +134 -17
- data/lib/webrick/httpversion.rb +28 -1
- data/lib/webrick/log.rb +25 -5
- data/lib/webrick/server.rb +234 -74
- data/lib/webrick/ssl.rb +100 -12
- data/lib/webrick/utils.rb +98 -69
- data/lib/webrick/version.rb +6 -1
- data/webrick.gemspec +76 -0
- metadata +73 -72
- data/README.txt +0 -21
- data/sample/webrick/demo-app.rb +0 -66
- data/sample/webrick/demo-multipart.cgi +0 -12
- data/sample/webrick/demo-servlet.rb +0 -6
- data/sample/webrick/demo-urlencoded.cgi +0 -12
- data/sample/webrick/hello.cgi +0 -11
- data/sample/webrick/hello.rb +0 -8
- data/sample/webrick/httpd.rb +0 -23
- data/sample/webrick/httpproxy.rb +0 -25
- data/sample/webrick/httpsd.rb +0 -33
- data/test/openssl/utils.rb +0 -313
- data/test/ruby/envutil.rb +0 -208
- data/test/webrick/test_cgi.rb +0 -134
- data/test/webrick/test_cookie.rb +0 -131
- data/test/webrick/test_filehandler.rb +0 -285
- data/test/webrick/test_httpauth.rb +0 -167
- data/test/webrick/test_httpproxy.rb +0 -282
- data/test/webrick/test_httprequest.rb +0 -411
- data/test/webrick/test_httpresponse.rb +0 -49
- data/test/webrick/test_httpserver.rb +0 -305
- data/test/webrick/test_httputils.rb +0 -96
- data/test/webrick/test_httpversion.rb +0 -40
- data/test/webrick/test_server.rb +0 -67
- data/test/webrick/test_utils.rb +0 -64
- data/test/webrick/utils.rb +0 -58
- data/test/webrick/webrick.cgi +0 -36
- data/test/webrick/webrick_long_filename.cgi +0 -36
data/lib/webrick/cookie.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: false
|
1
2
|
#
|
2
3
|
# cookie.rb -- Cookie class
|
3
4
|
#
|
@@ -9,17 +10,59 @@
|
|
9
10
|
# $IPR: cookie.rb,v 1.16 2002/09/21 12:23:35 gotoyuzo Exp $
|
10
11
|
|
11
12
|
require 'time'
|
12
|
-
|
13
|
+
require_relative 'httputils'
|
13
14
|
|
14
15
|
module WEBrick
|
16
|
+
|
17
|
+
##
|
18
|
+
# Processes HTTP cookies
|
19
|
+
|
15
20
|
class Cookie
|
16
21
|
|
22
|
+
##
|
23
|
+
# The cookie name
|
24
|
+
|
17
25
|
attr_reader :name
|
18
|
-
|
19
|
-
|
20
|
-
|
26
|
+
|
27
|
+
##
|
28
|
+
# The cookie value
|
29
|
+
|
30
|
+
attr_accessor :value
|
31
|
+
|
32
|
+
##
|
33
|
+
# The cookie version
|
34
|
+
|
35
|
+
attr_accessor :version
|
36
|
+
|
37
|
+
##
|
38
|
+
# The cookie domain
|
39
|
+
attr_accessor :domain
|
40
|
+
|
41
|
+
##
|
42
|
+
# The cookie path
|
43
|
+
|
44
|
+
attr_accessor :path
|
45
|
+
|
46
|
+
##
|
47
|
+
# Is this a secure cookie?
|
48
|
+
|
49
|
+
attr_accessor :secure
|
50
|
+
|
51
|
+
##
|
52
|
+
# The cookie comment
|
53
|
+
|
54
|
+
attr_accessor :comment
|
55
|
+
|
56
|
+
##
|
57
|
+
# The maximum age of the cookie
|
58
|
+
|
59
|
+
attr_accessor :max_age
|
60
|
+
|
21
61
|
#attr_accessor :comment_url, :discard, :port
|
22
62
|
|
63
|
+
##
|
64
|
+
# Creates a new cookie with the given +name+ and +value+
|
65
|
+
|
23
66
|
def initialize(name, value)
|
24
67
|
@name = name
|
25
68
|
@value = value
|
@@ -29,14 +72,25 @@ module WEBrick
|
|
29
72
|
@expires = @comment_url = @discard = @port = nil
|
30
73
|
end
|
31
74
|
|
75
|
+
##
|
76
|
+
# Sets the cookie expiration to the time +t+. The expiration time may be
|
77
|
+
# a false value to disable expiration or a Time or HTTP format time string
|
78
|
+
# to set the expiration date.
|
79
|
+
|
32
80
|
def expires=(t)
|
33
81
|
@expires = t && (t.is_a?(Time) ? t.httpdate : t.to_s)
|
34
82
|
end
|
35
83
|
|
84
|
+
##
|
85
|
+
# Retrieves the expiration time as a Time
|
86
|
+
|
36
87
|
def expires
|
37
88
|
@expires && Time.parse(@expires)
|
38
89
|
end
|
39
90
|
|
91
|
+
##
|
92
|
+
# The cookie string suitable for use in an HTTP header
|
93
|
+
|
40
94
|
def to_s
|
41
95
|
ret = ""
|
42
96
|
ret << @name << "=" << @value
|
@@ -50,14 +104,16 @@ module WEBrick
|
|
50
104
|
ret
|
51
105
|
end
|
52
106
|
|
53
|
-
|
54
|
-
#
|
107
|
+
##
|
108
|
+
# Parses a Cookie field sent from the user-agent. Returns an array of
|
109
|
+
# cookies.
|
110
|
+
|
55
111
|
def self.parse(str)
|
56
112
|
if str
|
57
113
|
ret = []
|
58
114
|
cookie = nil
|
59
115
|
ver = 0
|
60
|
-
str.split(
|
116
|
+
str.split(/;\s+/).each{|x|
|
61
117
|
key, val = x.split(/=/,2)
|
62
118
|
val = val ? HTTPUtils::dequote(val) : ""
|
63
119
|
case key
|
@@ -76,6 +132,9 @@ module WEBrick
|
|
76
132
|
end
|
77
133
|
end
|
78
134
|
|
135
|
+
##
|
136
|
+
# Parses the cookie in +str+
|
137
|
+
|
79
138
|
def self.parse_set_cookie(str)
|
80
139
|
cookie_elem = str.split(/;/)
|
81
140
|
first_elem = cookie_elem.shift
|
@@ -101,6 +160,9 @@ module WEBrick
|
|
101
160
|
return cookie
|
102
161
|
end
|
103
162
|
|
163
|
+
##
|
164
|
+
# Parses the cookies in +str+
|
165
|
+
|
104
166
|
def self.parse_set_cookies(str)
|
105
167
|
return str.split(/,(?=[^;,]*=)|,$/).collect{|c|
|
106
168
|
parse_set_cookie(c)
|
data/lib/webrick/htmlutils.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: false
|
1
2
|
#--
|
2
3
|
# htmlutils.rb -- HTMLUtils Module
|
3
4
|
#
|
@@ -15,12 +16,13 @@ module WEBrick
|
|
15
16
|
# Escapes &, ", > and < in +string+
|
16
17
|
|
17
18
|
def escape(string)
|
18
|
-
|
19
|
+
return "" unless string
|
20
|
+
str = string.b
|
19
21
|
str.gsub!(/&/n, '&')
|
20
22
|
str.gsub!(/\"/n, '"')
|
21
23
|
str.gsub!(/>/n, '>')
|
22
24
|
str.gsub!(/</n, '<')
|
23
|
-
str
|
25
|
+
str.force_encoding(string.encoding)
|
24
26
|
end
|
25
27
|
module_function :escape
|
26
28
|
|
data/lib/webrick/httpauth.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: false
|
1
2
|
#
|
2
3
|
# httpauth.rb -- HTTP access authentication
|
3
4
|
#
|
@@ -8,11 +9,11 @@
|
|
8
9
|
#
|
9
10
|
# $IPR: httpauth.rb,v 1.14 2003/07/22 19:20:42 gotoyuzo Exp $
|
10
11
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
12
|
+
require_relative 'httpauth/basicauth'
|
13
|
+
require_relative 'httpauth/digestauth'
|
14
|
+
require_relative 'httpauth/htpasswd'
|
15
|
+
require_relative 'httpauth/htdigest'
|
16
|
+
require_relative 'httpauth/htgroup'
|
16
17
|
|
17
18
|
module WEBrick
|
18
19
|
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: false
|
1
2
|
#--
|
2
3
|
# httpauth/authenticator.rb -- Authenticator mix-in module.
|
3
4
|
#
|
@@ -16,10 +17,10 @@ module WEBrick
|
|
16
17
|
|
17
18
|
module Authenticator
|
18
19
|
|
19
|
-
RequestField = "Authorization"
|
20
|
-
ResponseField = "WWW-Authenticate"
|
21
|
-
ResponseInfoField = "Authentication-Info"
|
22
|
-
AuthException = HTTPStatus::Unauthorized
|
20
|
+
RequestField = "Authorization" # :nodoc:
|
21
|
+
ResponseField = "WWW-Authenticate" # :nodoc:
|
22
|
+
ResponseInfoField = "Authentication-Info" # :nodoc:
|
23
|
+
AuthException = HTTPStatus::Unauthorized # :nodoc:
|
23
24
|
|
24
25
|
##
|
25
26
|
# Method of authentication, must be overridden by the including class
|
@@ -43,6 +44,8 @@ module WEBrick
|
|
43
44
|
|
44
45
|
private
|
45
46
|
|
47
|
+
# :stopdoc:
|
48
|
+
|
46
49
|
##
|
47
50
|
# Initializes the authenticator from +config+
|
48
51
|
|
@@ -96,6 +99,8 @@ module WEBrick
|
|
96
99
|
log(:info, fmt, *args)
|
97
100
|
end
|
98
101
|
end
|
102
|
+
|
103
|
+
# :startdoc:
|
99
104
|
end
|
100
105
|
|
101
106
|
##
|
@@ -103,10 +108,10 @@ module WEBrick
|
|
103
108
|
# authentication schemes for proxies.
|
104
109
|
|
105
110
|
module ProxyAuthenticator
|
106
|
-
RequestField = "Proxy-Authorization"
|
107
|
-
ResponseField = "Proxy-Authenticate"
|
108
|
-
InfoField = "Proxy-Authentication-Info"
|
109
|
-
AuthException = HTTPStatus::ProxyAuthenticationRequired
|
111
|
+
RequestField = "Proxy-Authorization" # :nodoc:
|
112
|
+
ResponseField = "Proxy-Authenticate" # :nodoc:
|
113
|
+
InfoField = "Proxy-Authentication-Info" # :nodoc:
|
114
|
+
AuthException = HTTPStatus::ProxyAuthenticationRequired # :nodoc:
|
110
115
|
end
|
111
116
|
end
|
112
117
|
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: false
|
1
2
|
#
|
2
3
|
# httpauth/basicauth.rb -- HTTP basic access authentication
|
3
4
|
#
|
@@ -7,9 +8,9 @@
|
|
7
8
|
#
|
8
9
|
# $IPR: basicauth.rb,v 1.5 2003/02/20 07:15:47 gotoyuzo Exp $
|
9
10
|
|
10
|
-
|
11
|
-
|
12
|
-
|
11
|
+
require_relative '../config'
|
12
|
+
require_relative '../httpstatus'
|
13
|
+
require_relative 'authenticator'
|
13
14
|
|
14
15
|
module WEBrick
|
15
16
|
module HTTPAuth
|
@@ -23,7 +24,7 @@ module WEBrick
|
|
23
24
|
#
|
24
25
|
# config = { :Realm => 'BasicAuth example realm' }
|
25
26
|
#
|
26
|
-
# htpasswd = WEBrick::HTTPAuth::Htpasswd.new 'my_password_file'
|
27
|
+
# htpasswd = WEBrick::HTTPAuth::Htpasswd.new 'my_password_file', password_hash: :bcrypt
|
27
28
|
# htpasswd.set_passwd config[:Realm], 'username', 'password'
|
28
29
|
# htpasswd.flush
|
29
30
|
#
|
@@ -34,7 +35,7 @@ module WEBrick
|
|
34
35
|
class BasicAuth
|
35
36
|
include Authenticator
|
36
37
|
|
37
|
-
AuthScheme = "Basic"
|
38
|
+
AuthScheme = "Basic" # :nodoc:
|
38
39
|
|
39
40
|
##
|
40
41
|
# Used by UserDB to create a basic password entry
|
@@ -80,7 +81,15 @@ module WEBrick
|
|
80
81
|
error("%s: the user is not allowed.", userid)
|
81
82
|
challenge(req, res)
|
82
83
|
end
|
83
|
-
|
84
|
+
|
85
|
+
case encpass
|
86
|
+
when /\A\$2[aby]\$/
|
87
|
+
password_matches = BCrypt::Password.new(encpass.sub(/\A\$2[aby]\$/, '$2a$')) == password
|
88
|
+
else
|
89
|
+
password_matches = password.crypt(encpass) == encpass
|
90
|
+
end
|
91
|
+
|
92
|
+
unless password_matches
|
84
93
|
error("%s: password unmatch.", userid)
|
85
94
|
challenge(req, res)
|
86
95
|
end
|
@@ -89,8 +98,7 @@ module WEBrick
|
|
89
98
|
end
|
90
99
|
|
91
100
|
##
|
92
|
-
# Returns a challenge response which asks for
|
93
|
-
# information
|
101
|
+
# Returns a challenge response which asks for authentication information
|
94
102
|
|
95
103
|
def challenge(req, res)
|
96
104
|
res[@response_field] = "#{@auth_scheme} realm=\"#{@realm}\""
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: false
|
1
2
|
#
|
2
3
|
# httpauth/digestauth.rb -- HTTP digest access authentication
|
3
4
|
#
|
@@ -11,9 +12,9 @@
|
|
11
12
|
#
|
12
13
|
# $IPR: digestauth.rb,v 1.5 2003/02/20 07:15:47 gotoyuzo Exp $
|
13
14
|
|
14
|
-
|
15
|
-
|
16
|
-
|
15
|
+
require_relative '../config'
|
16
|
+
require_relative '../httpstatus'
|
17
|
+
require_relative 'authenticator'
|
17
18
|
require 'digest/md5'
|
18
19
|
require 'digest/sha1'
|
19
20
|
|
@@ -45,9 +46,22 @@ module WEBrick
|
|
45
46
|
class DigestAuth
|
46
47
|
include Authenticator
|
47
48
|
|
48
|
-
AuthScheme = "Digest"
|
49
|
-
|
50
|
-
|
49
|
+
AuthScheme = "Digest" # :nodoc:
|
50
|
+
|
51
|
+
##
|
52
|
+
# Struct containing the opaque portion of the digest authentication
|
53
|
+
|
54
|
+
OpaqueInfo = Struct.new(:time, :nonce, :nc) # :nodoc:
|
55
|
+
|
56
|
+
##
|
57
|
+
# Digest authentication algorithm
|
58
|
+
|
59
|
+
attr_reader :algorithm
|
60
|
+
|
61
|
+
##
|
62
|
+
# Quality of protection. RFC 2617 defines "auth" and "auth-int"
|
63
|
+
|
64
|
+
attr_reader :qop
|
51
65
|
|
52
66
|
##
|
53
67
|
# Used by UserDB to create a digest password entry
|
@@ -97,7 +111,7 @@ module WEBrick
|
|
97
111
|
@instance_key = hexdigest(self.__id__, Time.now.to_i, Process.pid)
|
98
112
|
@opaques = {}
|
99
113
|
@last_nonce_expire = Time.now
|
100
|
-
@mutex = Mutex.new
|
114
|
+
@mutex = Thread::Mutex.new
|
101
115
|
end
|
102
116
|
|
103
117
|
##
|
@@ -115,8 +129,7 @@ module WEBrick
|
|
115
129
|
end
|
116
130
|
|
117
131
|
##
|
118
|
-
# Returns a challenge response which asks for
|
119
|
-
# information
|
132
|
+
# Returns a challenge response which asks for authentication information
|
120
133
|
|
121
134
|
def challenge(req, res, stale=false)
|
122
135
|
nonce = generate_next_nonce(req)
|
@@ -142,6 +155,8 @@ module WEBrick
|
|
142
155
|
|
143
156
|
private
|
144
157
|
|
158
|
+
# :stopdoc:
|
159
|
+
|
145
160
|
MustParams = ['username','realm','nonce','uri','response']
|
146
161
|
MustParamsAuth = ['cnonce','nc']
|
147
162
|
|
@@ -189,7 +204,7 @@ module WEBrick
|
|
189
204
|
|
190
205
|
password = @userdb.get_passwd(@realm, auth_req['username'], @reload_db)
|
191
206
|
unless password
|
192
|
-
error('%s: the user is not
|
207
|
+
error('%s: the user is not allowed.', auth_req['username'])
|
193
208
|
return false
|
194
209
|
end
|
195
210
|
|
@@ -220,9 +235,11 @@ module WEBrick
|
|
220
235
|
ha2 = hexdigest(req.request_method, auth_req['uri'])
|
221
236
|
ha2_res = hexdigest("", auth_req['uri'])
|
222
237
|
elsif auth_req['qop'] == "auth-int"
|
223
|
-
|
224
|
-
|
225
|
-
|
238
|
+
body_digest = @h.new
|
239
|
+
req.body { |chunk| body_digest.update(chunk) }
|
240
|
+
body_digest = body_digest.hexdigest
|
241
|
+
ha2 = hexdigest(req.request_method, auth_req['uri'], body_digest)
|
242
|
+
ha2_res = hexdigest("", auth_req['uri'], body_digest)
|
226
243
|
end
|
227
244
|
|
228
245
|
if auth_req['qop'] == "auth" || auth_req['qop'] == "auth-int"
|
@@ -273,23 +290,8 @@ module WEBrick
|
|
273
290
|
|
274
291
|
def split_param_value(string)
|
275
292
|
ret = {}
|
276
|
-
|
277
|
-
|
278
|
-
when /^\s*([\w\-\.\*\%\!]+)=\s*\"((\\.|[^\"])*)\"\s*,?/
|
279
|
-
key = $1
|
280
|
-
matched = $2
|
281
|
-
string = $'
|
282
|
-
ret[key] = matched.gsub(/\\(.)/, "\\1")
|
283
|
-
when /^\s*([\w\-\.\*\%\!]+)=\s*([^,\"]*),?/
|
284
|
-
key = $1
|
285
|
-
matched = $2
|
286
|
-
string = $'
|
287
|
-
ret[key] = matched.clone
|
288
|
-
when /^s*^,/
|
289
|
-
string = $'
|
290
|
-
else
|
291
|
-
break
|
292
|
-
end
|
293
|
+
string.scan(/\G\s*([\w\-.*%!]+)=\s*(?:\"((?>\\.|[^\"])*)\"|([^,\"]*))\s*,?/) do
|
294
|
+
ret[$1] = $3 || $2.gsub(/\\(.)/, "\\1")
|
293
295
|
end
|
294
296
|
ret
|
295
297
|
end
|
@@ -297,7 +299,7 @@ module WEBrick
|
|
297
299
|
def generate_next_nonce(req)
|
298
300
|
now = "%012d" % req.request_time.to_i
|
299
301
|
pk = hexdigest(now, @instance_key)[0,32]
|
300
|
-
nonce = [now + ":" + pk].pack("
|
302
|
+
nonce = [now + ":" + pk].pack("m0") # it has 60 length of chars.
|
301
303
|
nonce
|
302
304
|
end
|
303
305
|
|
@@ -375,6 +377,7 @@ module WEBrick
|
|
375
377
|
@h.hexdigest(args.join(":"))
|
376
378
|
end
|
377
379
|
|
380
|
+
# :startdoc:
|
378
381
|
end
|
379
382
|
|
380
383
|
##
|
@@ -384,7 +387,7 @@ module WEBrick
|
|
384
387
|
include ProxyAuthenticator
|
385
388
|
|
386
389
|
private
|
387
|
-
def check_uri(req, auth_req)
|
390
|
+
def check_uri(req, auth_req) # :nodoc:
|
388
391
|
return true
|
389
392
|
end
|
390
393
|
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: false
|
1
2
|
#
|
2
3
|
# httpauth/htdigest.rb -- Apache compatible htdigest file
|
3
4
|
#
|
@@ -7,8 +8,8 @@
|
|
7
8
|
#
|
8
9
|
# $IPR: htdigest.rb,v 1.4 2003/07/22 19:20:45 gotoyuzo Exp $
|
9
10
|
|
10
|
-
|
11
|
-
|
11
|
+
require_relative 'userdb'
|
12
|
+
require_relative 'digestauth'
|
12
13
|
require 'tempfile'
|
13
14
|
|
14
15
|
module WEBrick
|
@@ -37,9 +38,9 @@ module WEBrick
|
|
37
38
|
@path = path
|
38
39
|
@mtime = Time.at(0)
|
39
40
|
@digest = Hash.new
|
40
|
-
@mutex = Mutex::new
|
41
|
+
@mutex = Thread::Mutex::new
|
41
42
|
@auth_type = DigestAuth
|
42
|
-
open(@path,"a").close unless File
|
43
|
+
File.open(@path,"a").close unless File.exist?(@path)
|
43
44
|
reload
|
44
45
|
end
|
45
46
|
|
@@ -50,7 +51,7 @@ module WEBrick
|
|
50
51
|
mtime = File::mtime(@path)
|
51
52
|
if mtime > @mtime
|
52
53
|
@digest.clear
|
53
|
-
open(@path){|io|
|
54
|
+
File.open(@path){|io|
|
54
55
|
while line = io.gets
|
55
56
|
line.chomp!
|
56
57
|
user, realm, pass = line.split(/:/, 3)
|
@@ -70,13 +71,16 @@ module WEBrick
|
|
70
71
|
|
71
72
|
def flush(output=nil)
|
72
73
|
output ||= @path
|
73
|
-
tmp = Tempfile.
|
74
|
+
tmp = Tempfile.create("htpasswd", File::dirname(output))
|
75
|
+
renamed = false
|
74
76
|
begin
|
75
77
|
each{|item| tmp.puts(item.join(":")) }
|
76
78
|
tmp.close
|
77
79
|
File::rename(tmp.path, output)
|
78
|
-
|
79
|
-
|
80
|
+
renamed = true
|
81
|
+
ensure
|
82
|
+
tmp.close
|
83
|
+
File.unlink(tmp.path) if !renamed
|
80
84
|
end
|
81
85
|
end
|
82
86
|
|