# 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

- ._escape(str, regex) ⇒ Object
- ._make_regex(str) ⇒ Object

  :stopdoc:.

- ._make_regex!(str) ⇒ Object
- ._unescape(str, regex) ⇒ Object
- .dequote(str) ⇒ Object

  Removes quotes and escapes from `str`.

- .escape(str) ⇒ Object

  Escapes HTTP reserved and unwise characters in `str`.

- .escape8bit(str) ⇒ Object

  Escapes 8 bit characters in `str`.

- .escape_form(str) ⇒ Object

  Escapes form reserved characters in `str`.

- .escape_path(str) ⇒ Object

  Escapes path `str`.

- .load_mime_types(file) ⇒ Object

  Loads Apache-compatible mime.types in `file`.

- .mime_type(filename, mime_tab) ⇒ Object

  Returns the mime type of `filename` from the list in `mime_tab`.

- .normalize_path(path) ⇒ Object

  Normalizes a request path.

- .parse_form_data(io, boundary) ⇒ Object

  Parses form data in `io` with the given `boundary`.

- .parse_header(raw) ⇒ Object

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

- .parse_query(str) ⇒ Object

  Parses the query component of a URI in `str`.

- .parse_qvalues(value) ⇒ Object

  Parses q values in `value` as used in Accept headers.

- .parse_range_header(ranges_specifier) ⇒ Object

  Parses a Range header value `ranges_specifier`.

- .quote(str) ⇒ Object

  Quotes and escapes quotes in `str`.

- .split_header_value(str) ⇒ Object

  Splits a header value `str` according to HTTP specification.

- .unescape(str) ⇒ Object

  Unescapes HTTP reserved and unwise characters in `str`.

- .unescape_form(str) ⇒ Object

  Unescapes form reserved characters in `str`.

## 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