Skip to content

Enable handling of multipart/form-data content#791

Open
weshatheleopard wants to merge 3 commits intobblimke:masterfrom
eligoenergy:master
Open

Enable handling of multipart/form-data content#791
weshatheleopard wants to merge 3 commits intobblimke:masterfrom
eligoenergy:master

Conversation

@weshatheleopard
Copy link
Copy Markdown

We desperately needed that since our project uses some external websites (that we cannot change) that utilize this kind of forms, and we needed to write tests for those. Since HTTP clients use automatically generated content delimeters, I replace them with constant strings to enable creation of tests that do not change.

@bblimke
Copy link
Copy Markdown
Owner

bblimke commented Dec 27, 2018

@weshatheleopard thanks for sharing this solution.

I'm not sure if this should be added to WebMock by default. This modifies request body and might be confusing. E.g. VCR gem uses request signatures created by WebMock.

I consider adding that as a configuration though.

@weshatheleopard
Copy link
Copy Markdown
Author

@bblimke As I said, we desperately needed this fixed so this is what I was able to throw together in the limited time frame. I might be able to revisit this issue later and modify the comparison routine to ignore the differences in boundary strings, but it will take much bigger intrusion in the code.

@weshatheleopard
Copy link
Copy Markdown
Author

@bblimke I studied the code some more and I'm afraid I need your expertise here. I am not sure how this can be handled on the VCR level since the external code (such as Mechanize) generates new random boundary with every call, so it is not possible to create one persistent signature; the only way to handle that seems to replace all the changing stuff to static values upon entering Webmock code, which is exactly what I do here.

@bblimke
Copy link
Copy Markdown
Owner

bblimke commented Dec 27, 2018

@weshatheleopard that's the reason why multipart has not been supported by WebMock.

I think your solution is clever and can work as a WebMock feature enabled with a configuration setting, after some small refactoring and specs coverage.

@kwstannard
Copy link
Copy Markdown

@bblimke Sorry for the format here, but I managed a solution that seems to work with RestClient's implementation. Here are the relevant monkey patches:

require 'webmock/request_signature'
require 'webmock/request_pattern'

WebMock::RequestPattern.define_method(:matches?) do |request_signature|
  content_type = request_signature.headers['Content-Type'] if request_signature.headers
  @method_pattern.matches?(request_signature.method) &&
    @uri_pattern.matches?(request_signature.uri) &&
    (@body_pattern.nil? || @body_pattern.matches?(request_signature.body, content_type || "")) &&
    (@headers_pattern.nil? || @headers_pattern.matches?(request_signature.headers)) &&
    (@with_block.nil? || @with_block.call(request_signature))
end

WebMock::BodyPattern::BODY_FORMATS["multipart/form-data"] = "multipart/form-data"
WebMock::BodyPattern.define_method(:assert_non_multipart_body) do |content_type|
  #noop
end
WebMock::BodyPattern.define_method(:body_as_hash) do |body, content_type|
  ct, boundary = content_type.split(/; boundary=/)
  case body_format(ct)
  when :json then
    WebMock::Util::JSON.parse(body)
  when :xml then
    Crack::XML.parse(body)
  when 'multipart/form-data' then

    body.gsub("\r", "").sub(/--\n\z/, "").split("--"+boundary).reduce({}) do |hash, part|
      next hash unless part.present?
      lines = part.strip.lines

      key = lines.shift.match(/; name="(\w*)"/) {|m| m[1].to_s }
      if part_content_type = lines.shift.match(/\AContent-Type: (\S+)/) {|m| m[1] }
        lines.shift
        value = body_as_hash(lines.join("\n"), part_content_type)
      else
        value = part.lines[2..-1].join("\n").strip
      end

      hash[key] = value
      hash

    end

  else
    WebMock::Util::QueryMapper.query_to_values(body, notation: Config.instance.query_values_notation)
  end
end

@bblimke
Copy link
Copy Markdown
Owner

bblimke commented Aug 19, 2023

@kwstannard thank you. I will revisit support for multipart and your changes when I find some time

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants