Module: YARD::Server::HTTPUtils

Included in:
Commands::StaticFileHelpers, RackAdapter
Defined in:
lib/yard/server/http_utils.rb

Overview

HTTPUtils provides utility methods for working with the HTTP protocol.

This module is generally used internally by WEBrick

Since:

  • 0.6.0

Defined Under Namespace

Classes: FormData

Constant Summary collapse

DefaultMimeTypes =

Default mime types

Since:

  • 0.6.0

{
  "ai"    => "application/postscript",
  "asc"   => "text/plain",
  "avi"   => "video/x-msvideo",
  "bin"   => "application/octet-stream",
  "bmp"   => "image/bmp",
  "class" => "application/octet-stream",
  "cer"   => "application/pkix-cert",
  "crl"   => "application/pkix-crl",
  "crt"   => "application/x-x509-ca-cert",
 #"crl"   => "application/x-pkcs7-crl",
  "css"   => "text/css",
  "dms"   => "application/octet-stream",
  "doc"   => "application/msword",
  "dvi"   => "application/x-dvi",
  "eps"   => "application/postscript",
  "etx"   => "text/x-setext",
  "exe"   => "application/octet-stream",
  "gif"   => "image/gif",
  "htm"   => "text/html",
  "html"  => "text/html",
  "jpe"   => "image/jpeg",
  "jpeg"  => "image/jpeg",
  "jpg"   => "image/jpeg",
  "js"    => "application/javascript",
  "json"  => "application/json",
  "lha"   => "application/octet-stream",
  "lzh"   => "application/octet-stream",
  "mjs"   => "application/javascript",
  "mov"   => "video/quicktime",
  "mpe"   => "video/mpeg",
  "mpeg"  => "video/mpeg",
  "mpg"   => "video/mpeg",
  "pbm"   => "image/x-portable-bitmap",
  "pdf"   => "application/pdf",
  "pgm"   => "image/x-portable-graymap",
  "png"   => "image/png",
  "pnm"   => "image/x-portable-anymap",
  "ppm"   => "image/x-portable-pixmap",
  "ppt"   => "application/vnd.ms-powerpoint",
  "ps"    => "application/postscript",
  "qt"    => "video/quicktime",
  "ras"   => "image/x-cmu-raster",
  "rb"    => "text/plain",
  "rd"    => "text/plain",
  "rtf"   => "application/rtf",
  "sgm"   => "text/sgml",
  "sgml"  => "text/sgml",
  "svg"   => "image/svg+xml",
  "tif"   => "image/tiff",
  "tiff"  => "image/tiff",
  "txt"   => "text/plain",
  "wasm"  => "application/wasm",
  "xbm"   => "image/x-xbitmap",
  "xhtml" => "text/html",
  "xls"   => "application/vnd.ms-excel",
  "xml"   => "text/xml",
  "xpm"   => "image/x-xpixmap",
  "xwd"   => "image/x-xwindowdump",
  "zip"   => "application/zip",
}
UNESCAPED =

Since:

  • 0.6.0

_make_regex(control+space+delims+unwise+nonascii)
UNESCAPED_FORM =

Since:

  • 0.6.0

_make_regex(reserved+control+delims+unwise+nonascii)
NONASCII =

Since:

  • 0.6.0

_make_regex(nonascii)
ESCAPED =

Since:

  • 0.6.0

/%([0-9a-fA-F]{2})/
UNESCAPED_PCHAR =

Since:

  • 0.6.0

_make_regex!(unreserved+":@&=+$,")

Class Method Summary collapse

Class Method Details

._escape(str, regex) ⇒ Object

Since:

  • 0.6.0



443
444
445
446
447
448
# File 'lib/yard/server/http_utils.rb', line 443

def _escape(str, regex)
  str = str.b
  str.gsub!(regex) {"%%%02X" % $1.ord}
  # %-escaped string should contain US-ASCII only
  str.force_encoding(Encoding::US_ASCII)
end

._make_regex(str) ⇒ Object

:stopdoc:

Since:

  • 0.6.0



441
# File 'lib/yard/server/http_utils.rb', line 441

