~ubuntu-branches/ubuntu/hardy/ruby1.8/hardy-updates

« back to all changes in this revision

Viewing changes to lib/webrick/httputils.rb

  • Committer: Bazaar Package Importer
  • Author(s): akira yamada
  • Date: 2007-03-13 22:11:58 UTC
  • mfrom: (1.1.5 upstream)
  • Revision ID: james.westby@ubuntu.com-20070313221158-h3oql37brlaf2go2
Tags: 1.8.6-1
* new upstream version, 1.8.6.
* libruby1.8 conflicts with libopenssl-ruby1.8 (< 1.8.6) (closes: #410018)
* changed packaging style to cdbs from dbs.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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)}o, '/'); end   # /.      => /
 
27
      begin                                      # /foo/.. => /foo
 
28
        match = ret.sub!(%r{/([^/]+)/\.\.(/|\Z)}o){
 
29
          if $1 == ".."
 
30
            raise "abnormal path `#{path}'"
 
31
          else
 
32
            "/"
 
33
          end
 
34
        }
 
35
      end while match
 
36
 
 
37
      raise "abnormal path `#{path}'" if %r{/\.\.(/|\Z)} =~ ret
 
38
      ret
 
39
    end
 
40
    module_function :normalize_path
 
41
 
 
42
    #####
 
43
 
 
44
    DefaultMimeTypes = {
 
45
      "ai"    => "application/postscript",
 
46
      "asc"   => "text/plain",
 
47
      "avi"   => "video/x-msvideo",
 
48
      "bin"   => "application/octet-stream",
 
49
      "bmp"   => "image/bmp",
 
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",
 
55
      "css"   => "text/css",
 
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",
 
62
      "gif"   => "image/gif",
 
63
      "htm"   => "text/html",
 
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",
 
77
      "png"   => "image/png",
 
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",
 
84
      "rb"    => "text/plain",
 
85
      "rd"    => "text/plain",
 
86
      "rtf"   => "application/rtf",
 
87
      "sgm"   => "text/sgml",
 
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",
 
94
      "xml"   => "text/xml",
 
95
      "xpm"   => "image/x-xpixmap",
 
96
      "xwd"   => "image/x-xwindowdump",
 
97
      "zip"   => "application/zip",
 
98
    }
 
99
 
 
100
    # Load Apache compatible mime.types file.
 
101
    def load_mime_types(file)
 
102
      open(file){ |io|
 
103
        hash = Hash.new
 
104
        io.each{ |line|
 
105
          next if /^#/ =~ line
 
106
          line.chomp!
 
107
          mimetype, ext0 = line.split(/\s+/, 2)
 
108
          next unless ext0   
 
109
          next if ext0.empty?
 
110
          ext0.split(/\s+/).each{ |ext| hash[ext] = mimetype }
 
111
        }
 
112
        hash
 
113
      }
 
114
    end
 
115
    module_function :load_mime_types
 
116
 
 
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"
 
121
    end
 
122
    module_function :mime_type
 
123
 
 
124
    #####
 
125
 
 
126
    def parse_header(raw)
 
127
      header = Hash.new([].freeze)
 
128
      field = nil
 
129
      raw.each{|line|
 
130
        case line
 
131
        when /^([A-Za-z0-9!\#$%&'*+\-.^_`|~]+):\s*(.*?)\s*\z/om
 
132
          field, value = $1, $2
 
133
          field.downcase!
 
134
          header[field] = [] unless header.has_key?(field)
 
135
          header[field] << value
 
136
        when /^\s+(.*?)\s*\z/om
 
137
          value = $1
 
138
          unless field
 
139
            raise "bad header '#{line.inspect}'."
 
140
          end
 
141
          header[field][-1] << " " << value
 
142
        else
 
143
          raise "bad header '#{line.inspect}'."
 
144
        end
 
145
      }
 
146
      header.each{|key, values|
 
147
        values.each{|value|
 
148
          value.strip!
 
149
          value.gsub!(/\s+/, " ")
 
150
        }
 
151
      }
 
152
      header
 
153
    end
 
154
    module_function :parse_header
 
155
 
 
156
    def split_header_value(str)
 
157
      str.scan(/((?:"(?:\\.|[^"])+?"|[^",]+)+)
 
158
                (?:,\s*|\Z)/xn).collect{|v| v[0] }
 
159
    end
 
160
    module_function :split_header_value
 
161
 
 
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|
 
166
          case 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
 
170
          else return nil
 
171
          end
 
172
        }
 
173
      end
 
174
    end
 
175
    module_function :parse_range_header
 
176
 
 
177
    def parse_qvalues(value)
 
178
      tmp = []
 
179
      if value
 
180
        parts = value.split(/,\s*/)
 
181
        parts.each {|part|
 
182
          if m = %r{^([^\s,]+?)(?:;\s*q=(\d+(?:\.\d+)?))?$}.match(part)
 
183
            val = m[1]
 
184
            q = (m[2] or 1).to_f
 
185
            tmp.push([val, q])
 
186
          end
 
187
        }
 
188
        tmp = tmp.sort_by{|val, q| -q}
 
189
        tmp.collect!{|val, q| val}
 
190
      end
 
191
      return tmp
 
192
    end
 
193
    module_function :parse_qvalues
 
194
 
 
195
    #####
 
196
 
 
197
    def dequote(str)
 
198
      ret = (/\A"(.*)"\Z/ =~ str) ? $1 : str.dup
 
199
      ret.gsub!(/\\(.)/, "\\1")
 
200
      ret
 
201
    end
 
202
    module_function :dequote
 
