2
# httputils.rb -- HTTPUtils Module
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
9
# $IPR: httputils.rb,v 1.34 2003/06/05 21:34:08 gotoyuzo Exp $
21
def normalize_path(path)
22
raise "abnormal path `#{path}'" if path[0] != ?/
25
ret.gsub!(%r{/+}o, '/') # // => /
26
while ret.sub!(%r{/\.(/|\Z)}o, '/'); end # /. => /
27
begin # /foo/.. => /foo
28
match = ret.sub!(%r{/([^/]+)/\.\.(/|\Z)}o){
30
raise "abnormal path `#{path}'"
37
raise "abnormal path `#{path}'" if %r{/\.\.(/|\Z)} =~ ret
40
module_function :normalize_path
45
"ai" => "application/postscript",
46
"asc" => "text/plain",
47
"avi" => "video/x-msvideo",
48
"bin" => "application/octet-stream",
50
"class" => "application/octet-stream",
51
"cer" => "application/pkix-cert",
52
"crl" => "application/pkix-crl",
53
"crt" => "application/x-x509-ca-cert",
54
#"crl" => "application/x-pkcs7-crl",
56
"dms" => "application/octet-stream",
57
"doc" => "application/msword",
58
"dvi" => "application/x-dvi",
59
"eps" => "application/postscript",
60
"etx" => "text/x-setext",
61
"exe" => "application/octet-stream",
64
"html" => "text/html",
65
"jpe" => "image/jpeg",
66
"jpeg" => "image/jpeg",
67
"jpg" => "image/jpeg",
68
"lha" => "application/octet-stream",
69
"lzh" => "application/octet-stream",
70
"mov" => "video/quicktime",
71
"mpe" => "video/mpeg",
72
"mpeg" => "video/mpeg",
73
"mpg" => "video/mpeg",
74
"pbm" => "image/x-portable-bitmap",
75
"pdf" => "application/pdf",
76
"pgm" => "image/x-portable-graymap",
78
"pnm" => "image/x-portable-anymap",
79
"ppm" => "image/x-portable-pixmap",
80
"ppt" => "application/vnd.ms-powerpoint",
81
"ps" => "application/postscript",
82
"qt" => "video/quicktime",
83
"ras" => "image/x-cmu-raster",
86
"rtf" => "application/rtf",
88
"sgml" => "text/sgml",
89
"tif" => "image/tiff",
90
"tiff" => "image/tiff",
91
"txt" => "text/plain",
92
"xbm" => "image/x-xbitmap",
93
"xls" => "application/vnd.ms-excel",
95
"xpm" => "image/x-xpixmap",
96
"xwd" => "image/x-xwindowdump",
97
"zip" => "application/zip",
100
# Load Apache compatible mime.types file.
101
def load_mime_types(file)
107
mimetype, ext0 = line.split(/\s+/, 2)
110
ext0.split(/\s+/).each{ |ext| hash[ext] = mimetype }
115
module_function :load_mime_types
117
def mime_type(filename, mime_tab)
118
suffix1 = (/\.(\w+)$/ =~ filename && $1.downcase)
119
suffix2 = (/\.(\w+)\.[\w\-]+$/ =~ filename && $1.downcase)
120
mime_tab[suffix1] || mime_tab[suffix2] || "application/octet-stream"
122
module_function :mime_type
126
def parse_header(raw)
127
header = Hash.new([].freeze)
131
when /^([A-Za-z0-9!\#$%&'*+\-.^_`|~]+):\s*(.*?)\s*\z/om
132
field, value = $1, $2
134
header[field] = [] unless header.has_key?(field)
135
header[field] << value
136
when /^\s+(.*?)\s*\z/om
139
raise "bad header '#{line.inspect}'."
141
header[field][-1] << " " << value
143
raise "bad header '#{line.inspect}'."
146
header.each{|key, values|
149
value.gsub!(/\s+/, " ")
154
module_function :parse_header
156
def split_header_value(str)
157
str.scan(/((?:"(?:\\.|[^"])+?"|[^",]+)+)
158
(?:,\s*|\Z)/xn).collect{|v| v[0] }
160
module_function :split_header_value
162
def parse_range_header(ranges_specifier)
163
if /^bytes=(.*)/ =~ ranges_specifier
164
byte_range_set = split_header_value($1)
165
byte_range_set.collect{|range_spec|
167
when /^(\d+)-(\d+)/ then $1.to_i .. $2.to_i
168
when /^(\d+)-/ then $1.to_i .. -1
169
when /^-(\d+)/ then -($1.to_i) .. -1
175
module_function :parse_range_header
177
def parse_qvalues(value)
180
parts = value.split(/,\s*/)
182
if m = %r{^([^\s,]+?)(?:;\s*q=(\d+(?:\.\d+)?))?$}.match(part)
188
tmp = tmp.sort_by{|val, q| -q}
189
tmp.collect!{|val, q| val}
193
module_function :parse_qvalues
198
ret = (/\A"(.*)"\Z/ =~ str) ? $1 : str.dup
199
ret.gsub!(/\\(.)/, "\\1")
202
module_function :dequote
205
'"' << str.gsub(/[\\\"]/o, "\\\1") << '"'
207
module_function :quote
211
class FormData < String
212
EmptyRawHeader = [].freeze
213
EmptyHeader = {}.freeze
215
attr_accessor :name, :filename, :next_data
218
def initialize(*args)
219
@name = @filename = @next_data = nil
225
@raw_header = EmptyRawHeader
226
@header = EmptyHeader
229
@next_data = self.class.new(*args)
236
@header[key[0].downcase].join(", ")
237
rescue StandardError, NameError
246
@header = HTTPUtils::parse_header(@raw_header)
247
if cd = self['content-disposition']
248
if /\s+name="(.*?)"/ =~ cd then @name = $1 end
249
if /\s+filename="(.*?)"/ =~ cd then @filename = $1 end
257
def append_data(data)
272
next_data = tmp.next_data
296
str.split(/[&;]/).each{|x|
298
key, val = x.split(/=/,2)
299
key = unescape_form(key)
300
val = unescape_form(val.to_s)
301
val = FormData.new(val)
303
if query.has_key?(key)
304
query[key].append_data(val)
312
module_function :parse_query
314
def parse_form_data(io, boundary)
315
boundary_regexp = /\A--#{boundary}(--)?#{CRLF}\z/
317
return form_data unless io
320
if boundary_regexp =~ line
324
if form_data.has_key?(key)
325
form_data[key].append_data(data)
327
form_data[key] = data
340
module_function :parse_form_data
344
reserved = ';/?:@&=+$,'
346
lowalpha = 'abcdefghijklmnopqrstuvwxyz'
347
upalpha = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
349
unreserved = num + lowalpha + upalpha + mark
350
control = (0x0..0x1f).collect{|c| c.chr }.join + "\x7f"
354
nonascii = (0x80..0xff).collect{|c| c.chr }.join
358
def _make_regex(str) /([#{Regexp.escape(str)}])/n end
359
def _make_regex!(str) /([^#{Regexp.escape(str)}])/n end
360
def _escape(str, regex) str.gsub(regex){ "%%%02X" % $1[0] } end
361
def _unescape(str, regex) str.gsub(regex){ $1.hex.chr } end
363
UNESCAPED = _make_regex(control+space+delims+unwise+nonascii)
364
UNESCAPED_FORM = _make_regex(reserved+control+delims+unwise+nonascii)
365
NONASCII = _make_regex(nonascii)
366
ESCAPED = /%([0-9a-fA-F]{2})/
367
UNESCAPED_PCHAR = _make_regex!(unreserved+":@&=+$,")
370
_escape(str, UNESCAPED)
374
_unescape(str, ESCAPED)
378
ret = _escape(str, UNESCAPED_FORM)
383
def unescape_form(str)
384
_unescape(str.gsub(/\+/, " "), ESCAPED)
389
str.scan(%r{/([^/]*)}).each{|i|
390
result << "/" << _escape(i[0], UNESCAPED_PCHAR)
396
_escape(str, NONASCII)