http-form_data-2.2.0/0000755000004100000410000000000013625471704014465 5ustar www-datawww-datahttp-form_data-2.2.0/.travis.yml0000644000004100000410000000076113625471704016602 0ustar www-datawww-datalanguage: ruby sudo: false cache: bundler before_install: - gem update --system - gem --version - gem install bundler --no-document - bundle --version install: bundle install --without development doc script: bundle exec rake env: JRUBY_OPTS="$JRUBY_OPTS --debug" rvm: # Include JRuby first because it takes the longest - jruby-9.1.13.0 - 2.4 - 2.5 - 2.6 - 2.7 matrix: fast_finish: true include: - rvm: 2.7 env: SUITE="rubocop" branches: only: - master http-form_data-2.2.0/.rspec0000644000004100000410000000003613625471704015601 0ustar www-datawww-data--color --require spec_helper http-form_data-2.2.0/README.md0000644000004100000410000000544113625471704015750 0ustar www-datawww-data# HTTP::FormData [![Gem Version](https://badge.fury.io/rb/http-form_data.svg)](http://rubygems.org/gems/http-form_data) [![Build Status](https://secure.travis-ci.org/httprb/form_data.svg?branch=master)](http://travis-ci.org/httprb/form_data) [![Code Climate](https://codeclimate.com/github/httprb/form_data.svg)](https://codeclimate.com/github/httprb/form_data) [![Coverage Status](https://coveralls.io/repos/httprb/form_data.rb/badge.svg?branch=master)](https://coveralls.io/r/httprb/form_data.rb) Utility-belt to build form data request bodies. ## Installation Add this line to your application's Gemfile: ```ruby gem 'http-form_data' ``` And then execute: $ bundle Or install it yourself as: $ gem install http-form_data ## Usage ``` ruby require "http/form_data" form = HTTP::FormData.create({ :username => "ixti", :avatar_file => HTTP::FormData::File.new("/home/ixti/avatar.png") }) # Assuming socket is an open socket to some HTTP server socket << "POST /some-url HTTP/1.1\r\n" socket << "Host: example.com\r\n" socket << "Content-Type: #{form.content_type}\r\n" socket << "Content-Length: #{form.content_length}\r\n" socket << "\r\n" socket << form.to_s ``` It's also possible to create a non-file part with Content-Type: ``` ruby form = HTTP::FormData.create({ :username => HTTP::FormData::Part.new('{"a": 1}', content_type: 'application/json'), :avatar_file => HTTP::FormData::File.new("/home/ixti/avatar.png") }) ``` ## Supported Ruby Versions This library aims to support and is [tested against][ci] the following Ruby versions: * Ruby 2.4.x * Ruby 2.5.x * Ruby 2.6.x * Ruby 2.7.x * JRuby 9.1.x.x If something doesn't work on one of these versions, it's a bug. This library may inadvertently work (or seem to work) on other Ruby versions, however support will only be provided for the versions listed above. If you would like this library to support another Ruby version or implementation, you may volunteer to be a maintainer. Being a maintainer entails making sure all tests run and pass on that implementation. When something breaks on your implementation, you will be responsible for providing patches in a timely fashion. If critical issues for a particular implementation exist at the time of a major release, support for that Ruby version may be dropped. ## Contributing 1. Fork it ( https://github.com/httprb/form_data.rb/fork ) 2. Create your feature branch (`git checkout -b my-new-feature`) 3. Commit your changes (`git commit -am 'Add some feature'`) 4. Push to the branch (`git push origin my-new-feature`) 5. Create a new Pull Request ## Copyright Copyright (c) 2015-2017 Alexey V Zapparov. See [LICENSE.txt][license] for further details. [ci]: http://travis-ci.org/httprb/form_data.rb [license]: https://github.com/httprb/form_data.rb/blob/master/LICENSE.txt http-form_data-2.2.0/spec/0000755000004100000410000000000013625471704015417 5ustar www-datawww-datahttp-form_data-2.2.0/spec/fixtures/0000755000004100000410000000000013625471704017270 5ustar www-datawww-datahttp-form_data-2.2.0/spec/fixtures/the-http-gem.info0000644000004100000410000000011513625471704022445 0ustar www-datawww-dataThe HTTP Gem is an easy-to-use client library for making requests from Ruby. http-form_data-2.2.0/spec/fixtures/expected-multipart-body.tpl0000644000004100000410000000000013625471704024552 0ustar www-datawww-datahttp-form_data-2.2.0/spec/lib/0000755000004100000410000000000013625471704016165 5ustar www-datawww-datahttp-form_data-2.2.0/spec/lib/http/0000755000004100000410000000000013625471704017144 5ustar www-datawww-datahttp-form_data-2.2.0/spec/lib/http/form_data/0000755000004100000410000000000013625471704021100 5ustar www-datawww-datahttp-form_data-2.2.0/spec/lib/http/form_data/composite_io_spec.rb0000644000004100000410000000616213625471704025135 0ustar www-datawww-data# frozen_string_literal: true RSpec.describe HTTP::FormData::CompositeIO do subject(:composite_io) { HTTP::FormData::CompositeIO.new(ios) } let(:ios) { ["Hello", " ", "", "world", "!"].map { |s| StringIO.new(s) } } describe "#initialize" do it "accepts IOs and strings" do io = HTTP::FormData::CompositeIO.new(["Hello ", StringIO.new("world!")]) expect(io.read).to eq "Hello world!" end it "fails if an IO is neither a String nor an IO" do expect { HTTP::FormData::CompositeIO.new %i[hello world] } .to raise_error(ArgumentError) end end describe "#read" do it "reads all data" do expect(composite_io.read).to eq "Hello world!" end it "reads partial data" do expect(composite_io.read(3)).to eq "Hel" expect(composite_io.read(2)).to eq "lo" expect(composite_io.read(1)).to eq " " expect(composite_io.read(6)).to eq "world!" end it "returns empty string when no data was retrieved" do composite_io.read expect(composite_io.read).to eq "" end it "returns nil when no partial data was retrieved" do composite_io.read expect(composite_io.read(3)).to eq nil end it "reads partial data with a buffer" do outbuf = String.new expect(composite_io.read(3, outbuf)).to eq "Hel" expect(composite_io.read(2, outbuf)).to eq "lo" expect(composite_io.read(1, outbuf)).to eq " " expect(composite_io.read(6, outbuf)).to eq "world!" end it "fills the buffer with retrieved content" do outbuf = String.new composite_io.read(3, outbuf) expect(outbuf).to eq "Hel" composite_io.read(2, outbuf) expect(outbuf).to eq "lo" composite_io.read(1, outbuf) expect(outbuf).to eq " " composite_io.read(6, outbuf) expect(outbuf).to eq "world!" end it "returns nil when no partial data was retrieved with a buffer" do outbuf = String.new("content") composite_io.read expect(composite_io.read(3, outbuf)).to eq nil expect(outbuf).to eq "" end it "returns data in binary encoding" do io = HTTP::FormData::CompositeIO.new(%w[Janko Marohnić]) expect(io.read(5).encoding).to eq Encoding::BINARY expect(io.read(9).encoding).to eq Encoding::BINARY io.rewind expect(io.read.encoding).to eq Encoding::BINARY expect(io.read.encoding).to eq Encoding::BINARY end it "reads data in bytes" do emoji = "😃" io = HTTP::FormData::CompositeIO.new([emoji]) expect(io.read(1)).to eq emoji.b[0] expect(io.read(1)).to eq emoji.b[1] expect(io.read(1)).to eq emoji.b[2] expect(io.read(1)).to eq emoji.b[3] end end describe "#rewind" do it "rewinds all IOs" do composite_io.read composite_io.rewind expect(composite_io.read).to eq "Hello world!" end end describe "#size" do it "returns sum of all IO sizes" do expect(composite_io.size).to eq 12 end it "returns 0 when there are no IOs" do empty_composite_io = HTTP::FormData::CompositeIO.new [] expect(empty_composite_io.size).to eq 0 end end end http-form_data-2.2.0/spec/lib/http/form_data/file_spec.rb0000644000004100000410000001412413625471704023360 0ustar www-datawww-data# frozen_string_literal: true # coding: utf-8 RSpec.describe HTTP::FormData::File do let(:opts) { nil } let(:form_file) { described_class.new(file, opts) } describe "#size" do subject { form_file.size } context "when file given as a String" do let(:file) { fixture("the-http-gem.info").to_s } it { is_expected.to eq fixture("the-http-gem.info").size } end context "when file given as a Pathname" do let(:file) { fixture("the-http-gem.info") } it { is_expected.to eq fixture("the-http-gem.info").size } end context "when file given as File" do let(:file) { fixture("the-http-gem.info").open } after { file.close } it { is_expected.to eq fixture("the-http-gem.info").size } end context "when file given as IO" do let(:file) { StringIO.new "привет мир!" } it { is_expected.to eq 20 } end end describe "#to_s" do subject { form_file.to_s } context "when file given as a String" do let(:file) { fixture("the-http-gem.info").to_s } it { is_expected.to eq fixture("the-http-gem.info").read(:mode => "rb") } it "rewinds content" do content = form_file.read expect(form_file.to_s).to eq content expect(form_file.read).to eq content end end context "when file given as a Pathname" do let(:file) { fixture("the-http-gem.info") } it { is_expected.to eq fixture("the-http-gem.info").read(:mode => "rb") } it "rewinds content" do content = form_file.read expect(form_file.to_s).to eq content expect(form_file.read).to eq content end end context "when file given as File" do let(:file) { fixture("the-http-gem.info").open("rb") } after { file.close } it { is_expected.to eq fixture("the-http-gem.info").read(:mode => "rb") } it "rewinds content" do content = form_file.read expect(form_file.to_s).to eq content expect(form_file.read).to eq content end end context "when file given as IO" do let(:file) { StringIO.new "привет мир!" } it { is_expected.to eq "привет мир!" } it "rewinds content" do content = form_file.read expect(form_file.to_s).to eq content expect(form_file.read).to eq content end end end describe "#read" do subject { form_file.read } context "when file given as a String" do let(:file) { fixture("the-http-gem.info").to_s } it { is_expected.to eq fixture("the-http-gem.info").read(:mode => "rb") } end context "when file given as a Pathname" do let(:file) { fixture("the-http-gem.info") } it { is_expected.to eq fixture("the-http-gem.info").read(:mode => "rb") } end context "when file given as File" do let(:file) { fixture("the-http-gem.info").open("rb") } after { file.close } it { is_expected.to eq fixture("the-http-gem.info").read(:mode => "rb") } end context "when file given as IO" do let(:file) { StringIO.new "привет мир!" } it { is_expected.to eq "привет мир!" } end end describe "#rewind" do context "when file given as a String" do let(:file) { fixture("the-http-gem.info").to_s } it "rewinds the underlying IO object" do content = form_file.read form_file.rewind expect(form_file.read).to eq content end end context "when file given as a Pathname" do let(:file) { fixture("the-http-gem.info") } it "rewinds the underlying IO object" do content = form_file.read form_file.rewind expect(form_file.read).to eq content end end context "when file given as File" do let(:file) { fixture("the-http-gem.info").open("rb") } after { file.close } it "rewinds the underlying IO object" do content = form_file.read form_file.rewind expect(form_file.read).to eq content end end context "when file given as IO" do let(:file) { StringIO.new "привет мир!" } it "rewinds the underlying IO object" do content = form_file.read form_file.rewind expect(form_file.read).to eq content end end end describe "#filename" do subject { form_file.filename } context "when file given as a String" do let(:file) { fixture("the-http-gem.info").to_s } it { is_expected.to eq ::File.basename file } context "and filename given with options" do let(:opts) { { :filename => "foobar.txt" } } it { is_expected.to eq "foobar.txt" } end end context "when file given as a Pathname" do let(:file) { fixture("the-http-gem.info") } it { is_expected.to eq ::File.basename file } context "and filename given with options" do let(:opts) { { :filename => "foobar.txt" } } it { is_expected.to eq "foobar.txt" } end end context "when file given as File" do let(:file) { fixture("the-http-gem.info").open } after { file.close } it { is_expected.to eq "the-http-gem.info" } context "and filename given with options" do let(:opts) { { :filename => "foobar.txt" } } it { is_expected.to eq "foobar.txt" } end end context "when file given as IO" do let(:file) { StringIO.new } it { is_expected.to eq "stream-#{file.object_id}" } context "and filename given with options" do let(:opts) { { :filename => "foobar.txt" } } it { is_expected.to eq "foobar.txt" } end end end describe "#content_type" do let(:file) { StringIO.new } subject { form_file.content_type } it { is_expected.to eq "application/octet-stream" } context "when it was given with options" do let(:opts) { { :content_type => "application/json" } } it { is_expected.to eq "application/json" } end end describe "#mime_type" do it "should be an alias of #content_type" do expect(described_class.instance_method(:mime_type)) .to eq(described_class.instance_method(:content_type)) end end end http-form_data-2.2.0/spec/lib/http/form_data/multipart_spec.rb0000644000004100000410000001100213625471704024452 0ustar www-datawww-data# frozen_string_literal: true RSpec.describe HTTP::FormData::Multipart do subject(:form_data) { HTTP::FormData::Multipart.new params } let(:file) { HTTP::FormData::File.new fixture "the-http-gem.info" } let(:params) { { :foo => :bar, :baz => file } } let(:boundary) { /-{21}[a-f0-9]{42}/ } describe "#to_s" do def disposition(params) params = params.map { |k, v| "#{k}=#{v.inspect}" }.join("; ") "Content-Disposition: form-data; #{params}" end let(:crlf) { "\r\n" } it "properly generates multipart data" do boundary_value = form_data.boundary expect(form_data.to_s).to eq [ "--#{boundary_value}#{crlf}", "#{disposition 'name' => 'foo'}#{crlf}", "#{crlf}bar#{crlf}", "--#{boundary_value}#{crlf}", "#{disposition 'name' => 'baz', 'filename' => file.filename}#{crlf}", "Content-Type: #{file.content_type}#{crlf}", "#{crlf}#{file}#{crlf}", "--#{boundary_value}--#{crlf}" ].join("") end it "rewinds content" do content = form_data.read expect(form_data.to_s).to eq content expect(form_data.read).to eq content end context "with user-defined boundary" do subject(:form_data) do HTTP::FormData::Multipart.new params, :boundary => "my-boundary" end it "uses the given boundary" do expect(form_data.to_s).to eq [ "--my-boundary#{crlf}", "#{disposition 'name' => 'foo'}#{crlf}", "#{crlf}bar#{crlf}", "--my-boundary#{crlf}", "#{disposition 'name' => 'baz', 'filename' => file.filename}#{crlf}", "Content-Type: #{file.content_type}#{crlf}", "#{crlf}#{file}#{crlf}", "--my-boundary--#{crlf}" ].join("") end end context "with filename set to nil" do let(:part) { HTTP::FormData::Part.new("s", :content_type => "mime/type") } let(:form_data) { HTTP::FormData::Multipart.new({ :foo => part }) } it "doesn't include a filename" do boundary_value = form_data.content_type[/(#{boundary})$/, 1] expect(form_data.to_s).to eq [ "--#{boundary_value}#{crlf}", "#{disposition 'name' => 'foo'}#{crlf}", "Content-Type: #{part.content_type}#{crlf}", "#{crlf}s#{crlf}", "--#{boundary_value}--#{crlf}" ].join("") end end context "with content type set to nil" do let(:part) { HTTP::FormData::Part.new("s") } let(:form_data) { HTTP::FormData::Multipart.new({ :foo => part }) } it "doesn't include a filename" do boundary_value = form_data.content_type[/(#{boundary})$/, 1] expect(form_data.to_s).to eq [ "--#{boundary_value}#{crlf}", "#{disposition 'name' => 'foo'}#{crlf}", "#{crlf}s#{crlf}", "--#{boundary_value}--#{crlf}" ].join("") end end end describe "#size" do it "returns bytesize of multipart data" do expect(form_data.size).to eq form_data.to_s.bytesize end end describe "#read" do it "returns multipart data" do expect(form_data.read).to eq form_data.to_s end end describe "#rewind" do it "rewinds the multipart data IO" do form_data.read form_data.rewind expect(form_data.read).to eq form_data.to_s end end describe "#content_type" do subject { form_data.content_type } let(:content_type) { %r{^multipart\/form-data; boundary=#{boundary}$} } it { is_expected.to match(content_type) } context "with user-defined boundary" do let(:form_data) do HTTP::FormData::Multipart.new params, :boundary => "my-boundary" end it "includes the given boundary" do expect(form_data.content_type) .to eq "multipart/form-data; boundary=my-boundary" end end end describe "#content_length" do subject { form_data.content_length } it { is_expected.to eq form_data.to_s.bytesize } end describe "#boundary" do it "returns a new boundary" do expect(form_data.boundary).to match(boundary) end context "with user-defined boundary" do let(:form_data) do HTTP::FormData::Multipart.new params, :boundary => "my-boundary" end it "returns the given boundary" do expect(form_data.boundary).to eq "my-boundary" end end end describe ".generate_boundary" do it "returns a string suitable as a multipart boundary" do expect(form_data.class.generate_boundary).to match(boundary) end end end http-form_data-2.2.0/spec/lib/http/form_data/urlencoded_spec.rb0000644000004100000410000000336013625471704024565 0ustar www-datawww-data# frozen_string_literal: true # coding: utf-8 RSpec.describe HTTP::FormData::Urlencoded do let(:data) { { "foo[bar]" => "test" } } subject(:form_data) { HTTP::FormData::Urlencoded.new data } describe "#content_type" do subject { form_data.content_type } it { is_expected.to eq "application/x-www-form-urlencoded" } end describe "#content_length" do subject { form_data.content_length } it { is_expected.to eq form_data.to_s.bytesize } context "with unicode chars" do let(:data) { { "foo[bar]" => "тест" } } it { is_expected.to eq form_data.to_s.bytesize } end end describe "#to_s" do subject { form_data.to_s } it { is_expected.to eq "foo%5Bbar%5D=test" } context "with unicode chars" do let(:data) { { "foo[bar]" => "тест" } } it { is_expected.to eq "foo%5Bbar%5D=%D1%82%D0%B5%D1%81%D1%82" } end it "rewinds content" do content = form_data.read expect(form_data.to_s).to eq content expect(form_data.read).to eq content end end describe "#size" do it "returns bytesize of multipart data" do expect(form_data.size).to eq form_data.to_s.bytesize end end describe "#read" do it "returns multipart data" do expect(form_data.read).to eq form_data.to_s end end describe "#rewind" do it "rewinds the multipart data IO" do form_data.read form_data.rewind expect(form_data.read).to eq form_data.to_s end end describe ".encoder=" do before { described_class.encoder = ::JSON.method(:dump) } after { described_class.encoder = ::URI.method(:encode_www_form) } it "switches form encoder implementation" do expect(form_data.to_s).to eq('{"foo[bar]":"test"}') end end end http-form_data-2.2.0/spec/lib/http/form_data/part_spec.rb0000644000004100000410000000330013625471704023401 0ustar www-datawww-data# frozen_string_literal: true RSpec.describe HTTP::FormData::Part do let(:body) { "" } let(:opts) { {} } subject(:part) { HTTP::FormData::Part.new(body, **opts) } describe "#size" do subject { part.size } context "when body given as a String" do let(:body) { "привет мир!" } it { is_expected.to eq 20 } end end describe "#to_s" do subject! { part.to_s } context "when body given as String" do let(:body) { "привет мир!" } it { is_expected.to eq "привет мир!" } it "rewinds content" do content = part.read expect(part.to_s).to eq content expect(part.read).to eq content end end end describe "#read" do subject { part.read } context "when body given as String" do let(:body) { "привет мир!" } it { is_expected.to eq "привет мир!" } end end describe "#rewind" do context "when body given as String" do let(:body) { "привет мир!" } it "rewinds the underlying IO object" do part.read part.rewind expect(part.read).to eq "привет мир!" end end end describe "#filename" do subject { part.filename } it { is_expected.to eq nil } context "when it was given with options" do let(:opts) { { :filename => "foobar.txt" } } it { is_expected.to eq "foobar.txt" } end end describe "#content_type" do subject { part.content_type } it { is_expected.to eq nil } context "when it was given with options" do let(:opts) { { :content_type => "application/json" } } it { is_expected.to eq "application/json" } end end end http-form_data-2.2.0/spec/lib/http/form_data_spec.rb0000644000004100000410000000271713625471704022446 0ustar www-datawww-data# frozen_string_literal: true RSpec.describe HTTP::FormData do describe ".create" do subject { HTTP::FormData.create params } context "when form has no files" do let(:params) { { :foo => :bar } } it { is_expected.to be_a HTTP::FormData::Urlencoded } end context "when form has at least one file param" do let(:file) { HTTP::FormData::File.new(fixture("the-http-gem.info").to_s) } let(:params) { { :foo => :bar, :baz => file } } it { is_expected.to be_a HTTP::FormData::Multipart } end context "when form has file in an array param" do let(:file) { HTTP::FormData::File.new(fixture("the-http-gem.info").to_s) } let(:params) { { :foo => :bar, :baz => [file] } } it { is_expected.to be_a HTTP::FormData::Multipart } end end describe ".ensure_hash" do subject(:ensure_hash) { HTTP::FormData.ensure_hash data } context "when Hash given" do let(:data) { { :foo => :bar } } it { is_expected.to eq :foo => :bar } end context "when #to_h given" do let(:data) { double(:to_h => { :foo => :bar }) } it { is_expected.to eq :foo => :bar } end context "when nil given" do let(:data) { nil } it { is_expected.to eq({}) } end context "when neither Hash nor #to_h given" do let(:data) { double } it "fails with HTTP::FormData::Error" do expect { ensure_hash }.to raise_error HTTP::FormData::Error end end end end http-form_data-2.2.0/spec/spec_helper.rb0000644000004100000410000000624213625471704020241 0ustar www-datawww-data# frozen_string_literal: true require "simplecov" require "coveralls" SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new([ SimpleCov::Formatter::HTMLFormatter, Coveralls::SimpleCov::Formatter ]) SimpleCov.start { add_filter "/spec/" } require "http/form_data" require "support/fixtures_helper" # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration RSpec.configure do |config| config.expect_with :rspec do |expectations| # This option will default to `true` in RSpec 4. It makes the `description` # and `failure_message` of custom matchers include text for helper methods # defined using `chain`, e.g.: # be_bigger_than(2).and_smaller_than(4).description # # => "be bigger than 2 and smaller than 4" # ...rather than: # # => "be bigger than 2" expectations.include_chain_clauses_in_custom_matcher_descriptions = true end config.mock_with :rspec do |mocks| # Prevents you from mocking or stubbing a method that does not exist on # a real object. This is generally recommended, and will default to # `true` in RSpec 4. mocks.verify_partial_doubles = true end # These two settings work together to allow you to limit a spec run # to individual examples or groups you care about by tagging them with # `:focus` metadata. When nothing is tagged with `:focus`, all examples # get run. config.filter_run :focus config.run_all_when_everything_filtered = true # Limits the available syntax to the non-monkey patched syntax that is # recommended. For more details, see: # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ # - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching config.disable_monkey_patching! # This setting enables warnings. It's recommended, but in some cases may # be too noisy due to issues in dependencies. config.warnings = true # Many RSpec users commonly either run the entire suite or an individual # file, and it's useful to allow more verbose output when running an # individual spec file. if config.files_to_run.one? # Use the documentation formatter for detailed output, # unless a formatter has already been configured # (e.g. via a command-line flag). config.default_formatter = "doc" end # Print the 10 slowest examples and example groups at the # end of the spec run, to help surface which specs are running # particularly slow. config.profile_examples = 10 # Run specs in random order to surface order dependencies. If you find an # order dependency and want to debug it, you can fix the order by providing # the seed, which is printed after each run. # --seed 1234 config.order = :random # Seed global randomization in this process using the `--seed` CLI option. # Setting this allows you to use `--seed` to deterministically reproduce # test failures related to randomization by passing the same `--seed` value # as the one that triggered the failure. Kernel.srand config.seed # Include common helpers config.include FixturesHelper end http-form_data-2.2.0/spec/support/0000755000004100000410000000000013625471704017133 5ustar www-datawww-datahttp-form_data-2.2.0/spec/support/fixtures_helper.rb0000644000004100000410000000036413625471704022673 0ustar www-datawww-data# frozen_string_literal: true require "pathname" module FixturesHelper def fixture(filename) fixtures_root.join filename end def fixtures_root @fixtures_root ||= Pathname.new(__FILE__).join("../../fixtures").realpath end end http-form_data-2.2.0/.rubocop.yml0000644000004100000410000000231313625471704016736 0ustar www-datawww-dataAllCops: DisplayCopNames: true TargetRubyVersion: 2.4 ## Metrics ##################################################################### Metrics/BlockLength: Exclude: - "Guardfile" - "spec/**/*" Metrics/MethodLength: CountComments: false Max: 15 ## Styles ###################################################################### Style/AlignParameters: EnforcedStyle: with_fixed_indentation Style/BracesAroundHashParameters: Enabled: false Style/EmptyLineBetweenDefs: AllowAdjacentOneLineDefs: true Style/Encoding: EnforcedStyle: when_needed Style/HashSyntax: EnforcedStyle: hash_rockets Style/IndentHash: EnforcedStyle: consistent Style/IndentArray: EnforcedStyle: consistent # New lambda syntax is as ugly to me as new syntax of Hash. Style/Lambda: Enabled: false Style/MultilineOperationIndentation: EnforcedStyle: indented Style/StringLiterals: EnforcedStyle: double_quotes Style/EmptyCaseCondition: Enabled: false # Not all trivial readers/writers can be defined with attr_* methods # # class Example < SimpleDelegator # def __getobj__ # @obj # end # # def __setobj__(obj) # @obj = obj # end # end Style/TrivialAccessors: Enabled: false http-form_data-2.2.0/.gitignore0000644000004100000410000000020513625471704016452 0ustar www-datawww-data/.bundle/ /.yardoc /.ruby-version /Gemfile.lock /_yardoc/ /coverage/ /doc/ /pkg/ /spec/reports/ /tmp/ *.bundle *.so *.o *.a mkmf.log http-form_data-2.2.0/.editorconfig0000644000004100000410000000022313625471704017137 0ustar www-datawww-dataroot = true [*] indent_style = space indent_size = 2 end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true http-form_data-2.2.0/http-form_data.gemspec0000644000004100000410000000175313625471704020751 0ustar www-datawww-data# frozen_string_literal: true lib = File.expand_path("../lib", __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require "http/form_data/version" Gem::Specification.new do |spec| spec.name = "http-form_data" spec.version = HTTP::FormData::VERSION spec.homepage = "https://github.com/httprb/form_data.rb" spec.authors = ["Aleksey V Zapparov"] spec.email = ["ixti@member.fsf.org"] spec.license = "MIT" spec.summary = "http-form_data-#{HTTP::FormData::VERSION}" spec.description = <<-DESC.gsub(/^\s+> /m, "").tr("\n", " ").strip > Utility-belt to build form data request bodies. > Provides support for `application/x-www-form-urlencoded` and > `multipart/form-data` types. DESC spec.files = `git ls-files -z`.split("\x0") spec.executables = spec.files.grep(%r{^bin\/}).map { |f| File.basename(f) } spec.test_files = spec.files.grep(%r{^(test|spec|features)\/}) spec.require_paths = ["lib"] end http-form_data-2.2.0/Rakefile0000644000004100000410000000067613625471704016143 0ustar www-datawww-data# frozen_string_literal: true require "bundler/gem_tasks" require "rspec/core/rake_task" RSpec::Core::RakeTask.new begin require "rubocop/rake_task" RuboCop::RakeTask.new rescue LoadError task :rubocop do $stderr.puts "RuboCop is disabled" end end if ENV["CI"].nil? task :default => %i[spec rubocop] else case ENV["SUITE"] when "rubocop" then task :default => :rubocop else task :default => :spec end end http-form_data-2.2.0/lib/0000755000004100000410000000000013625471704015233 5ustar www-datawww-datahttp-form_data-2.2.0/lib/http/0000755000004100000410000000000013625471704016212 5ustar www-datawww-datahttp-form_data-2.2.0/lib/http/form_data/0000755000004100000410000000000013625471704020146 5ustar www-datawww-datahttp-form_data-2.2.0/lib/http/form_data/version.rb0000644000004100000410000000016013625471704022155 0ustar www-datawww-data# frozen_string_literal: true module HTTP module FormData # Gem version. VERSION = "2.2.0" end end http-form_data-2.2.0/lib/http/form_data/urlencoded.rb0000644000004100000410000000443113625471704022621 0ustar www-datawww-data# frozen_string_literal: true require "http/form_data/readable" require "uri" require "stringio" module HTTP module FormData # `application/x-www-form-urlencoded` form data. class Urlencoded include Readable class << self # Set custom form data encoder implementation. # # @example # # module CustomFormDataEncoder # UNESCAPED_CHARS = /[^a-z0-9\-\.\_\~]/i # # def self.escape(s) # ::URI::DEFAULT_PARSER.escape(s.to_s, UNESCAPED_CHARS) # end # # def self.call(data) # parts = [] # # data.each do |k, v| # k = escape(k) # # if v.nil? # parts << k # elsif v.respond_to?(:to_ary) # v.to_ary.each { |vv| parts << "#{k}=#{escape vv}" } # else # parts << "#{k}=#{escape v}" # end # end # # parts.join("&") # end # end # # HTTP::FormData::Urlencoded.encoder = CustomFormDataEncoder # # @raise [ArgumentError] if implementation deos not responds to `#call`. # @param implementation [#call] # @return [void] def encoder=(implementation) raise ArgumentError unless implementation.respond_to? :call @encoder = implementation end # Returns form data encoder implementation. # Default: `URI.encode_www_form`. # # @see .encoder= # @return [#call] def encoder @encoder ||= ::URI.method(:encode_www_form) end end # @param [#to_h, Hash] data form data key-value Hash def initialize(data) @io = StringIO.new(self.class.encoder.call(FormData.ensure_hash(data))) end # Returns MIME type to be used for HTTP request `Content-Type` header. # # @return [String] def content_type "application/x-www-form-urlencoded" end # Returns form data content size to be used for HTTP request # `Content-Length` header. # # @return [Integer] alias content_length size end end end http-form_data-2.2.0/lib/http/form_data/part.rb0000644000004100000410000000137013625471704021442 0ustar www-datawww-data# frozen_string_literal: true require "stringio" require "http/form_data/readable" module HTTP module FormData # Represents a body part of multipart/form-data request. # # @example Usage with String # # body = "Message" # FormData::Part.new body, :content_type => 'foobar.txt; charset="UTF-8"' class Part include Readable attr_reader :content_type, :filename # @param [#to_s] body # @param [String] content_type Value of Content-Type header # @param [String] filename Value of filename parameter def initialize(body, content_type: nil, filename: nil) @io = StringIO.new(body.to_s) @content_type = content_type @filename = filename end end end end http-form_data-2.2.0/lib/http/form_data/readable.rb0000644000004100000410000000143713625471704022237 0ustar www-datawww-data# frozen_string_literal: true module HTTP module FormData # Common behaviour for objects defined by an IO object. module Readable # Returns IO content. # # @return [String] def to_s rewind content = read rewind content end # Reads and returns part of IO content. # # @param [Integer] length Number of bytes to retrieve # @param [String] outbuf String to be replaced with retrieved data # # @return [String, nil] def read(length = nil, outbuf = nil) @io.read(length, outbuf) end # Returns IO size. # # @return [Integer] def size @io.size end # Rewinds the IO. def rewind @io.rewind end end end end http-form_data-2.2.0/lib/http/form_data/multipart/0000755000004100000410000000000013625471704022167 5ustar www-datawww-datahttp-form_data-2.2.0/lib/http/form_data/multipart/param.rb0000644000004100000410000000435413625471704023622 0ustar www-datawww-data# frozen_string_literal: true require "http/form_data/readable" require "http/form_data/composite_io" module HTTP module FormData class Multipart # Utility class to represent multi-part chunks class Param include Readable # Initializes body part with headers and data. # # @example With {FormData::File} value # # Content-Disposition: form-data; name="avatar"; filename="avatar.png" # Content-Type: application/octet-stream # # ...data of avatar.png... # # @example With non-{FormData::File} value # # Content-Disposition: form-data; name="username" # # ixti # # @return [String] # @param [#to_s] name # @param [FormData::File, FormData::Part, #to_s] value def initialize(name, value) @name = name.to_s @part = if value.is_a?(FormData::Part) value else FormData::Part.new(value) end @io = CompositeIO.new [header, @part, footer] end # Flattens given `data` Hash into an array of `Param`'s. # Nested array are unwinded. # Behavior is similar to `URL.encode_www_form`. # # @param [Hash] data # @return [Array] def self.coerce(data) params = [] data.each do |name, values| Array(values).each do |value| params << new(name, value) end end params end private def header header = "".b header << "Content-Disposition: form-data; #{parameters}#{CRLF}" header << "Content-Type: #{content_type}#{CRLF}" if content_type header << CRLF header end def parameters parameters = { :name => @name } parameters[:filename] = filename if filename parameters.map { |k, v| "#{k}=#{v.inspect}" }.join("; ") end def content_type @part.content_type end def filename @part.filename end def footer CRLF.dup end end end end end http-form_data-2.2.0/lib/http/form_data/multipart.rb0000644000004100000410000000261313625471704022516 0ustar www-datawww-data# frozen_string_literal: true require "securerandom" require "http/form_data/multipart/param" require "http/form_data/readable" require "http/form_data/composite_io" module HTTP module FormData # `multipart/form-data` form data. class Multipart include Readable attr_reader :boundary # @param [#to_h, Hash] data form data key-value Hash def initialize(data, boundary: self.class.generate_boundary) parts = Param.coerce FormData.ensure_hash data @boundary = boundary.to_s.freeze @io = CompositeIO.new [*parts.flat_map { |part| [glue, part] }, tail] end # Generates a string suitable for using as a boundary in multipart form # data. # # @return [String] def self.generate_boundary ("-" * 21) << SecureRandom.hex(21) end # Returns MIME type to be used for HTTP request `Content-Type` header. # # @return [String] def content_type "multipart/form-data; boundary=#{@boundary}" end # Returns form data content size to be used for HTTP request # `Content-Length` header. # # @return [Integer] alias content_length size private # @return [String] def glue @glue ||= "--#{@boundary}#{CRLF}" end # @return [String] def tail @tail ||= "--#{@boundary}--#{CRLF}" end end end end http-form_data-2.2.0/lib/http/form_data/composite_io.rb0000644000004100000410000000415413625471704023170 0ustar www-datawww-data# frozen_string_literal: true require "stringio" module HTTP module FormData # Provides IO interface across multiple IO objects. class CompositeIO # @param [Array] ios Array of IO objects def initialize(ios) @index = 0 @buffer = "".b @ios = ios.map do |io| if io.is_a?(String) StringIO.new(io) elsif io.respond_to?(:read) io else raise ArgumentError, "#{io.inspect} is neither a String nor an IO object" end end end # Reads and returns partial content acrosss multiple IO objects. # # @param [Integer] length Number of bytes to retrieve # @param [String] outbuf String to be replaced with retrieved data # # @return [String, nil] def read(length = nil, outbuf = nil) data = outbuf.clear.force_encoding(Encoding::BINARY) if outbuf data ||= "".b read_chunks(length) { |chunk| data << chunk } data unless length && data.empty? end # Returns sum of all IO sizes. def size @size ||= @ios.map(&:size).inject(0, :+) end # Rewinds all IO objects and set cursor to the first IO object. def rewind @ios.each(&:rewind) @index = 0 end private # Yields chunks with total length up to `length`. def read_chunks(length = nil) while (chunk = readpartial(length)) yield chunk.force_encoding(Encoding::BINARY) next if length.nil? length -= chunk.bytesize break if length.zero? end end # Reads chunk from current IO with length up to `max_length`. def readpartial(max_length = nil) while current_io chunk = current_io.read(max_length, @buffer) return chunk if chunk && !chunk.empty? advance_io end end # Returns IO object under the cursor. def current_io @ios[@index] end # Advances cursor to the next IO object. def advance_io @index += 1 end end end end http-form_data-2.2.0/lib/http/form_data/file.rb0000644000004100000410000000370613625471704021420 0ustar www-datawww-data# frozen_string_literal: true module HTTP module FormData # Represents file form param. # # @example Usage with StringIO # # io = StringIO.new "foo bar baz" # FormData::File.new io, :filename => "foobar.txt" # # @example Usage with IO # # File.open "/home/ixti/avatar.png" do |io| # FormData::File.new io # end # # @example Usage with pathname # # FormData::File.new "/home/ixti/avatar.png" class File < Part # Default MIME type DEFAULT_MIME = "application/octet-stream" # @deprecated Use #content_type instead alias mime_type content_type # @see DEFAULT_MIME # @param [String, Pathname, IO] path_or_io Filename or IO instance. # @param [#to_h] opts # @option opts [#to_s] :content_type (DEFAULT_MIME) # Value of Content-Type header # @option opts [#to_s] :filename # When `path_or_io` is a String, Pathname or File, defaults to basename. # When `path_or_io` is a IO, defaults to `"stream-{object_id}"`. def initialize(path_or_io, opts = {}) opts = FormData.ensure_hash(opts) if opts.key? :mime_type warn "[DEPRECATED] :mime_type option deprecated, use :content_type" opts[:content_type] = opts[:mime_type] end @io = make_io(path_or_io) @content_type = opts.fetch(:content_type, DEFAULT_MIME).to_s @filename = opts.fetch(:filename, filename_for(@io)) end private def make_io(path_or_io) if path_or_io.is_a?(String) ::File.open(path_or_io, :binmode => true) elsif defined?(Pathname) && path_or_io.is_a?(Pathname) path_or_io.open(:binmode => true) else path_or_io end end def filename_for(io) if io.respond_to?(:path) ::File.basename io.path else "stream-#{io.object_id}" end end end end end http-form_data-2.2.0/lib/http/form_data.rb0000644000004100000410000000433113625471704020474 0ustar www-datawww-data# frozen_string_literal: true require "http/form_data/part" require "http/form_data/file" require "http/form_data/multipart" require "http/form_data/urlencoded" require "http/form_data/version" # http gem namespace. # @see https://github.com/httprb/http module HTTP # Utility-belt to build form data request bodies. # Provides support for `application/x-www-form-urlencoded` and # `multipart/form-data` types. # # @example Usage # # form = FormData.create({ # :username => "ixti", # :avatar_file => FormData::File.new("/home/ixti/avatar.png") # }) # # # Assuming socket is an open socket to some HTTP server # socket << "POST /some-url HTTP/1.1\r\n" # socket << "Host: example.com\r\n" # socket << "Content-Type: #{form.content_type}\r\n" # socket << "Content-Length: #{form.content_length}\r\n" # socket << "\r\n" # socket << form.to_s module FormData # CRLF CRLF = "\r\n" # Generic FormData error. class Error < StandardError; end class << self # FormData factory. Automatically selects best type depending on given # `data` Hash. # # @param [#to_h, Hash] data # @return [Multipart] if any of values is a {FormData::File} # @return [Urlencoded] otherwise def create(data) data = ensure_hash data klass = multipart?(data) ? Multipart : Urlencoded klass.new data end # Coerce `obj` to Hash. # # @note Internal usage helper, to workaround lack of `#to_h` on Ruby < 2.1 # @raise [Error] `obj` can't be coerced. # @return [Hash] def ensure_hash(obj) case when obj.nil? then {} when obj.is_a?(Hash) then obj when obj.respond_to?(:to_h) then obj.to_h else raise Error, "#{obj.inspect} is neither Hash nor responds to :to_h" end end private # Tells whenever data contains multipart data or not. # # @param [Hash] data # @return [Boolean] def multipart?(data) data.any? do |_, v| next true if v.is_a? FormData::Part v.respond_to?(:to_ary) && v.to_ary.any? { |e| e.is_a? FormData::Part } end end end end end http-form_data-2.2.0/Guardfile0000644000004100000410000000057213625471704016316 0ustar www-datawww-data# frozen_string_literal: true guard :rspec, :cmd => "bundle exec rspec" do require "guard/rspec/dsl" dsl = Guard::RSpec::Dsl.new(self) # RSpec files rspec = dsl.rspec watch(rspec.spec_helper) { rspec.spec_dir } watch(rspec.spec_support) { rspec.spec_dir } watch(rspec.spec_files) # Ruby files ruby = dsl.ruby dsl.watch_spec_files_for(ruby.lib_files) end http-form_data-2.2.0/.yardopts0000644000004100000410000000005613625471704016334 0ustar www-datawww-data--markup-provider=redcarpet --markup=markdown http-form_data-2.2.0/Gemfile0000644000004100000410000000065113625471704015762 0ustar www-datawww-data# frozen_string_literal: true source "https://rubygems.org" gem "rake" group :development do gem "guard" gem "guard-rspec", :require => false gem "pry" end group :test do gem "coveralls" gem "rspec", "~> 3.1" gem "rubocop", "= 0.48.1" gem "simplecov", ">= 0.9" end group :doc do gem "redcarpet", :platform => :mri gem "yard" end # Specify your gem's dependencies in form_data.gemspec gemspec http-form_data-2.2.0/appveyor.yml0000644000004100000410000000021713625471704017055 0ustar www-datawww-dataversion: "#{build}" build: off install: - set PATH=C:\Ruby23\bin;%PATH% - bundle install test_script: - bundle exec rake skip_tags: true http-form_data-2.2.0/CHANGES.md0000644000004100000410000000362313625471704016063 0ustar www-datawww-data## 2.2.0 (2020-01-09) * [#28](https://github.com/httprb/form_data/pull/28) Ruby 2.7 compatibility. [@janko][] ## 2.1.1 (2018-06-01) * [#23](https://github.com/httprb/form_data/pull/23) Allow override urlencoded form data encoder. [@FabienChaynes][] ## 2.1.0 (2018-03-05) * [#21](https://github.com/httprb/form_data/pull/21) Rewind content at the end of `Readable#to_s`. [@janko-m][] * [#19](https://github.com/httprb/form_data/pull/19) Fix buffer encoding. [@HoneyryderChuck][] ## 2.0.0 (2017-10-01) * [#17](https://github.com/httprb/form_data/pull/17) Add CRLF character to end of multipart body. [@mhickman][] ## 2.0.0.pre2 (2017-05-11) * [#14](https://github.com/httprb/form_data/pull/14) Enable streaming for urlencoded form data. [@janko-m][] ## 2.0.0.pre1 (2017-05-10) * [#12](https://github.com/httprb/form_data.rb/pull/12) Enable form data streaming. [@janko-m][] ## 1.0.2 (2017-05-08) * [#5](https://github.com/httprb/form_data.rb/issues/5) Allow setting Content-Type non-file parts [@abotalov][] * [#6](https://github.com/httprb/form_data.rb/issues/6) Creation of file parts without filename [@abotalov][] * [#11](https://github.com/httprb/form_data.rb/pull/11) Deprecate `HTTP::FormData::File#mime_type`. Use `#content_type` instead. [@ixti][] ## 1.0.1 (2015-03-31) * Fix usage of URI module. ## 1.0.0 (2015-01-04) * Gem renamed to `http-form_data` as `FormData` is not top-level citizen anymore: `FormData -> HTTP::FormData`. ## 0.1.0 (2015-01-02) * Move repo under `httprb` organization on GitHub. * Add `nil` support to `FormData#ensure_hash`. ## 0.0.1 (2014-12-15) * First release ever! [@ixti]: https://github.com/ixti [@abotalov]: https://github.com/abotalov [@janko-m]: https://github.com/janko-m [@mhickman]: https://github.com/mhickman [@HoneyryderChuck]: https://github.com/HoneyryderChuck [@FabienChaynes]: https://github.com/FabienChaynes http-form_data-2.2.0/LICENSE.txt0000644000004100000410000000206713625471704016315 0ustar www-datawww-dataCopyright (c) 2015-2017 Alexey V Zapparov MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.