203
 
 
204
    def quote(str)
 
205
      '"' << str.gsub(/[\\\"]/o, "\\\1") << '"'
 
206
    end
 
207
    module_function :quote
 
208
 
 
209
    #####
 
210
 
 
211
    class FormData < String
 
212
      EmptyRawHeader = [].freeze
 
213
      EmptyHeader = {}.freeze
 
214
 
 
215
      attr_accessor :name, :filename, :next_data
 
216
      protected :next_data
 
217
 
 
218
      def initialize(*args)
 
219
        @name = @filename = @next_data = nil
 
220
        if args.empty?
 
221
          @raw_header = []
 
222
          @header = nil
 
223
          super("")
 
224
        else
 
225
          @raw_header = EmptyRawHeader
 
226
          @header = EmptyHeader 
 
227
          super(args.shift)
 
228
          unless args.empty?
 
229
            @next_data = self.class.new(*args)
 
230
          end
 
231
        end
 
232
      end
 
233
 
 
234
      def [](*key)
 
235
        begin
 
236
          @header[key[0].downcase].join(", ")
 
237
        rescue StandardError, NameError
 
238
          super
 
239
        end
 
240
      end
 
241
 
 
242
      def <<(str)
 
243
        if @header
 
244
          super
 
245
        elsif str == CRLF
 
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
 
250
          end
 
251
        else
 
252
          @raw_header << str
 
253
        end
 
254
        self
 
255
      end
 
256
 
 
257
      def append_data(data)
 
258
        tmp = self
 
259
        while tmp
 
260
          unless tmp.next_data 
 
261
            tmp.next_data = data
 
262
            break
 
263
          end
 
264
          tmp = tmp.next_data
 
265
        end
 
266
        self
 
267
      end
 
268
 
 
269
      def each_data
 
270
        tmp = self
 
271
        while tmp
 
272
          next_data = tmp.next_data
 
273
          yield(tmp)
 
274
          tmp = next_data
 
275
        end
 
276
      end
 
277
 
 
278
      def list
 
279
        ret = []
 
280
        each_data{|data|
 
281
          ret << data.to_s
 
282
        }
 
283
        ret
 
284
      end
 
285
 
 
286
      alias :to_ary :list
 
287
 
 
288
      def to_s
 
289
        String.new(self)
 
290
      end
 
291
    end
 
292
 
 
293
    def parse_query(str)
 
294
      query = Hash.new
 
295
      if str
 
296
        str.split(/[&;]/).each{|x|
 
297
          next if x.empty? 
 
298
          key, val = x.split(/=/,2)
 
299
          key = unescape_form(key)
 
300
          val = unescape_form(val.to_s)
 
301
          val = FormData.new(val)
 
302
          val.name = key
 
303
          if query.has_key?(key)
 
304
            query[key].append_data(val)
 
305
            next
 
306
          end
 
307
          query[key] = val
 
308
        }
 
309
      end
 
310
      query
 
311
    end
 
312
    module_function :parse_query
 
313
 
 
314
    def parse_form_data(io, boundary)
 
315
      boundary_regexp = /\A--#{boundary}(--)?#{CRLF}\z/
 
316
      form_data = Hash.new
 
317
      return form_data unless io
 
318
      data = nil
 
319
      io.each{|line|
 
320
        if boundary_regexp =~ line
 
321
          if data
 
322
            data.chop!
 
323
            key = data.name
 
324
            if form_data.has_key?(key)
 
325
              form_data[key].append_data(data)
 
326
            else
 
327
              form_data[key] = data 
 
328
            end
 
329
          end
 
330
          data = FormData.new
 
331
          next
 
332
        else
 
333
          if data
 
334
            data << line
 
335
          end
 
336
        end
 
337
      }
 
338
      return form_data
 
339
    end
 
340
    module_function :parse_form_data
 
341
 
 
342
    #####
 
343
 
 
344
    reserved = ';/?:@&=+$,'
 
345
    num      = '0123456789'
 
346
    lowalpha = 'abcdefghijklmnopqrstuvwxyz'
 
347
    upalpha  = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
 
348
    mark     = '-_.!~*\'()'
 
349
    unreserved = num + lowalpha + upalpha + mark
 
350
    control  = (0x0..0x1f).collect{|c| c.chr }.join + "\x7f"
 
351
    space    = " "
 
352
    delims   = '<>#%"'
 
353
    unwise   = '{}|\\^[]`'
 
354
    nonascii = (0x80..0xff).collect{|c| c.chr }.join
 
355
 
 
356
    module_function
 
357
 
 
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
 
362
 
 
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+":@&=+$,")
 
368
 
 
369
    def escape(str)
 
370
      _escape(str, UNESCAPED)
 
371
    end
 
372
 
 
373
    def unescape(str)
 
374
      _unescape(str, ESCAPED)
 
375
    end
 
376
 
 
377
    def escape_form(str)
 
378
      ret = _escape(str, UNESCAPED_FORM)
 
379
      ret.gsub!(/ /, "+")
 
380
      ret
 
381
    end
 
382
 
 
383
    def unescape_form(str)
 
384
      _unescape(str.gsub(/\+/, " "), ESCAPED)
 
385
    end
 
386
 
 
387
    def escape_path(str)
 
388
      result = ""
 
389
      str.scan(%r{/([^/]*)}).each{|i|
 
390
        result << "/" << _escape(i[0], UNESCAPED_PCHAR)
 
391
      }
 
392
      return result
 
393
    end
 
394
 
 
395
    def escape8bit(str)
 
396
      _escape(str, NONASCII)
 
397
    end
 
398
  end
 
399
end