Class: HTTPX::Transcoder::Multipart::Decoder

Inherits:
Object
  • Object
show all
Includes:
Utils
Defined in:
lib/httpx/transcoder/multipart/decoder.rb,
sig/transcoder/multipart.rbs

Constant Summary collapse

CRLF =

Returns:

  • (String)
"\r\n"
BOUNDARY_RE =

Returns:

  • (Regexp)
/;\s*boundary=([^;]+)/i.freeze
MULTIPART_CONTENT_TYPE =

Returns:

  • (Regexp)
/Content-Type: (.*)#{CRLF}/ni.freeze
MULTIPART_CONTENT_DISPOSITION =

Returns:

  • (Regexp)
/Content-Disposition:.*;\s*name=(#{VALUE})/ni.freeze
MULTIPART_CONTENT_ID =

Returns:

  • (Regexp)
/Content-ID:\s*([^#{CRLF}]*)/ni.freeze
WINDOW_SIZE =

Returns:

  • (Integer)
2 << 14

Constants included from Utils

Utils::FILENAME_EXTENSION_REGEX, Utils::FILENAME_REGEX, Utils::TOKEN, Utils::URIParser, Utils::VALUE

Instance Method Summary collapse

Methods included from Utils

elapsed_time, get_filename, #in_ractor?, now, parse_retry_after, #self?.elapsed_time, #self?.get_filename, #self?.in_ractor?, #self?.now, #self?.parse_retry_after, #self?.to_uri, to_uri

Constructor Details

#initialize(response) ⇒ Decoder

Returns a new instance of Decoder.

Parameters:



30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/httpx/transcoder/multipart/decoder.rb', line 30

def initialize(response)
  @boundary = begin
    m = response.headers["content-type"].to_s[BOUNDARY_RE, 1]
    raise Error, "no boundary declared in content-type header" unless m

    m.strip
  end
  @buffer = "".b
  @parts = {}
  @intermediate_boundary = "--#{@boundary}"
  @state = :idle
  @current = nil
end

Instance Method Details

#call(response) ⇒ Hash[String, untyped]

Parameters:

Returns:

  • (Hash[String, untyped])

Raises:



44
45
46
47
48
49
50
51
52
53
54
# File 'lib/httpx/transcoder/multipart/decoder.rb', line 44

def call(response, *)
  response.body.each do |chunk|
    @buffer << chunk

    parse
  end

  raise Error, "invalid or unsupported multipart format" unless @buffer.empty?

  @parts
end

#parsevoid

This method returns an undefined value.



58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/httpx/transcoder/multipart/decoder.rb', line 58

def parse
  case @state
  when :idle
    raise Error, "payload does not start with boundary" unless @buffer.start_with?("#{@intermediate_boundary}#{CRLF}")

    @buffer = @buffer.byteslice((@intermediate_boundary.bytesize + 2)..-1)

    @state = :part_header
  when :part_header
    idx = @buffer.index("#{CRLF}#{CRLF}")

    # raise Error, "couldn't parse part headers" unless idx
    return unless idx

    # @type var head: String
    head = @buffer.byteslice(0..(idx + 4 - 1))

    @buffer = @buffer.byteslice(head.bytesize..-1)

    content_type = head[MULTIPART_CONTENT_TYPE, 1] || "text/plain"
    if (name = head[MULTIPART_CONTENT_DISPOSITION, 1])
      name = /\A"(.*)"\Z/ =~ name ? Regexp.last_match(1) : name.dup
      name.gsub!(/\\(.)/, "\\1")
      name
    else
      name = head[MULTIPART_CONTENT_ID, 1]
    end

    filename = HTTPX::Utils.get_filename(head)

    name = filename || +"#{content_type}[]" if name.nil? || name.empty?

    @current = name

    @parts[name] = if filename
      FilePart.new(filename, content_type)
    else
      "".b
    end

    @state = :part_body
  when :part_body
    part = @parts[@current]

    body_separator = if part.is_a?(FilePart)
      "#{CRLF}#{CRLF}"
    else
      CRLF
    end
    idx = @buffer.index(body_separator)

    if idx
      payload = @buffer.byteslice(0..(idx - 1))
      @buffer = @buffer.byteslice((idx + body_separator.bytesize)..-1)
      part << payload
      part.rewind if part.respond_to?(:rewind)
      @state = :parse_boundary
    else
      part << @buffer
      @buffer.clear
    end
  when :parse_boundary
    raise Error, "payload does not start with boundary" unless @buffer.start_with?(@intermediate_boundary)

    @buffer = @buffer.byteslice(@intermediate_boundary.bytesize..-1)

    if @buffer == "--"
      @buffer.clear
      @state = :done
      return
    elsif @buffer.start_with?(CRLF)
      @buffer = @buffer.byteslice(2..-1)
      @state = :part_header
    else
      return
    end
  when :done
    raise Error, "parsing should have been over by now"
  end until @buffer.empty?
end