def _make_regex(str) /([#{Regexp.escape(str)}])/n end

._make_regex!(str) ⇒ Object

Since:

  • 0.6.0



442
# File 'lib/yard/server/http_utils.rb', line 442

def _make_regex!(str) /([^#{Regexp.escape(str)}])/n end

._unescape(str, regex) ⇒ Object

Since:

  • 0.6.0



449
450
451
452
453
454
# File 'lib/yard/server/http_utils.rb', line 449

def _unescape(str, regex)
  str = str.b
  str.gsub!(regex) {$1.hex.chr}
  # encoding of %-unescaped string is unknown
  str
end

.dequote(str) ⇒ Object

Removes quotes and escapes from str

Since:

  • 0.6.0



223
224
225
226
227
# File 'lib/yard/server/http_utils.rb', line 223

def dequote(str)
  ret = (/\A"(.*)"\Z/ =~ str) ? $1 : str.dup
  ret.gsub!(/\\(.)/, "\\1")
  ret
end

.escape(str) ⇒ Object

Escapes HTTP reserved and unwise characters in str

Since:

  • 0.6.0



467
468
469
# File 'lib/yard/server/http_utils.rb', line 467

def escape(str)
  _escape(str, UNESCAPED)
end

.escape8bit(str) ⇒ Object

Escapes 8 bit characters in str

Since:

  • 0.6.0



508
509
510
# File 'lib/yard/server/http_utils.rb', line 508

def escape8bit(str)
  _escape(str, NONASCII)
end

.escape_form(str) ⇒ Object

Escapes form reserved characters in str

Since:

  • 0.6.0



481
482
483
484
485
# File 'lib/yard/server/http_utils.rb', line 481

def escape_form(str)
  ret = _escape(str, UNESCAPED_FORM)
  ret.gsub!(/ /, "+")
  ret
end

.escape_path(str) ⇒ Object

Escapes path str

Since:

  • 0.6.0



497
498
499
500
501
502
503
# File 'lib/yard/server/http_utils.rb', line 497

def escape_path(str)
  result = ""
  str.scan(%r{/([^/]*)}).each{|i|
    result << "/" << _escape(i[0], UNESCAPED_PCHAR)
  }
  return result
end

.load_mime_types(file) ⇒ Object

Loads Apache-compatible mime.types in file.

Since:

  • 0.6.0



112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/yard/server/http_utils.rb', line 112

def load_mime_types(file)
  # note: +file+ may be a "| command" for now; some people may
  # rely on this, but currently we do not use this method by default.
  open(file){ |io|
    hash = Hash.new
    io.each{ |line|
      next if /^#/ =~ line
      line.chomp!
      mimetype, ext0 = line.split(/\s+/, 2)
      next unless ext0
      next if ext0.empty?
      ext0.split(/\s+/).each{ |ext| hash[ext] = mimetype }
    }
    hash
  }
end

.mime_type(filename, mime_tab) ⇒ Object

Returns the mime type of filename from the list in mime_tab. If no mime type was found application/octet-stream is returned.

Since:

  • 0.6.0



134
135
136
137
138
# File 'lib/yard/server/http_utils.rb', line 134

def mime_type(filename, mime_tab)
  suffix1 = (/\.(\w+)$/ =~ filename && $1.downcase)
  suffix2 = (/\.(\w+)\.[\w\-]+$/ =~ filename && $1.downcase)
  mime_tab[suffix1] || mime_tab[suffix2] || "application/octet-stream"
end

.normalize_path(path) ⇒ Object

Normalizes a request path. Raises an exception if the path cannot be normalized.

Since:

  • 0.6.0



31
32
33
34
35
36
37
38
39
40
41
# File 'lib/yard/server/http_utils.rb', line 31

def normalize_path(path)
  raise "abnormal path `#{path}'" if path[0] != ?/
  ret = path.dup

  ret.gsub!(%r{/+}o, '/')                    # //      => /
  while ret.sub!(%r'/\.(?:/|\Z)', '/'); end  # /.      => /
  while ret.sub!(%r'/(?!\.\./)[^/]+/\.\.(?:/|\Z)', '/'); end # /foo/.. => /foo

  raise "abnormal path `#{path}'" if %r{/\.\.(/|\Z)} =~ ret
  ret
end

.parse_form_data(io, boundary) ⇒ Object

Parses form data in io with the given boundary

Since:

  • 0.6.0



395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
# File 'lib/yard/server/http_utils.rb', line 395

def parse_form_data(io, boundary)
  boundary_regexp = /\A--#{Regexp.quote(boundary)}(--)?#{CRLF}\z/
  form_data = Hash.new
  return form_data unless io
  data = nil
  io.each_line{|line|
    if boundary_regexp =~ line
      if data
        data.chop!
        key = data.name
        if form_data.has_key?(key)
          form_data[key].append_data(data)
        else
          form_data[key] = data
        end
      end
      data = FormData.new
      next
    else
      if data
        data << line
      end
    end
  }
  return form_data
end

.parse_header(raw) ⇒ Object

Parses an HTTP header raw into a hash of header fields with an Array of values.

Since:

  • 0.6.0



145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/yard/server/http_utils.rb', line 145

def parse_header(raw)
  header = Hash.new([].freeze)
  field = nil
  raw.each_line{|line|
    case line
    when /^([A-Za-z0-9!\#$%&'*+\-.^_`|~]+):\s*(.*?)\s*\z/om
      field, value = $1, $2
      field.downcase!
      header[field] = [] unless header.has_key?(field)
      header[field] << value
    when /^\s+(.*?)\s*\z/om
      value = $1
      unless field
        raise HTTPStatus::BadRequest, "bad header '#{line}'."
      end
      header[field][-1] << " " << value
    else
      raise HTTPStatus::BadRequest, "bad header '#{line}'."
    end
  }
  header.each{|key, values|
    values.each(&:strip!)
  }
  header
end

.parse_query(str) ⇒ Object

Parses the query component of a URI in str

Since:

  • 0.6.0



371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
# File 'lib/yard/server/http_utils.rb', line 371

def parse_query(str)
  query = Hash.new
  if str
    str.split(/[&;]/).each{|x|
      next if x.empty?
      key, val = x.split(/=/,2)
      key = unescape_form(key)
      val = unescape_form(val.to_s)
      val = FormData.new(val)
      val.name = key
      if query.has_key?(key)
        query[key].append_data(val)
        next
      end
      query[key] = val
    }
  end
  query
end

.parse_qvalues(value) ⇒ Object

Parses q values in value as used in Accept headers.

Since:

  • 0.6.0



202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
# File 'lib/yard/server/http_utils.rb', line 202

def parse_qvalues(value)
  tmp = []
  if value
    parts = value.split(/,\s*/)
    parts.each {|part|
      if m = %r{^([^\s,]+?)(?:;\s*q=(\d+(?:\.\d+)?))?$}.match(part)
        val = m[1]
        q = (m[2] or 1).to_f
        tmp.push([val, q])
      end
    }
    tmp = tmp.sort_by{|val, q| -q}
    tmp.collect!{|val, q| val}
  end
  return tmp
end

.parse_range_header(ranges_specifier) ⇒ Object

Parses a Range header value ranges_specifier

Since:

  • 0.6.0



184
185
186
187
188
189
190
191
192
193
194
195
196
# File 'lib/yard/server/http_utils.rb', line 184

def parse_range_header(ranges_specifier)
  if /^bytes=(.*)/ =~ ranges_specifier
    byte_range_set = split_header_value($1)
    byte_range_set.collect{|range_spec|
      case range_spec
      when /^(\d+)-(\d+)/ then $1.to_i .. $2.to_i
      when /^(\d+)-/      then $1.to_i .. -1
      when /^-(\d+)/      then -($1.to_i) .. -1
      else return nil
      end
    }
  end
end

.quote(str) ⇒ Object

Quotes and escapes quotes in str

Since:

  • 0.6.0



233
234
235
# File 'lib/yard/server/http_utils.rb', line 233

def quote(str)
  '"' << str.gsub(/[\\\"]/o, "\\\1") << '"'
end

.split_header_value(str) ⇒ Object

Splits a header value str according to HTTP specification.

Since:

  • 0.6.0



175
176
177
178
# File 'lib/yard/server/http_utils.rb', line 175

def split_header_value(str)
  str.scan(%r'\G((?:"(?:\\.|[^"])+?"|[^",]+)+)
                (?:,\s*|\Z)'xn).flatten
end

.unescape(str) ⇒ Object

Unescapes HTTP reserved and unwise characters in str

Since:

  • 0.6.0



474
475
476
# File 'lib/yard/server/http_utils.rb', line 474

def unescape(str)
  _unescape(str, ESCAPED)
end

.unescape_form(str) ⇒ Object

Unescapes form reserved characters in str

Since:

  • 0.6.0



490
491
492
# File 'lib/yard/server/http_utils.rb', line 490

def unescape_form(str)
  _unescape(str.gsub(/\+/, " "), ESCAPED)
end