PageRenderTime 68ms CodeModel.GetById 20ms app.highlight 43ms RepoModel.GetById 1ms app.codeStats 0ms

/tools/Ruby/lib/ruby/1.8/webrick/httputils.rb

http://github.com/agross/netopenspace
Ruby | 391 lines | 333 code | 42 blank | 16 comment | 27 complexity | 273ebb1d96b3db5d74446a7005d2931c MD5 | raw file
  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
 11require 'socket'
 12require 'tempfile'
 13
 14module 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 HTTPStatus::BadRequest, "bad header '#{line}'."
132          end
133          header[field][-1] << " " << value
134        else
135          raise HTTPStatus::BadRequest, "bad header '#{line}'."
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
391end