rubysl-webrick 1.0.0
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/.gitignore +17 -0
- data/.travis.yml +8 -0
- data/Gemfile +4 -0
- data/LICENSE +25 -0
- data/README.md +29 -0
- data/Rakefile +1 -0
- data/lib/rubysl/webrick.rb +2 -0
- data/lib/rubysl/webrick/version.rb +5 -0
- data/lib/rubysl/webrick/webrick.rb +29 -0
- data/lib/webrick.rb +1 -0
- data/lib/webrick/accesslog.rb +67 -0
- data/lib/webrick/cgi.rb +257 -0
- data/lib/webrick/compat.rb +15 -0
- data/lib/webrick/config.rb +97 -0
- data/lib/webrick/cookie.rb +110 -0
- data/lib/webrick/htmlutils.rb +25 -0
- data/lib/webrick/httpauth.rb +45 -0
- data/lib/webrick/httpauth/authenticator.rb +79 -0
- data/lib/webrick/httpauth/basicauth.rb +65 -0
- data/lib/webrick/httpauth/digestauth.rb +343 -0
- data/lib/webrick/httpauth/htdigest.rb +91 -0
- data/lib/webrick/httpauth/htgroup.rb +61 -0
- data/lib/webrick/httpauth/htpasswd.rb +83 -0
- data/lib/webrick/httpauth/userdb.rb +29 -0
- data/lib/webrick/httpproxy.rb +254 -0
- data/lib/webrick/httprequest.rb +365 -0
- data/lib/webrick/httpresponse.rb +327 -0
- data/lib/webrick/https.rb +63 -0
- data/lib/webrick/httpserver.rb +210 -0
- data/lib/webrick/httpservlet.rb +22 -0
- data/lib/webrick/httpservlet/abstract.rb +71 -0
- data/lib/webrick/httpservlet/cgi_runner.rb +45 -0
- data/lib/webrick/httpservlet/cgihandler.rb +104 -0
- data/lib/webrick/httpservlet/erbhandler.rb +54 -0
- data/lib/webrick/httpservlet/filehandler.rb +398 -0
- data/lib/webrick/httpservlet/prochandler.rb +33 -0
- data/lib/webrick/httpstatus.rb +126 -0
- data/lib/webrick/httputils.rb +391 -0
- data/lib/webrick/httpversion.rb +49 -0
- data/lib/webrick/log.rb +88 -0
- data/lib/webrick/server.rb +200 -0
- data/lib/webrick/ssl.rb +126 -0
- data/lib/webrick/utils.rb +100 -0
- data/lib/webrick/version.rb +13 -0
- data/rubysl-webrick.gemspec +23 -0
- metadata +145 -0
@@ -0,0 +1,33 @@
|
|
1
|
+
#
|
2
|
+
# prochandler.rb -- ProcHandler Class
|
3
|
+
#
|
4
|
+
# Author: IPR -- Internet Programming with Ruby -- writers
|
5
|
+
# Copyright (c) 2001 TAKAHASHI Masayoshi, GOTOU Yuuzou
|
6
|
+
# Copyright (c) 2002 Internet Programming with Ruby writers. All rights
|
7
|
+
# reserved.
|
8
|
+
#
|
9
|
+
# $IPR: prochandler.rb,v 1.7 2002/09/21 12:23:42 gotoyuzo Exp $
|
10
|
+
|
11
|
+
require 'webrick/httpservlet/abstract.rb'
|
12
|
+
|
13
|
+
module WEBrick
|
14
|
+
module HTTPServlet
|
15
|
+
|
16
|
+
class ProcHandler < AbstractServlet
|
17
|
+
def get_instance(server, *options)
|
18
|
+
self
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize(proc)
|
22
|
+
@proc = proc
|
23
|
+
end
|
24
|
+
|
25
|
+
def do_GET(request, response)
|
26
|
+
@proc.call(request, response)
|
27
|
+
end
|
28
|
+
|
29
|
+
alias do_POST do_GET
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
#
|
2
|
+
# httpstatus.rb -- HTTPStatus Class
|
3
|
+
#
|
4
|
+
# Author: IPR -- Internet Programming with Ruby -- writers
|
5
|
+
# Copyright (c) 2000, 2001 TAKAHASHI Masayoshi, GOTOU Yuuzou
|
6
|
+
# Copyright (c) 2002 Internet Programming with Ruby writers. All rights
|
7
|
+
# reserved.
|
8
|
+
#
|
9
|
+
# $IPR: httpstatus.rb,v 1.11 2003/03/24 20:18:55 gotoyuzo Exp $
|
10
|
+
|
11
|
+
module WEBrick
|
12
|
+
|
13
|
+
module HTTPStatus
|
14
|
+
|
15
|
+
class Status < StandardError; end
|
16
|
+
class Info < Status; end
|
17
|
+
class Success < Status; end
|
18
|
+
class Redirect < Status; end
|
19
|
+
class Error < Status; end
|
20
|
+
class ClientError < Error; end
|
21
|
+
class ServerError < Error; end
|
22
|
+
|
23
|
+
class EOFError < StandardError; end
|
24
|
+
|
25
|
+
StatusMessage = {
|
26
|
+
100 => 'Continue',
|
27
|
+
101 => 'Switching Protocols',
|
28
|
+
200 => 'OK',
|
29
|
+
201 => 'Created',
|
30
|
+
202 => 'Accepted',
|
31
|
+
203 => 'Non-Authoritative Information',
|
32
|
+
204 => 'No Content',
|
33
|
+
205 => 'Reset Content',
|
34
|
+
206 => 'Partial Content',
|
35
|
+
300 => 'Multiple Choices',
|
36
|
+
301 => 'Moved Permanently',
|
37
|
+
302 => 'Found',
|
38
|
+
303 => 'See Other',
|
39
|
+
304 => 'Not Modified',
|
40
|
+
305 => 'Use Proxy',
|
41
|
+
307 => 'Temporary Redirect',
|
42
|
+
400 => 'Bad Request',
|
43
|
+
401 => 'Unauthorized',
|
44
|
+
402 => 'Payment Required',
|
45
|
+
403 => 'Forbidden',
|
46
|
+
404 => 'Not Found',
|
47
|
+
405 => 'Method Not Allowed',
|
48
|
+
406 => 'Not Acceptable',
|
49
|
+
407 => 'Proxy Authentication Required',
|
50
|
+
408 => 'Request Timeout',
|
51
|
+
409 => 'Conflict',
|
52
|
+
410 => 'Gone',
|
53
|
+
411 => 'Length Required',
|
54
|
+
412 => 'Precondition Failed',
|
55
|
+
413 => 'Request Entity Too Large',
|
56
|
+
414 => 'Request-URI Too Large',
|
57
|
+
415 => 'Unsupported Media Type',
|
58
|
+
416 => 'Request Range Not Satisfiable',
|
59
|
+
417 => 'Expectation Failed',
|
60
|
+
500 => 'Internal Server Error',
|
61
|
+
501 => 'Not Implemented',
|
62
|
+
502 => 'Bad Gateway',
|
63
|
+
503 => 'Service Unavailable',
|
64
|
+
504 => 'Gateway Timeout',
|
65
|
+
505 => 'HTTP Version Not Supported'
|
66
|
+
}
|
67
|
+
|
68
|
+
CodeToError = {}
|
69
|
+
|
70
|
+
StatusMessage.each{|code, message|
|
71
|
+
var_name = message.gsub(/[ \-]/,'_').upcase
|
72
|
+
err_name = message.gsub(/[ \-]/,'')
|
73
|
+
|
74
|
+
case code
|
75
|
+
when 100...200; parent = Info
|
76
|
+
when 200...300; parent = Success
|
77
|
+
when 300...400; parent = Redirect
|
78
|
+
when 400...500; parent = ClientError
|
79
|
+
when 500...600; parent = ServerError
|
80
|
+
end
|
81
|
+
|
82
|
+
eval %-
|
83
|
+
RC_#{var_name} = #{code}
|
84
|
+
class #{err_name} < #{parent}
|
85
|
+
def self.code() RC_#{var_name} end
|
86
|
+
def self.reason_phrase() StatusMessage[code] end
|
87
|
+
def code() self::class::code end
|
88
|
+
def reason_phrase() self::class::reason_phrase end
|
89
|
+
alias to_i code
|
90
|
+
end
|
91
|
+
-
|
92
|
+
|
93
|
+
CodeToError[code] = const_get(err_name)
|
94
|
+
}
|
95
|
+
|
96
|
+
def reason_phrase(code)
|
97
|
+
StatusMessage[code.to_i]
|
98
|
+
end
|
99
|
+
def info?(code)
|
100
|
+
code.to_i >= 100 and code.to_i < 200
|
101
|
+
end
|
102
|
+
def success?(code)
|
103
|
+
code.to_i >= 200 and code.to_i < 300
|
104
|
+
end
|
105
|
+
def redirect?(code)
|
106
|
+
code.to_i >= 300 and code.to_i < 400
|
107
|
+
end
|
108
|
+
def error?(code)
|
109
|
+
code.to_i >= 400 and code.to_i < 600
|
110
|
+
end
|
111
|
+
def client_error?(code)
|
112
|
+
code.to_i >= 400 and code.to_i < 500
|
113
|
+
end
|
114
|
+
def server_error?(code)
|
115
|
+
code.to_i >= 500 and code.to_i < 600
|
116
|
+
end
|
117
|
+
|
118
|
+
def self.[](code)
|
119
|
+
CodeToError[code]
|
120
|
+
end
|
121
|
+
|
122
|
+
module_function :reason_phrase
|
123
|
+
module_function :info?, :success?, :redirect?, :error?
|
124
|
+
module_function :client_error?, :server_error?
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,391 @@
|
|
1
|
+
#
|
2
|
+
# httputils.rb -- HTTPUtils Module
|
3
|
+
#
|
4
|
+
# Author: IPR -- Internet Programming with Ruby -- writers
|
5
|
+
# Copyright (c) 2000, 2001 TAKAHASHI Masayoshi, GOTOU Yuuzou
|
6
|
+
# Copyright (c) 2002 Internet Programming with Ruby writers. All rights
|
7
|
+
# reserved.
|
8
|
+
#
|
9
|
+
# $IPR: httputils.rb,v 1.34 2003/06/05 21:34:08 gotoyuzo Exp $
|
10
|
+
|
11
|
+
require 'socket'
|
12
|
+
require 'tempfile'
|
13
|
+
|
14
|
+
module WEBrick
|
15
|
+
CR = "\x0d"
|
16
|
+
LF = "\x0a"
|
17
|
+
CRLF = "\x0d\x0a"
|
18
|
+
|
19
|
+
module HTTPUtils
|
20
|
+
|
21
|
+
def normalize_path(path)
|
22
|
+
raise "abnormal path `#{path}'" if path[0] != ?/
|
23
|
+
ret = path.dup
|
24
|
+
|
25
|
+
ret.gsub!(%r{/+}o, '/') # // => /
|
26
|
+
while ret.sub!(%r'/\.(?:/|\Z)', '/'); end # /. => /
|
27
|
+
while ret.sub!(%r'/(?!\.\./)[^/]+/\.\.(?:/|\Z)', '/'); end # /foo/.. => /foo
|
28
|
+
|
29
|
+
raise "abnormal path `#{path}'" if %r{/\.\.(/|\Z)} =~ ret
|
30
|
+
ret
|
31
|
+
end
|
32
|
+
module_function :normalize_path
|
33
|
+
|
34
|
+
#####
|
35
|
+
|
36
|
+
DefaultMimeTypes = {
|
37
|
+
"ai" => "application/postscript",
|
38
|
+
"asc" => "text/plain",
|
39
|
+
"avi" => "video/x-msvideo",
|
40
|
+
"bin" => "application/octet-stream",
|
41
|
+
"bmp" => "image/bmp",
|
42
|
+
"class" => "application/octet-stream",
|
43
|
+
"cer" => "application/pkix-cert",
|
44
|
+
"crl" => "application/pkix-crl",
|
45
|
+
"crt" => "application/x-x509-ca-cert",
|
46
|
+
#"crl" => "application/x-pkcs7-crl",
|
47
|
+
"css" => "text/css",
|
48
|
+
"dms" => "application/octet-stream",
|
49
|
+
"doc" => "application/msword",
|
50
|
+
"dvi" => "application/x-dvi",
|
51
|
+
"eps" => "application/postscript",
|
52
|
+
"etx" => "text/x-setext",
|
53
|
+
"exe" => "application/octet-stream",
|
54
|
+
"gif" => "image/gif",
|
55
|
+
"htm" => "text/html",
|
56
|
+
"html" => "text/html",
|
57
|
+
"jpe" => "image/jpeg",
|
58
|
+
"jpeg" => "image/jpeg",
|
59
|
+
"jpg" => "image/jpeg",
|
60
|
+
"lha" => "application/octet-stream",
|
61
|
+
"lzh" => "application/octet-stream",
|
62
|
+
"mov" => "video/quicktime",
|
63
|
+
"mpe" => "video/mpeg",
|
64
|
+
"mpeg" => "video/mpeg",
|
65
|
+
"mpg" => "video/mpeg",
|
66
|
+
"pbm" => "image/x-portable-bitmap",
|
67
|
+
"pdf" => "application/pdf",
|
68
|
+
"pgm" => "image/x-portable-graymap",
|
69
|
+
"png" => "image/png",
|
70
|
+
"pnm" => "image/x-portable-anymap",
|
71
|
+
"ppm" => "image/x-portable-pixmap",
|
72
|
+
"ppt" => "application/vnd.ms-powerpoint",
|
73
|
+
"ps" => "application/postscript",
|
74
|
+
"qt" => "video/quicktime",
|
75
|
+
"ras" => "image/x-cmu-raster",
|
76
|
+
"rb" => "text/plain",
|
77
|
+
"rd" => "text/plain",
|
78
|
+
"rtf" => "application/rtf",
|
79
|
+
"sgm" => "text/sgml",
|
80
|
+
"sgml" => "text/sgml",
|
81
|
+
"tif" => "image/tiff",
|
82
|
+
"tiff" => "image/tiff",
|
83
|
+
"txt" => "text/plain",
|
84
|
+
"xbm" => "image/x-xbitmap",
|
85
|
+
"xls" => "application/vnd.ms-excel",
|
86
|
+
"xml" => "text/xml",
|
87
|
+
"xpm" => "image/x-xpixmap",
|
88
|
+
"xwd" => "image/x-xwindowdump",
|
89
|
+
"zip" => "application/zip",
|
90
|
+
}
|
91
|
+
|
92
|
+
# Load Apache compatible mime.types file.
|
93
|
+
def load_mime_types(file)
|
94
|
+
open(file){ |io|
|
95
|
+
hash = Hash.new
|
96
|
+
io.each{ |line|
|
97
|
+
next if /^#/ =~ line
|
98
|
+
line.chomp!
|
99
|
+
mimetype, ext0 = line.split(/\s+/, 2)
|
100
|
+
next unless ext0
|
101
|
+
next if ext0.empty?
|
102
|
+
ext0.split(/\s+/).each{ |ext| hash[ext] = mimetype }
|
103
|
+
}
|
104
|
+
hash
|
105
|
+
}
|
106
|
+
end
|
107
|
+
module_function :load_mime_types
|
108
|
+
|
109
|
+
def mime_type(filename, mime_tab)
|
110
|
+
suffix1 = (/\.(\w+)$/ =~ filename && $1.downcase)
|
111
|
+
suffix2 = (/\.(\w+)\.[\w\-]+$/ =~ filename && $1.downcase)
|
112
|
+
mime_tab[suffix1] || mime_tab[suffix2] || "application/octet-stream"
|
113
|
+
end
|
114
|
+
module_function :mime_type
|
115
|
+
|
116
|
+
#####
|
117
|
+
|
118
|
+
def parse_header(raw)
|
119
|
+
header = Hash.new([].freeze)
|
120
|
+
field = nil
|
121
|
+
raw.each{|line|
|
122
|
+
case line
|
123
|
+
when /^([A-Za-z0-9!\#$%&'*+\-.^_`|~]+):\s*(.*?)\s*\z/om
|
124
|
+
field, value = $1, $2
|
125
|
+
field.downcase!
|
126
|
+
header[field] = [] unless header.has_key?(field)
|
127
|
+
header[field] << value
|
128
|
+
when /^\s+(.*?)\s*\z/om
|
129
|
+
value = $1
|
130
|
+
unless field
|
131
|
+
raise "bad header '#{line.inspect}'."
|
132
|
+
end
|
133
|
+
header[field][-1] << " " << value
|
134
|
+
else
|
135
|
+
raise "bad header '#{line.inspect}'."
|
136
|
+
end
|
137
|
+
}
|
138
|
+
header.each{|key, values|
|
139
|
+
values.each{|value|
|
140
|
+
value.strip!
|
141
|
+
value.gsub!(/\s+/, " ")
|
142
|
+
}
|
143
|
+
}
|
144
|
+
header
|
145
|
+
end
|
146
|
+
module_function :parse_header
|
147
|
+
|
148
|
+
def split_header_value(str)
|
149
|
+
str.scan(%r'\G((?:"(?:\\.|[^"])+?"|[^",]+)+)
|
150
|
+
(?:,\s*|\Z)'xn).flatten
|
151
|
+
end
|
152
|
+
module_function :split_header_value
|
153
|
+
|
154
|
+
def parse_range_header(ranges_specifier)
|
155
|
+
if /^bytes=(.*)/ =~ ranges_specifier
|
156
|
+
byte_range_set = split_header_value($1)
|
157
|
+
byte_range_set.collect{|range_spec|
|
158
|
+
case range_spec
|
159
|
+
when /^(\d+)-(\d+)/ then $1.to_i .. $2.to_i
|
160
|
+
when /^(\d+)-/ then $1.to_i .. -1
|
161
|
+
when /^-(\d+)/ then -($1.to_i) .. -1
|
162
|
+
else return nil
|
163
|
+
end
|
164
|
+
}
|
165
|
+
end
|
166
|
+
end
|
167
|
+
module_function :parse_range_header
|
168
|
+
|
169
|
+
def parse_qvalues(value)
|
170
|
+
tmp = []
|
171
|
+
if value
|
172
|
+
parts = value.split(/,\s*/)
|
173
|
+
parts.each {|part|
|
174
|
+
if m = %r{^([^\s,]+?)(?:;\s*q=(\d+(?:\.\d+)?))?$}.match(part)
|
175
|
+
val = m[1]
|
176
|
+
q = (m[2] or 1).to_f
|
177
|
+
tmp.push([val, q])
|
178
|
+
end
|
179
|
+
}
|
180
|
+
tmp = tmp.sort_by{|val, q| -q}
|
181
|
+
tmp.collect!{|val, q| val}
|
182
|
+
end
|
183
|
+
return tmp
|
184
|
+
end
|
185
|
+
module_function :parse_qvalues
|
186
|
+
|
187
|
+
#####
|
188
|
+
|
189
|
+
def dequote(str)
|
190
|
+
ret = (/\A"(.*)"\Z/ =~ str) ? $1 : str.dup
|
191
|
+
ret.gsub!(/\\(.)/, "\\1")
|
192
|
+
ret
|
193
|
+
end
|
194
|
+
module_function :dequote
|
195
|
+
|
196
|
+
def quote(str)
|
197
|
+
'"' << str.gsub(/[\\\"]/o, "\\\1") << '"'
|
198
|
+
end
|
199
|
+
module_function :quote
|
200
|
+
|
201
|
+
#####
|
202
|
+
|
203
|
+
class FormData < String
|
204
|
+
EmptyRawHeader = [].freeze
|
205
|
+
EmptyHeader = {}.freeze
|
206
|
+
|
207
|
+
attr_accessor :name, :filename, :next_data
|
208
|
+
protected :next_data
|
209
|
+
|
210
|
+
def initialize(*args)
|
211
|
+
@name = @filename = @next_data = nil
|
212
|
+
if args.empty?
|
213
|
+
@raw_header = []
|
214
|
+
@header = nil
|
215
|
+
super("")
|
216
|
+
else
|
217
|
+
@raw_header = EmptyRawHeader
|
218
|
+
@header = EmptyHeader
|
219
|
+
super(args.shift)
|
220
|
+
unless args.empty?
|
221
|
+
@next_data = self.class.new(*args)
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
def [](*key)
|
227
|
+
begin
|
228
|
+
@header[key[0].downcase].join(", ")
|
229
|
+
rescue StandardError, NameError
|
230
|
+
super
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
def <<(str)
|
235
|
+
if @header
|
236
|
+
super
|
237
|
+
elsif str == CRLF
|
238
|
+
@header = HTTPUtils::parse_header(@raw_header)
|
239
|
+
if cd = self['content-disposition']
|
240
|
+
if /\s+name="(.*?)"/ =~ cd then @name = $1 end
|
241
|
+
if /\s+filename="(.*?)"/ =~ cd then @filename = $1 end
|
242
|
+
end
|
243
|
+
else
|
244
|
+
@raw_header << str
|
245
|
+
end
|
246
|
+
self
|
247
|
+
end
|
248
|
+
|
249
|
+
def append_data(data)
|
250
|
+
tmp = self
|
251
|
+
while tmp
|
252
|
+
unless tmp.next_data
|
253
|
+
tmp.next_data = data
|
254
|
+
break
|
255
|
+
end
|
256
|
+
tmp = tmp.next_data
|
257
|
+
end
|
258
|
+
self
|
259
|
+
end
|
260
|
+
|
261
|
+
def each_data
|
262
|
+
tmp = self
|
263
|
+
while tmp
|
264
|
+
next_data = tmp.next_data
|
265
|
+
yield(tmp)
|
266
|
+
tmp = next_data
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
def list
|
271
|
+
ret = []
|
272
|
+
each_data{|data|
|
273
|
+
ret << data.to_s
|
274
|
+
}
|
275
|
+
ret
|
276
|
+
end
|
277
|
+
|
278
|
+
alias :to_ary :list
|
279
|
+
|
280
|
+
def to_s
|
281
|
+
String.new(self)
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
def parse_query(str)
|
286
|
+
query = Hash.new
|
287
|
+
if str
|
288
|
+
str.split(/[&;]/).each{|x|
|
289
|
+
next if x.empty?
|
290
|
+
key, val = x.split(/=/,2)
|
291
|
+
key = unescape_form(key)
|
292
|
+
val = unescape_form(val.to_s)
|
293
|
+
val = FormData.new(val)
|
294
|
+
val.name = key
|
295
|
+
if query.has_key?(key)
|
296
|
+
query[key].append_data(val)
|
297
|
+
next
|
298
|
+
end
|
299
|
+
query[key] = val
|
300
|
+
}
|
301
|
+
end
|
302
|
+
query
|
303
|
+
end
|
304
|
+
module_function :parse_query
|
305
|
+
|
306
|
+
def parse_form_data(io, boundary)
|
307
|
+
boundary_regexp = /\A--#{boundary}(--)?#{CRLF}\z/
|
308
|
+
form_data = Hash.new
|
309
|
+
return form_data unless io
|
310
|
+
data = nil
|
311
|
+
io.each{|line|
|
312
|
+
if boundary_regexp =~ line
|
313
|
+
if data
|
314
|
+
data.chop!
|
315
|
+
key = data.name
|
316
|
+
if form_data.has_key?(key)
|
317
|
+
form_data[key].append_data(data)
|
318
|
+
else
|
319
|
+
form_data[key] = data
|
320
|
+
end
|
321
|
+
end
|
322
|
+
data = FormData.new
|
323
|
+
next
|
324
|
+
else
|
325
|
+
if data
|
326
|
+
data << line
|
327
|
+
end
|
328
|
+
end
|
329
|
+
}
|
330
|
+
return form_data
|
331
|
+
end
|
332
|
+
module_function :parse_form_data
|
333
|
+
|
334
|
+
#####
|
335
|
+
|
336
|
+
reserved = ';/?:@&=+$,'
|
337
|
+
num = '0123456789'
|
338
|
+
lowalpha = 'abcdefghijklmnopqrstuvwxyz'
|
339
|
+
upalpha = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
340
|
+
mark = '-_.!~*\'()'
|
341
|
+
unreserved = num + lowalpha + upalpha + mark
|
342
|
+
control = (0x0..0x1f).collect{|c| c.chr }.join + "\x7f"
|
343
|
+
space = " "
|
344
|
+
delims = '<>#%"'
|
345
|
+
unwise = '{}|\\^[]`'
|
346
|
+
nonascii = (0x80..0xff).collect{|c| c.chr }.join
|
347
|
+
|
348
|
+
module_function
|
349
|
+
|
350
|
+
def _make_regex(str) /([#{Regexp.escape(str)}])/n end
|
351
|
+
def _make_regex!(str) /([^#{Regexp.escape(str)}])/n end
|
352
|
+
def _escape(str, regex) str.gsub(regex){ "%%%02X" % $1[0] } end
|
353
|
+
def _unescape(str, regex) str.gsub(regex){ $1.hex.chr } end
|
354
|
+
|
355
|
+
UNESCAPED = _make_regex(control+space+delims+unwise+nonascii)
|
356
|
+
UNESCAPED_FORM = _make_regex(reserved+control+delims+unwise+nonascii)
|
357
|
+
NONASCII = _make_regex(nonascii)
|
358
|
+
ESCAPED = /%([0-9a-fA-F]{2})/
|
359
|
+
UNESCAPED_PCHAR = _make_regex!(unreserved+":@&=+$,")
|
360
|
+
|
361
|
+
def escape(str)
|
362
|
+
_escape(str, UNESCAPED)
|
363
|
+
end
|
364
|
+
|
365
|
+
def unescape(str)
|
366
|
+
_unescape(str, ESCAPED)
|
367
|
+
end
|
368
|
+
|
369
|
+
def escape_form(str)
|
370
|
+
ret = _escape(str, UNESCAPED_FORM)
|
371
|
+
ret.gsub!(/ /, "+")
|
372
|
+
ret
|
373
|
+
end
|
374
|
+
|
375
|
+
def unescape_form(str)
|
376
|
+
_unescape(str.gsub(/\+/, " "), ESCAPED)
|
377
|
+
end
|
378
|
+
|
379
|
+
def escape_path(str)
|
380
|
+
result = ""
|
381
|
+
str.scan(%r{/([^/]*)}).each{|i|
|
382
|
+
result << "/" << _escape(i[0], UNESCAPED_PCHAR)
|
383
|
+
}
|
384
|
+
return result
|
385
|
+
end
|
386
|
+
|
387
|
+
def escape8bit(str)
|
388
|
+
_escape(str, NONASCII)
|
389
|
+
end
|
390
|
+
end
|
391
|
+
end
|