http-form_data 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/.rspec +2 -0
- data/.rubocop.yml +68 -0
- data/.travis.yml +20 -0
- data/.yardopts +2 -0
- data/CHANGES.md +15 -0
- data/Gemfile +24 -0
- data/Guardfile +14 -0
- data/LICENSE.txt +22 -0
- data/README.md +88 -0
- data/Rakefile +17 -0
- data/http-form_data.gemspec +26 -0
- data/lib/http/form_data.rb +76 -0
- data/lib/http/form_data/file.rb +75 -0
- data/lib/http/form_data/multipart.rb +64 -0
- data/lib/http/form_data/multipart/param.rb +82 -0
- data/lib/http/form_data/urlencoded.rb +33 -0
- data/lib/http/form_data/version.rb +6 -0
- data/spec/fixtures/expected-multipart-body.tpl +0 -0
- data/spec/fixtures/the-http-gem.info +1 -0
- data/spec/lib/http/form_data/file_spec.rb +94 -0
- data/spec/lib/http/form_data/multipart_spec.rb +40 -0
- data/spec/lib/http/form_data/urlencoded_spec.rb +31 -0
- data/spec/lib/http/form_data_spec.rb +48 -0
- data/spec/spec_helper.rb +83 -0
- data/spec/support/fixtures_helper.rb +13 -0
- metadata +94 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 6b6f78da2bcad8f8c3d16a66a0a3b2d95446e5b9
|
4
|
+
data.tar.gz: 821cfd73800cc9cf8ee056638be1c4e2ae4181de
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 058ac0d84ee6e846dca8143b270b9ac0196bc12a51894772239004577d93f36064f609cadc851f628311339e85d0c5dfb06b0d11ee39ef358fdbf7186f481f1d
|
7
|
+
data.tar.gz: 3e9b0000a763a4c14f825ed58d1606cbbeb4fe94031fa32a22a530d2345500860540581ef8233b88b1efd8e1ffe978f719b7148c60b59e4e1c0bc083b348461e
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
## Styles ######################################################################
|
2
|
+
|
3
|
+
# See: http://erniemiller.org/2014/10/23/in-defense-of-alias/
|
4
|
+
Style/Alias:
|
5
|
+
Enabled: false
|
6
|
+
|
7
|
+
Style/AlignParameters:
|
8
|
+
EnforcedStyle: with_fixed_indentation
|
9
|
+
|
10
|
+
Style/BracesAroundHashParameters:
|
11
|
+
Enabled: false
|
12
|
+
|
13
|
+
# Broken (2014-12-15). Use `yardstick` gem instead.
|
14
|
+
# See: https://github.com/bbatsov/rubocop/issues/947
|
15
|
+
# TODO: Enable back once cop is fixed.
|
16
|
+
Style/Documentation:
|
17
|
+
Enabled: false
|
18
|
+
|
19
|
+
Style/EmptyLineBetweenDefs:
|
20
|
+
AllowAdjacentOneLineDefs: true
|
21
|
+
|
22
|
+
Style/Encoding:
|
23
|
+
EnforcedStyle: when_needed
|
24
|
+
|
25
|
+
Style/HashSyntax:
|
26
|
+
EnforcedStyle: hash_rockets
|
27
|
+
|
28
|
+
Style/IndentHash:
|
29
|
+
EnforcedStyle: consistent
|
30
|
+
|
31
|
+
# New lambda syntax is as ugly to me as new syntax of Hash.
|
32
|
+
Style/Lambda:
|
33
|
+
Enabled: false
|
34
|
+
|
35
|
+
Style/MultilineOperationIndentation:
|
36
|
+
EnforcedStyle: indented
|
37
|
+
|
38
|
+
# A bit useless restriction, that makes impossible aligning code like this:
|
39
|
+
#
|
40
|
+
# redis do |conn|
|
41
|
+
# conn.hset :k1, now
|
42
|
+
# conn.hincrby :k2, 123
|
43
|
+
# end
|
44
|
+
SingleSpaceBeforeFirstArg:
|
45
|
+
Enabled: false
|
46
|
+
|
47
|
+
Style/StringLiterals:
|
48
|
+
EnforcedStyle: double_quotes
|
49
|
+
|
50
|
+
# Not all trivial readers/writers can be defined with attr_* methods
|
51
|
+
#
|
52
|
+
# class Example < SimpleDelegator
|
53
|
+
# def __getobj__
|
54
|
+
# @obj
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
# def __setobj__(obj)
|
58
|
+
# @obj = obj
|
59
|
+
# end
|
60
|
+
# end
|
61
|
+
Style/TrivialAccessors:
|
62
|
+
Enabled: false
|
63
|
+
|
64
|
+
## Metrics #####################################################################
|
65
|
+
|
66
|
+
Metrics/MethodLength:
|
67
|
+
CountComments: false
|
68
|
+
Max: 15
|
data/.travis.yml
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
bundler_args: --without development doc
|
2
|
+
env:
|
3
|
+
global:
|
4
|
+
- JRUBY_OPTS="$JRUBY_OPTS --debug"
|
5
|
+
language: ruby
|
6
|
+
rvm:
|
7
|
+
- 1.9.3
|
8
|
+
- 2.0.0
|
9
|
+
- 2.1
|
10
|
+
- 2.2
|
11
|
+
- jruby-19mode
|
12
|
+
- jruby-head
|
13
|
+
- rbx-2
|
14
|
+
- ruby-head
|
15
|
+
matrix:
|
16
|
+
allow_failures:
|
17
|
+
- rvm: jruby-head
|
18
|
+
- rvm: ruby-head
|
19
|
+
fast_finish: true
|
20
|
+
sudo: false
|
data/.yardopts
ADDED
data/CHANGES.md
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
## 1.0.0 (2015-01-04)
|
2
|
+
|
3
|
+
* Gem renamed to `http-form_data` as `FormData` is not top-level citizen
|
4
|
+
anymore: `FormData -> HTTP::FormData`.
|
5
|
+
|
6
|
+
|
7
|
+
## 0.1.0 (2015-01-02)
|
8
|
+
|
9
|
+
* Move repo under `httprb` organization on GitHub.
|
10
|
+
* Add `nil` support to `FormData#ensure_hash`.
|
11
|
+
|
12
|
+
|
13
|
+
## 0.0.1 (2014-12-15)
|
14
|
+
|
15
|
+
* First release ever!
|
data/Gemfile
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
source "https://rubygems.org"
|
2
|
+
|
3
|
+
gem "rake"
|
4
|
+
|
5
|
+
group :development do
|
6
|
+
gem "pry"
|
7
|
+
gem "guard"
|
8
|
+
gem "guard-rspec", :require => false
|
9
|
+
end
|
10
|
+
|
11
|
+
group :test do
|
12
|
+
gem "coveralls"
|
13
|
+
gem "rspec", "~> 3.1"
|
14
|
+
gem "simplecov", ">= 0.9"
|
15
|
+
gem "rubocop", "~> 0.28.0"
|
16
|
+
end
|
17
|
+
|
18
|
+
group :doc do
|
19
|
+
gem "yard"
|
20
|
+
gem "redcarpet"
|
21
|
+
end
|
22
|
+
|
23
|
+
# Specify your gem's dependencies in form_data.gemspec
|
24
|
+
gemspec
|
data/Guardfile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
guard :rspec, :cmd => "bundle exec rspec" do
|
2
|
+
require "guard/rspec/dsl"
|
3
|
+
dsl = Guard::RSpec::Dsl.new(self)
|
4
|
+
|
5
|
+
# RSpec files
|
6
|
+
rspec = dsl.rspec
|
7
|
+
watch(rspec.spec_helper) { rspec.spec_dir }
|
8
|
+
watch(rspec.spec_support) { rspec.spec_dir }
|
9
|
+
watch(rspec.spec_files)
|
10
|
+
|
11
|
+
# Ruby files
|
12
|
+
ruby = dsl.ruby
|
13
|
+
dsl.watch_spec_files_for(ruby.lib_files)
|
14
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 Aleksey V Zapparov
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
# FormData
|
2
|
+
|
3
|
+
[](http://rubygems.org/gems/http-form_data)
|
4
|
+
[](http://travis-ci.org/httprb/form_data.rb)
|
5
|
+
[](https://codeclimate.com/github/httprb/form_data.rb)
|
6
|
+
[](https://coveralls.io/r/httprb/form_data.rb)
|
7
|
+
|
8
|
+
Utility-belt to build form data request bodies.
|
9
|
+
|
10
|
+
|
11
|
+
## Installation
|
12
|
+
|
13
|
+
Add this line to your application's Gemfile:
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
gem 'http-form_data'
|
17
|
+
```
|
18
|
+
|
19
|
+
And then execute:
|
20
|
+
|
21
|
+
$ bundle
|
22
|
+
|
23
|
+
Or install it yourself as:
|
24
|
+
|
25
|
+
$ gem install http-form_data
|
26
|
+
|
27
|
+
|
28
|
+
## Usage
|
29
|
+
|
30
|
+
``` ruby
|
31
|
+
require "http/form_data"
|
32
|
+
|
33
|
+
form = HTTP::FormData.create({
|
34
|
+
:username => "ixti",
|
35
|
+
:avatar_file => HTTP::FormData::File.new("/home/ixti/avatar.png")
|
36
|
+
})
|
37
|
+
|
38
|
+
# Assuming socket is an open socket to some HTTP server
|
39
|
+
socket << "POST /some-url HTTP/1.1\r\n"
|
40
|
+
socket << "Host: example.com\r\n"
|
41
|
+
socket << "Content-Type: #{form.content_type}\r\n"
|
42
|
+
socket << "Content-Length: #{form.content_length}\r\n"
|
43
|
+
socket << "\r\n"
|
44
|
+
socket << form.to_s
|
45
|
+
```
|
46
|
+
|
47
|
+
|
48
|
+
## Supported Ruby Versions
|
49
|
+
|
50
|
+
This library aims to support and is [tested against][ci] the following Ruby
|
51
|
+
versions:
|
52
|
+
|
53
|
+
* Ruby 1.9.3
|
54
|
+
* Ruby 2.0.0
|
55
|
+
* Ruby 2.1.x
|
56
|
+
* Ruby 2.2.x
|
57
|
+
|
58
|
+
If something doesn't work on one of these versions, it's a bug.
|
59
|
+
|
60
|
+
This library may inadvertently work (or seem to work) on other Ruby versions,
|
61
|
+
however support will only be provided for the versions listed above.
|
62
|
+
|
63
|
+
If you would like this library to support another Ruby version or
|
64
|
+
implementation, you may volunteer to be a maintainer. Being a maintainer
|
65
|
+
entails making sure all tests run and pass on that implementation. When
|
66
|
+
something breaks on your implementation, you will be responsible for providing
|
67
|
+
patches in a timely fashion. If critical issues for a particular implementation
|
68
|
+
exist at the time of a major release, support for that Ruby version may be
|
69
|
+
dropped.
|
70
|
+
|
71
|
+
|
72
|
+
## Contributing
|
73
|
+
|
74
|
+
1. Fork it ( https://github.com/httprb/form_data.rb/fork )
|
75
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
76
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
77
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
78
|
+
5. Create a new Pull Request
|
79
|
+
|
80
|
+
|
81
|
+
## Copyright
|
82
|
+
|
83
|
+
Copyright (c) 2015 Aleksey V Zapparov.
|
84
|
+
See [LICENSE.txt][license] for further details.
|
85
|
+
|
86
|
+
|
87
|
+
[ci]: http://travis-ci.org/httprb/form_data.rb
|
88
|
+
[license]: https://github.com/httprb/form_data.rb/blob/master/LICENSE.txt
|
data/Rakefile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
|
3
|
+
require "bundler/gem_tasks"
|
4
|
+
|
5
|
+
require "rspec/core/rake_task"
|
6
|
+
RSpec::Core::RakeTask.new
|
7
|
+
|
8
|
+
begin
|
9
|
+
require "rubocop/rake_task"
|
10
|
+
RuboCop::RakeTask.new
|
11
|
+
rescue LoadError
|
12
|
+
task :rubocop do
|
13
|
+
$stderr.puts "RuboCop is disabled"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
task :default => [:spec, :rubocop]
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "http/form_data/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "http-form_data"
|
8
|
+
spec.version = HTTP::FormData::VERSION
|
9
|
+
spec.homepage = "https://github.com/httprb/form_data.rb"
|
10
|
+
spec.authors = ["Aleksey V Zapparov"]
|
11
|
+
spec.email = ["ixti@member.fsf.org"]
|
12
|
+
spec.license = "MIT"
|
13
|
+
spec.summary = "http-form_data-#{HTTP::FormData::VERSION}"
|
14
|
+
spec.description = <<-DESC.gsub(/^\s+> /m, "").gsub("\n", " ").strip
|
15
|
+
> Utility-belt to build form data request bodies.
|
16
|
+
> Provides support for `application/x-www-form-urlencoded` and
|
17
|
+
> `multipart/form-data` types.
|
18
|
+
DESC
|
19
|
+
|
20
|
+
spec.files = `git ls-files -z`.split("\x0")
|
21
|
+
spec.executables = spec.files.grep(/^bin\//).map { |f| File.basename(f) }
|
22
|
+
spec.test_files = spec.files.grep(/^(test|spec|features)\//)
|
23
|
+
spec.require_paths = ["lib"]
|
24
|
+
|
25
|
+
spec.add_development_dependency "bundler", "~> 1.7"
|
26
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require "http/form_data/file"
|
2
|
+
require "http/form_data/multipart"
|
3
|
+
require "http/form_data/urlencoded"
|
4
|
+
require "http/form_data/version"
|
5
|
+
|
6
|
+
# http.rb namespace.
|
7
|
+
# @see https://github.com/httprb/http.rb
|
8
|
+
module HTTP
|
9
|
+
# Utility-belt to build form data request bodies.
|
10
|
+
# Provides support for `application/x-www-form-urlencoded` and
|
11
|
+
# `multipart/form-data` types.
|
12
|
+
#
|
13
|
+
# @example Usage
|
14
|
+
#
|
15
|
+
# form = FormData.create({
|
16
|
+
# :username => "ixti",
|
17
|
+
# :avatar_file => FormData::File.new("/home/ixti/avatar.png")
|
18
|
+
# })
|
19
|
+
#
|
20
|
+
# # Assuming socket is an open socket to some HTTP server
|
21
|
+
# socket << "POST /some-url HTTP/1.1\r\n"
|
22
|
+
# socket << "Host: example.com\r\n"
|
23
|
+
# socket << "Content-Type: #{form.content_type}\r\n"
|
24
|
+
# socket << "Content-Length: #{form.content_length}\r\n"
|
25
|
+
# socket << "\r\n"
|
26
|
+
# socket << form.to_s
|
27
|
+
module FormData
|
28
|
+
# CRLF
|
29
|
+
CRLF = "\r\n".freeze
|
30
|
+
|
31
|
+
# Generic FormData error.
|
32
|
+
class Error < StandardError; end
|
33
|
+
|
34
|
+
class << self
|
35
|
+
# FormData factory. Automatically selects best type depending on given
|
36
|
+
# `data` Hash.
|
37
|
+
#
|
38
|
+
# @param [#to_h, Hash] data
|
39
|
+
# @return [Multipart] if any of values is a {FormData::File}
|
40
|
+
# @return [Urlencoded] otherwise
|
41
|
+
def create(data)
|
42
|
+
data = ensure_hash data
|
43
|
+
klass = multipart?(data) ? Multipart : Urlencoded
|
44
|
+
|
45
|
+
klass.new data
|
46
|
+
end
|
47
|
+
|
48
|
+
# Coerce `obj` to Hash.
|
49
|
+
#
|
50
|
+
# @note Internal usage helper, to workaround lack of `#to_h` on Ruby < 2.1
|
51
|
+
# @raise [Error] `obj` can't be coerced.
|
52
|
+
# @return [Hash]
|
53
|
+
def ensure_hash(obj)
|
54
|
+
case
|
55
|
+
when obj.nil? then {}
|
56
|
+
when obj.is_a?(Hash) then obj
|
57
|
+
when obj.respond_to?(:to_h) then obj.to_h
|
58
|
+
else fail Error, "#{obj.inspect} is neither Hash nor responds to :to_h"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
# Tells whenever data contains multipart data or not.
|
65
|
+
#
|
66
|
+
# @param [Hash] data
|
67
|
+
# @return [Boolean]
|
68
|
+
def multipart?(data)
|
69
|
+
data.any? do |_, v|
|
70
|
+
next true if v.is_a? FormData::File
|
71
|
+
v.respond_to?(:to_ary) && v.to_ary.any? { |e| e.is_a? FormData::File }
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module HTTP
|
2
|
+
module FormData
|
3
|
+
# Represents file form param.
|
4
|
+
#
|
5
|
+
# @example Usage with StringIO
|
6
|
+
#
|
7
|
+
# io = StringIO.new "foo bar baz"
|
8
|
+
# FormData::File.new io, :filename => "foobar.txt"
|
9
|
+
#
|
10
|
+
# @example Usage with IO
|
11
|
+
#
|
12
|
+
# File.open "/home/ixti/avatar.png" do |io|
|
13
|
+
# FormData::File.new io
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# @example Usage with pathname
|
17
|
+
#
|
18
|
+
# FormData::File.new "/home/ixti/avatar.png"
|
19
|
+
class File
|
20
|
+
# Default MIME type
|
21
|
+
DEFAULT_MIME = "application/octet-stream".freeze
|
22
|
+
|
23
|
+
attr_reader :mime_type, :filename
|
24
|
+
|
25
|
+
# @see DEFAULT_MIME
|
26
|
+
# @param [String, StringIO, File] file_or_io Filename or IO instance.
|
27
|
+
# @param [#to_h] opts
|
28
|
+
# @option opts [#to_s] :mime_type (DEFAULT_MIME)
|
29
|
+
# @option opts [#to_s] :filename
|
30
|
+
# When `file` is a String, defaults to basename of `file`.
|
31
|
+
# When `file` is a File, defaults to basename of `file`.
|
32
|
+
# When `file` is a StringIO, defaults to `"stream-{object_id}"`
|
33
|
+
def initialize(file_or_io, opts = {})
|
34
|
+
@file_or_io = file_or_io
|
35
|
+
|
36
|
+
opts = FormData.ensure_hash opts
|
37
|
+
|
38
|
+
@mime_type = opts.fetch(:mime_type) { DEFAULT_MIME }
|
39
|
+
@filename = opts.fetch :filename do
|
40
|
+
case file_or_io
|
41
|
+
when String then ::File.basename file_or_io
|
42
|
+
when ::File then ::File.basename file_or_io.path
|
43
|
+
else "stream-#{file_or_io.object_id}"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Returns content size.
|
49
|
+
#
|
50
|
+
# @return [Fixnum]
|
51
|
+
def size
|
52
|
+
with_io(&:size)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Returns content of a file of IO.
|
56
|
+
#
|
57
|
+
# @return [String]
|
58
|
+
def to_s
|
59
|
+
with_io(&:read)
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
# @yield [io] Gives IO instance to the block
|
65
|
+
# @return result of yielded block
|
66
|
+
def with_io
|
67
|
+
if @file_or_io.is_a?(::File) || @file_or_io.is_a?(StringIO)
|
68
|
+
yield @file_or_io
|
69
|
+
else
|
70
|
+
::File.open(@file_or_io) { |io| yield io }
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# stdlib
|
2
|
+
require "securerandom"
|
3
|
+
|
4
|
+
# internal
|
5
|
+
require "http/form_data/multipart/param"
|
6
|
+
|
7
|
+
module HTTP
|
8
|
+
module FormData
|
9
|
+
# `multipart/form-data` form data.
|
10
|
+
class Multipart
|
11
|
+
# @param [#to_h, Hash] data form data key-value Hash
|
12
|
+
def initialize(data)
|
13
|
+
@parts = Param.coerce FormData.ensure_hash data
|
14
|
+
@boundary = ("-" * 21) << SecureRandom.hex(21)
|
15
|
+
@content_length = nil
|
16
|
+
end
|
17
|
+
|
18
|
+
# Returns content to be used for HTTP request body.
|
19
|
+
#
|
20
|
+
# @return [String]
|
21
|
+
def to_s
|
22
|
+
head + @parts.map(&:to_s).join(glue) + tail
|
23
|
+
end
|
24
|
+
|
25
|
+
# Returns MIME type to be used for HTTP request `Content-Type` header.
|
26
|
+
#
|
27
|
+
# @return [String]
|
28
|
+
def content_type
|
29
|
+
"multipart/form-data; boundary=#{@boundary}"
|
30
|
+
end
|
31
|
+
|
32
|
+
# Returns form data content size to be used for HTTP request
|
33
|
+
# `Content-Length` header.
|
34
|
+
#
|
35
|
+
# @return [Fixnum]
|
36
|
+
def content_length
|
37
|
+
unless @content_length
|
38
|
+
@content_length = head.bytesize + tail.bytesize
|
39
|
+
@content_length += @parts.map(&:size).reduce(:+)
|
40
|
+
@content_length += (glue.bytesize * (@parts.count - 1))
|
41
|
+
end
|
42
|
+
|
43
|
+
@content_length
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
# @return [String]
|
49
|
+
def head
|
50
|
+
@head ||= "--#{@boundary}#{CRLF}"
|
51
|
+
end
|
52
|
+
|
53
|
+
# @return [String]
|
54
|
+
def glue
|
55
|
+
@glue ||= "#{CRLF}--#{@boundary}#{CRLF}"
|
56
|
+
end
|
57
|
+
|
58
|
+
# @return [String]
|
59
|
+
def tail
|
60
|
+
@tail ||= "#{CRLF}--#{@boundary}--"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module HTTP
|
2
|
+
module FormData
|
3
|
+
class Multipart
|
4
|
+
# Utility class to represent multi-part chunks
|
5
|
+
class Param
|
6
|
+
# @param [#to_s] name
|
7
|
+
# @param [FormData::File, #to_s] value
|
8
|
+
def initialize(name, value)
|
9
|
+
@name, @value = name.to_s, value
|
10
|
+
|
11
|
+
@header = "Content-Disposition: form-data; name=#{@name.inspect}"
|
12
|
+
|
13
|
+
return unless file?
|
14
|
+
|
15
|
+
@header << "; filename=#{value.filename.inspect}"
|
16
|
+
@header << CRLF
|
17
|
+
@header << "Content-Type: #{value.mime_type}"
|
18
|
+
end
|
19
|
+
|
20
|
+
# Returns body part with headers and data.
|
21
|
+
#
|
22
|
+
# @example With {FormData::File} value
|
23
|
+
#
|
24
|
+
# Content-Disposition: form-data; name="avatar"; filename="avatar.png"
|
25
|
+
# Content-Type: application/octet-stream
|
26
|
+
#
|
27
|
+
# ...data of avatar.png...
|
28
|
+
#
|
29
|
+
# @example With non-{FormData::File} value
|
30
|
+
#
|
31
|
+
# Content-Disposition: form-data; name="username"
|
32
|
+
#
|
33
|
+
# ixti
|
34
|
+
#
|
35
|
+
# @return [String]
|
36
|
+
def to_s
|
37
|
+
"#{@header}#{CRLF * 2}#{@value}"
|
38
|
+
end
|
39
|
+
|
40
|
+
# Calculates size of a part (headers + body).
|
41
|
+
#
|
42
|
+
# @return [Fixnum]
|
43
|
+
def size
|
44
|
+
size = @header.bytesize + (CRLF.bytesize * 2)
|
45
|
+
|
46
|
+
if file?
|
47
|
+
size + @value.size
|
48
|
+
else
|
49
|
+
size + @value.to_s.bytesize
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Flattens given `data` Hash into an array of `Param`'s.
|
54
|
+
# Nested array are unwinded.
|
55
|
+
# Behavior is similar to `URL.encode_www_form`.
|
56
|
+
#
|
57
|
+
# @param [Hash] data
|
58
|
+
# @return [Array<FormData::MultiPart::Param>]
|
59
|
+
def self.coerce(data)
|
60
|
+
params = []
|
61
|
+
|
62
|
+
data.each do |name, values|
|
63
|
+
Array(values).each do |value|
|
64
|
+
params << new(name, value)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
params
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
# Tells whenever value is a {FormData::File} or not.
|
74
|
+
#
|
75
|
+
# @return [Boolean]
|
76
|
+
def file?
|
77
|
+
@value.is_a? FormData::File
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module HTTP
|
2
|
+
module FormData
|
3
|
+
# `application/x-www-form-urlencoded` form data.
|
4
|
+
class Urlencoded
|
5
|
+
# @param [#to_h, Hash] data form data key-value Hash
|
6
|
+
def initialize(data)
|
7
|
+
@data = FormData.ensure_hash data
|
8
|
+
end
|
9
|
+
|
10
|
+
# Returns content to be used for HTTP request body.
|
11
|
+
#
|
12
|
+
# @return [String]
|
13
|
+
def to_s
|
14
|
+
URI.encode_www_form @data
|
15
|
+
end
|
16
|
+
|
17
|
+
# Returns MIME type to be used for HTTP request `Content-Type` header.
|
18
|
+
#
|
19
|
+
# @return [String]
|
20
|
+
def content_type
|
21
|
+
"application/x-www-form-urlencoded"
|
22
|
+
end
|
23
|
+
|
24
|
+
# Returns form data content size to be used for HTTP request
|
25
|
+
# `Content-Length` header.
|
26
|
+
#
|
27
|
+
# @return [Fixnum]
|
28
|
+
def content_length
|
29
|
+
to_s.bytesize
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
File without changes
|
@@ -0,0 +1 @@
|
|
1
|
+
The HTTP Gem is an easy-to-use client library for making requests from Ruby.
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
RSpec.describe HTTP::FormData::File do
|
4
|
+
let(:opts) { nil }
|
5
|
+
|
6
|
+
describe "#size" do
|
7
|
+
subject { described_class.new(file, opts).size }
|
8
|
+
|
9
|
+
context "when file given as a String" do
|
10
|
+
let(:file) { fixture("the-http-gem.info").to_s }
|
11
|
+
it { is_expected.to eq fixture("the-http-gem.info").size }
|
12
|
+
end
|
13
|
+
|
14
|
+
context "when file given as StringIO" do
|
15
|
+
let(:file) { StringIO.new "привет мир!" }
|
16
|
+
it { is_expected.to eq 20 }
|
17
|
+
end
|
18
|
+
|
19
|
+
context "when file given as File" do
|
20
|
+
let(:file) { fixture("the-http-gem.info").open }
|
21
|
+
after { file.close }
|
22
|
+
it { is_expected.to eq fixture("the-http-gem.info").size }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "#to_s" do
|
27
|
+
subject { described_class.new(file, opts).to_s }
|
28
|
+
|
29
|
+
context "when file given as a String" do
|
30
|
+
let(:file) { fixture("the-http-gem.info").to_s }
|
31
|
+
it { is_expected.to eq fixture("the-http-gem.info").read }
|
32
|
+
end
|
33
|
+
|
34
|
+
context "when file given as StringIO" do
|
35
|
+
let(:file) { StringIO.new "привет мир!" }
|
36
|
+
it { is_expected.to eq "привет мир!" }
|
37
|
+
end
|
38
|
+
|
39
|
+
context "when file given as File" do
|
40
|
+
let(:file) { fixture("the-http-gem.info").open }
|
41
|
+
after { file.close }
|
42
|
+
it { is_expected.to eq fixture("the-http-gem.info").read }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe "#filename" do
|
47
|
+
subject { described_class.new(file, opts).filename }
|
48
|
+
|
49
|
+
context "when file given as a String" do
|
50
|
+
let(:file) { fixture("the-http-gem.info").to_s }
|
51
|
+
|
52
|
+
it { is_expected.to eq ::File.basename file }
|
53
|
+
|
54
|
+
context "and filename given with options" do
|
55
|
+
let(:opts) { { :filename => "foobar.txt" } }
|
56
|
+
it { is_expected.to eq "foobar.txt" }
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
context "when file given as StringIO" do
|
61
|
+
let(:file) { StringIO.new }
|
62
|
+
|
63
|
+
it { is_expected.to eq "stream-#{file.object_id}" }
|
64
|
+
|
65
|
+
context "and filename given with options" do
|
66
|
+
let(:opts) { { :filename => "foobar.txt" } }
|
67
|
+
it { is_expected.to eq "foobar.txt" }
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
context "when file given as File" do
|
72
|
+
let(:file) { fixture("the-http-gem.info").open }
|
73
|
+
after { file.close }
|
74
|
+
|
75
|
+
it { is_expected.to eq "the-http-gem.info" }
|
76
|
+
|
77
|
+
context "and filename given with options" do
|
78
|
+
let(:opts) { { :filename => "foobar.txt" } }
|
79
|
+
it { is_expected.to eq "foobar.txt" }
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
describe "#mime_type" do
|
85
|
+
subject { described_class.new(StringIO.new, opts).mime_type }
|
86
|
+
|
87
|
+
it { is_expected.to eq "application/octet-stream" }
|
88
|
+
|
89
|
+
context "when it was given with options" do
|
90
|
+
let(:opts) { { :mime_type => "application/json" } }
|
91
|
+
it { is_expected.to eq "application/json" }
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
RSpec.describe HTTP::FormData::Multipart do
|
2
|
+
let(:file) { HTTP::FormData::File.new fixture "the-http-gem.info" }
|
3
|
+
let(:params) { { :foo => :bar, :baz => file } }
|
4
|
+
let(:boundary) { /-{21}[a-f0-9]{42}/ }
|
5
|
+
subject(:form_data) { HTTP::FormData::Multipart.new params }
|
6
|
+
|
7
|
+
describe "#content_type" do
|
8
|
+
subject { form_data.content_type }
|
9
|
+
it { is_expected.to match(/^multipart\/form-data; boundary=#{boundary}$/) }
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "#content_length" do
|
13
|
+
subject { form_data.content_length }
|
14
|
+
it { is_expected.to eq form_data.to_s.bytesize }
|
15
|
+
end
|
16
|
+
|
17
|
+
describe "#to_s" do
|
18
|
+
def disposition(params)
|
19
|
+
params = params.map { |k, v| "#{k}=#{v.inspect}" }.join("; ")
|
20
|
+
"Content-Disposition: form-data; #{params}"
|
21
|
+
end
|
22
|
+
|
23
|
+
let(:crlf) { "\r\n" }
|
24
|
+
|
25
|
+
it "properly generates multipart data" do
|
26
|
+
boundary_value = form_data.content_type[/(#{boundary})$/, 1]
|
27
|
+
|
28
|
+
expect(form_data.to_s).to eq [
|
29
|
+
"--#{boundary_value}#{crlf}",
|
30
|
+
"#{disposition 'name' => 'foo'}#{crlf}",
|
31
|
+
"#{crlf}bar#{crlf}",
|
32
|
+
"--#{boundary_value}#{crlf}",
|
33
|
+
"#{disposition 'name' => 'baz', 'filename' => file.filename}#{crlf}",
|
34
|
+
"Content-Type: #{file.mime_type}#{crlf}",
|
35
|
+
"#{crlf}#{file}#{crlf}",
|
36
|
+
"--#{boundary_value}--"
|
37
|
+
].join("")
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
RSpec.describe HTTP::FormData::Urlencoded do
|
4
|
+
let(:data) { { "foo[bar]" => "test" } }
|
5
|
+
subject(:form_data) { HTTP::FormData::Urlencoded.new data }
|
6
|
+
|
7
|
+
describe "#content_type" do
|
8
|
+
subject { form_data.content_type }
|
9
|
+
it { is_expected.to eq "application/x-www-form-urlencoded" }
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "#content_length" do
|
13
|
+
subject { form_data.content_length }
|
14
|
+
it { is_expected.to eq form_data.to_s.bytesize }
|
15
|
+
|
16
|
+
context "with unicode chars" do
|
17
|
+
let(:data) { { "foo[bar]" => "тест" } }
|
18
|
+
it { is_expected.to eq form_data.to_s.bytesize }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "#to_s" do
|
23
|
+
subject { form_data.to_s }
|
24
|
+
it { is_expected.to eq "foo%5Bbar%5D=test" }
|
25
|
+
|
26
|
+
context "with unicode chars" do
|
27
|
+
let(:data) { { "foo[bar]" => "тест" } }
|
28
|
+
it { is_expected.to eq "foo%5Bbar%5D=%D1%82%D0%B5%D1%81%D1%82" }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
RSpec.describe HTTP::FormData do
|
2
|
+
describe ".create" do
|
3
|
+
subject { HTTP::FormData.create params }
|
4
|
+
|
5
|
+
context "when form has no files" do
|
6
|
+
let(:params) { { :foo => :bar } }
|
7
|
+
it { is_expected.to be_a HTTP::FormData::Urlencoded }
|
8
|
+
end
|
9
|
+
|
10
|
+
context "when form has at least one file param" do
|
11
|
+
let(:gemspec) { HTTP::FormData::File.new "gemspec" }
|
12
|
+
let(:params) { { :foo => :bar, :baz => gemspec } }
|
13
|
+
it { is_expected.to be_a HTTP::FormData::Multipart }
|
14
|
+
end
|
15
|
+
|
16
|
+
context "when form has file in an array param" do
|
17
|
+
let(:gemspec) { HTTP::FormData::File.new "gemspec" }
|
18
|
+
let(:params) { { :foo => :bar, :baz => [gemspec] } }
|
19
|
+
it { is_expected.to be_a HTTP::FormData::Multipart }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe ".ensure_hash" do
|
24
|
+
subject(:ensure_hash) { HTTP::FormData.ensure_hash data }
|
25
|
+
|
26
|
+
context "when Hash given" do
|
27
|
+
let(:data) { { :foo => :bar } }
|
28
|
+
it { is_expected.to eq :foo => :bar }
|
29
|
+
end
|
30
|
+
|
31
|
+
context "when #to_h given" do
|
32
|
+
let(:data) { double(:to_h => { :foo => :bar }) }
|
33
|
+
it { is_expected.to eq :foo => :bar }
|
34
|
+
end
|
35
|
+
|
36
|
+
context "when nil given" do
|
37
|
+
let(:data) { nil }
|
38
|
+
it { is_expected.to eq({}) }
|
39
|
+
end
|
40
|
+
|
41
|
+
context "when neither Hash nor #to_h given" do
|
42
|
+
let(:data) { double }
|
43
|
+
it "fails with HTTP::FormData::Error" do
|
44
|
+
expect { ensure_hash }.to raise_error HTTP::FormData::Error
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
require "simplecov"
|
4
|
+
require "coveralls"
|
5
|
+
|
6
|
+
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
|
7
|
+
SimpleCov::Formatter::HTMLFormatter,
|
8
|
+
Coveralls::SimpleCov::Formatter
|
9
|
+
]
|
10
|
+
|
11
|
+
SimpleCov.start { add_filter "/spec/" }
|
12
|
+
|
13
|
+
require "http/form_data"
|
14
|
+
require "support/fixtures_helper"
|
15
|
+
|
16
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
17
|
+
RSpec.configure do |config|
|
18
|
+
config.expect_with :rspec do |expectations|
|
19
|
+
# This option will default to `true` in RSpec 4. It makes the `description`
|
20
|
+
# and `failure_message` of custom matchers include text for helper methods
|
21
|
+
# defined using `chain`, e.g.:
|
22
|
+
# be_bigger_than(2).and_smaller_than(4).description
|
23
|
+
# # => "be bigger than 2 and smaller than 4"
|
24
|
+
# ...rather than:
|
25
|
+
# # => "be bigger than 2"
|
26
|
+
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
27
|
+
end
|
28
|
+
|
29
|
+
config.mock_with :rspec do |mocks|
|
30
|
+
# Prevents you from mocking or stubbing a method that does not exist on
|
31
|
+
# a real object. This is generally recommended, and will default to
|
32
|
+
# `true` in RSpec 4.
|
33
|
+
mocks.verify_partial_doubles = true
|
34
|
+
end
|
35
|
+
|
36
|
+
# These two settings work together to allow you to limit a spec run
|
37
|
+
# to individual examples or groups you care about by tagging them with
|
38
|
+
# `:focus` metadata. When nothing is tagged with `:focus`, all examples
|
39
|
+
# get run.
|
40
|
+
config.filter_run :focus
|
41
|
+
config.run_all_when_everything_filtered = true
|
42
|
+
|
43
|
+
# Limits the available syntax to the non-monkey patched syntax that is
|
44
|
+
# recommended. For more details, see:
|
45
|
+
# - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
|
46
|
+
# - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
|
47
|
+
# - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching
|
48
|
+
config.disable_monkey_patching!
|
49
|
+
|
50
|
+
# This setting enables warnings. It's recommended, but in some cases may
|
51
|
+
# be too noisy due to issues in dependencies.
|
52
|
+
config.warnings = true
|
53
|
+
|
54
|
+
# Many RSpec users commonly either run the entire suite or an individual
|
55
|
+
# file, and it's useful to allow more verbose output when running an
|
56
|
+
# individual spec file.
|
57
|
+
if config.files_to_run.one?
|
58
|
+
# Use the documentation formatter for detailed output,
|
59
|
+
# unless a formatter has already been configured
|
60
|
+
# (e.g. via a command-line flag).
|
61
|
+
config.default_formatter = "doc"
|
62
|
+
end
|
63
|
+
|
64
|
+
# Print the 10 slowest examples and example groups at the
|
65
|
+
# end of the spec run, to help surface which specs are running
|
66
|
+
# particularly slow.
|
67
|
+
config.profile_examples = 10
|
68
|
+
|
69
|
+
# Run specs in random order to surface order dependencies. If you find an
|
70
|
+
# order dependency and want to debug it, you can fix the order by providing
|
71
|
+
# the seed, which is printed after each run.
|
72
|
+
# --seed 1234
|
73
|
+
config.order = :random
|
74
|
+
|
75
|
+
# Seed global randomization in this process using the `--seed` CLI option.
|
76
|
+
# Setting this allows you to use `--seed` to deterministically reproduce
|
77
|
+
# test failures related to randomization by passing the same `--seed` value
|
78
|
+
# as the one that triggered the failure.
|
79
|
+
Kernel.srand config.seed
|
80
|
+
|
81
|
+
# Include common helpers
|
82
|
+
config.include FixturesHelper
|
83
|
+
end
|
metadata
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: http-form_data
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Aleksey V Zapparov
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-01-04 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.7'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.7'
|
27
|
+
description: Utility-belt to build form data request bodies. Provides support for
|
28
|
+
`application/x-www-form-urlencoded` and `multipart/form-data` types.
|
29
|
+
email:
|
30
|
+
- ixti@member.fsf.org
|
31
|
+
executables: []
|
32
|
+
extensions: []
|
33
|
+
extra_rdoc_files: []
|
34
|
+
files:
|
35
|
+
- ".gitignore"
|
36
|
+
- ".rspec"
|
37
|
+
- ".rubocop.yml"
|
38
|
+
- ".travis.yml"
|
39
|
+
- ".yardopts"
|
40
|
+
- CHANGES.md
|
41
|
+
- Gemfile
|
42
|
+
- Guardfile
|
43
|
+
- LICENSE.txt
|
44
|
+
- README.md
|
45
|
+
- Rakefile
|
46
|
+
- http-form_data.gemspec
|
47
|
+
- lib/http/form_data.rb
|
48
|
+
- lib/http/form_data/file.rb
|
49
|
+
- lib/http/form_data/multipart.rb
|
50
|
+
- lib/http/form_data/multipart/param.rb
|
51
|
+
- lib/http/form_data/urlencoded.rb
|
52
|
+
- lib/http/form_data/version.rb
|
53
|
+
- spec/fixtures/expected-multipart-body.tpl
|
54
|
+
- spec/fixtures/the-http-gem.info
|
55
|
+
- spec/lib/http/form_data/file_spec.rb
|
56
|
+
- spec/lib/http/form_data/multipart_spec.rb
|
57
|
+
- spec/lib/http/form_data/urlencoded_spec.rb
|
58
|
+
- spec/lib/http/form_data_spec.rb
|
59
|
+
- spec/spec_helper.rb
|
60
|
+
- spec/support/fixtures_helper.rb
|
61
|
+
homepage: https://github.com/httprb/form_data.rb
|
62
|
+
licenses:
|
63
|
+
- MIT
|
64
|
+
metadata: {}
|
65
|
+
post_install_message:
|
66
|
+
rdoc_options: []
|
67
|
+
require_paths:
|
68
|
+
- lib
|
69
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0'
|
74
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
75
|
+
requirements:
|
76
|
+
- - ">="
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: '0'
|
79
|
+
requirements: []
|
80
|
+
rubyforge_project:
|
81
|
+
rubygems_version: 2.2.2
|
82
|
+
signing_key:
|
83
|
+
specification_version: 4
|
84
|
+
summary: http-form_data-1.0.0
|
85
|
+
test_files:
|
86
|
+
- spec/fixtures/expected-multipart-body.tpl
|
87
|
+
- spec/fixtures/the-http-gem.info
|
88
|
+
- spec/lib/http/form_data/file_spec.rb
|
89
|
+
- spec/lib/http/form_data/multipart_spec.rb
|
90
|
+
- spec/lib/http/form_data/urlencoded_spec.rb
|
91
|
+
- spec/lib/http/form_data_spec.rb
|
92
|
+
- spec/spec_helper.rb
|
93
|
+
- spec/support/fixtures_helper.rb
|
94
|
+
has_rdoc:
|