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)