faraday-multipart-1.1.1/0000755000004100000410000000000015037757013015200 5ustar www-datawww-datafaraday-multipart-1.1.1/faraday-multipart.gemspec0000644000004100000410000000400015037757013022165 0ustar www-datawww-data######################################################### # This file has been automatically generated by gem2tgz # ######################################################### # -*- encoding: utf-8 -*- # stub: faraday-multipart 1.1.1 ruby lib Gem::Specification.new do |s| s.name = "faraday-multipart".freeze s.version = "1.1.1" s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version= s.metadata = { "bug_tracker_uri" => "https://github.com/lostisland/faraday-multipart/issues", "changelog_uri" => "https://github.com/lostisland/faraday-multipart/blob/v1.1.1/CHANGELOG.md", "documentation_uri" => "http://www.rubydoc.info/gems/faraday-multipart/1.1.1", "homepage_uri" => "https://github.com/lostisland/faraday-multipart", "source_code_uri" => "https://github.com/lostisland/faraday-multipart", "wiki_uri" => "https://github.com/lostisland/faraday-multipart/wiki" } if s.respond_to? :metadata= s.require_paths = ["lib".freeze] s.authors = ["Mattia Giuffrida".freeze] s.date = "1980-01-02" s.description = "Perform multipart-post requests using Faraday.\n".freeze s.email = ["giuffrida.mattia@gmail.com".freeze] s.files = ["CHANGELOG.md".freeze, "LICENSE.md".freeze, "README.md".freeze, "lib/faraday/multipart.rb".freeze, "lib/faraday/multipart/file_part.rb".freeze, "lib/faraday/multipart/middleware.rb".freeze, "lib/faraday/multipart/param_part.rb".freeze, "lib/faraday/multipart/version.rb".freeze] s.homepage = "https://github.com/lostisland/faraday-multipart".freeze s.licenses = ["MIT".freeze] s.required_ruby_version = Gem::Requirement.new([">= 2.4".freeze, "< 4".freeze]) s.rubygems_version = "3.3.15".freeze s.summary = "Perform multipart-post requests using Faraday.".freeze if s.respond_to? :specification_version then s.specification_version = 4 end if s.respond_to? :add_runtime_dependency then s.add_runtime_dependency(%q.freeze, ["~> 2.0"]) else s.add_dependency(%q.freeze, ["~> 2.0"]) end end faraday-multipart-1.1.1/lib/0000755000004100000410000000000015037757013015746 5ustar www-datawww-datafaraday-multipart-1.1.1/lib/faraday/0000755000004100000410000000000015037757013017355 5ustar www-datawww-datafaraday-multipart-1.1.1/lib/faraday/multipart.rb0000644000004100000410000000171315037757013021725 0ustar www-datawww-data# frozen_string_literal: true require_relative 'multipart/version' require_relative 'multipart/file_part' require_relative 'multipart/param_part' require_relative 'multipart/middleware' module Faraday # Main Faraday::Multipart module. module Multipart Faraday::Request.register_middleware(multipart: Faraday::Multipart::Middleware) end # Aliases for Faraday v1, these are all deprecated and will be removed in v2 of this middleware FilePart = Multipart::FilePart ParamPart = Multipart::ParamPart Parts = Multipart::Parts CompositeReadIO = Multipart::CompositeReadIO # multipart-post v2.2.0 introduces a new class hierarchy for classes like Parts and UploadIO # For backwards compatibility, detect the gem version and use the right class UploadIO = if ::Gem::Requirement.new('>= 2.2.0').satisfied_by?(Multipart.multipart_post_version) ::Multipart::Post::UploadIO else ::UploadIO end end faraday-multipart-1.1.1/lib/faraday/multipart/0000755000004100000410000000000015037757013021376 5ustar www-datawww-datafaraday-multipart-1.1.1/lib/faraday/multipart/version.rb0000644000004100000410000000053615037757013023414 0ustar www-datawww-data# frozen_string_literal: true module Faraday # #:nodoc: module Multipart VERSION = '1.1.1' def self.multipart_post_version require 'multipart/post/version' ::Gem::Version.new(::Multipart::Post::VERSION) rescue LoadError require 'multipart_post' ::Gem::Version.new(::MultipartPost::VERSION) end end end faraday-multipart-1.1.1/lib/faraday/multipart/middleware.rb0000644000004100000410000000765515037757013024055 0ustar www-datawww-data# frozen_string_literal: true require 'securerandom' module Faraday module Multipart # Middleware for supporting multi-part requests. class Middleware < Faraday::Middleware CONTENT_TYPE = 'Content-Type' DEFAULT_BOUNDARY_PREFIX = '-----------RubyMultipartPost' def initialize(app = nil, options = {}) super(app) @options = options end # Checks for files in the payload, otherwise leaves everything untouched. # # @param env [Faraday::Env] def call(env) match_content_type(env) do |params| env.request.boundary ||= unique_boundary env.request_headers[CONTENT_TYPE] += "; boundary=#{env.request.boundary}" env.body = create_multipart(env, params) end @app.call env end private # @param env [Faraday::Env] # @yield [request_body] Body of the request def match_content_type(env) return unless process_request?(env) env.request_headers[CONTENT_TYPE] ||= mime_type return if env.body.respond_to?(:to_str) || env.body.respond_to?(:read) yield(env.body) end # @param env [Faraday::Env] def process_request?(env) type = request_type(env) env.body.respond_to?(:each_key) && !env.body.empty? && ( (type.empty? && has_multipart?(env.body)) || (type == mime_type) ) end # @param env [Faraday::Env] # # @return [String] def request_type(env) type = env.request_headers[CONTENT_TYPE].to_s type = type.split(';', 2).first if type.index(';') type end # Returns true if obj is an enumerable with values that are multipart. # # @param obj [Object] # @return [Boolean] def has_multipart?(obj) if obj.respond_to?(:each) (obj.respond_to?(:values) ? obj.values : obj).each do |val| return true if val.respond_to?(:content_type) || has_multipart?(val) end end false end # @param env [Faraday::Env] # @param params [Hash] def create_multipart(env, params) boundary = env.request.boundary parts = process_params(params) do |key, value| part(boundary, key, value) end parts << Faraday::Multipart::Parts::EpiloguePart.new(boundary) body = Faraday::Multipart::CompositeReadIO.new(parts) env.request_headers[Faraday::Env::ContentLength] = body.length.to_s body end def part(boundary, key, value) if value.respond_to?(:to_part) value.to_part(boundary, key) else Faraday::Multipart::Parts::Part.new(boundary, key, value) end end # @return [String] def unique_boundary "#{DEFAULT_BOUNDARY_PREFIX}-#{SecureRandom.hex}" end # @param params [Hash] # @param prefix [String] # @param pieces [Array] def process_params(params, prefix = nil, pieces = nil, &block) params.inject(pieces || []) do |all, (key, value)| if prefix key = @options[:flat_encode] ? prefix.to_s : "#{prefix}[#{key}]" end case value when Array values = value.inject([]) { |a, v| a << [nil, v] } process_params(values, key, all, &block) when Hash process_params(value, key, all, &block) else all << block.call(key, value) # rubocop:disable Performance/RedundantBlockCall end end end # Determines and provides the multipart mime type for the request. # # @return [String] the multipart mime type def mime_type @mime_type ||= if @options[:content_type].to_s.match?(%r{\Amultipart/.+}) @options[:content_type].to_s else 'multipart/form-data' end end end end end faraday-multipart-1.1.1/lib/faraday/multipart/file_part.rb0000644000004100000410000000724015037757013023673 0ustar www-datawww-data# frozen_string_literal: true require 'stringio' module Faraday # Rubocop doesn't seem to understand that this is an extension to the # Multipart module, so let's add a nodoc # #:nodoc: module Multipart # Multipart value used to POST a binary data from a file or # # @example # payload = { file: Faraday::FilePart.new("file_name.ext", "content/type") } # http.post("/upload", payload) # # @!method initialize(filename_or_io, content_type, filename = nil, opts = {}) # # @param filename_or_io [String, IO] Either a String filename to a local # file or an open IO object. # @param content_type [String] String content type of the file data. # @param filename [String] Optional String filename, usually to add context # to a given IO object. # @param opts [Hash] Optional Hash of String key/value pairs to describethis # this uploaded file. Expected Header keys include: # * Content-Transfer-Encoding - Defaults to "binary" # * Content-Disposition - Defaults to "form-data" # * Content-Type - Defaults to the content_type argument. # * Content-ID - Optional. # # @return [Faraday::FilePart] # # @!attribute [r] content_type # The uploaded binary data's content type. # # @return [String] # # @!attribute [r] original_filename # The base filename, taken either from the filename_or_io or filename # arguments in #initialize. # # @return [String] # # @!attribute [r] opts # Extra String key/value pairs to make up the header for this uploaded file. # # @return [Hash] # # @!attribute [r] io # The open IO object for the uploaded file. # # @return [IO] if ::Gem::Requirement.new('>= 2.2.0').satisfied_by?(multipart_post_version) require 'multipart/post' FilePart = ::Multipart::Post::UploadIO Parts = ::Multipart::Post::Parts else require 'composite_io' require 'parts' FilePart = ::UploadIO Parts = ::Parts end # Similar to, but not compatible with CompositeReadIO provided by the # multipart-post gem. # https://github.com/nicksieger/multipart-post/blob/master/lib/composite_io.rb class CompositeReadIO def initialize(*parts) @parts = parts.flatten @ios = @parts.map(&:to_io) @index = 0 end # @return [Integer] sum of the lengths of all the parts def length @parts.inject(0) { |sum, part| sum + part.length } end # Rewind each of the IOs and reset the index to 0. # # @return [void] def rewind @ios.each(&:rewind) @index = 0 end # Read from IOs in order until `length` bytes have been received. # # @param length [Integer, nil] # @param outbuf [String, nil] def read(length = nil, outbuf = nil) got_result = false outbuf = outbuf ? (+outbuf).replace('') : +'' while (io = current_io) if (result = io.read(length)) got_result ||= !result.nil? result.force_encoding('BINARY') if result.respond_to?(:force_encoding) outbuf << result length -= result.length if length break if length&.zero? end advance_io end !got_result && length ? nil : outbuf end # Close each of the IOs. # # @return [void] def close @ios.each(&:close) end def ensure_open_and_readable # Rubinius compatibility end private def current_io @ios[@index] end def advance_io @index += 1 end end end end faraday-multipart-1.1.1/lib/faraday/multipart/param_part.rb0000644000004100000410000000271315037757013024054 0ustar www-datawww-data# frozen_string_literal: true module Faraday module Multipart # Multipart value used to POST data with a content type. class ParamPart # @param value [String] Uploaded content as a String. # @param content_type [String] String content type of the value. # @param content_id [String] Optional String of this value's Content-ID. # # @return [Faraday::ParamPart] def initialize(value, content_type, content_id = nil) @value = value @content_type = content_type @content_id = content_id end # Converts this value to a form part. # # @param boundary [String] String multipart boundary that must not exist in # the content exactly. # @param key [String] String key name for this value. # # @return [Faraday::Parts::Part] def to_part(boundary, key) Faraday::Multipart::Parts::Part.new(boundary, key, value, headers) end # Returns a Hash of String key/value pairs. # # @return [Hash] def headers { 'Content-Type' => content_type, 'Content-ID' => content_id } end # The content to upload. # # @return [String] attr_reader :value # The value's content type. # # @return [String] attr_reader :content_type # The value's content ID, if given. # # @return [String, nil] attr_reader :content_id end end end faraday-multipart-1.1.1/LICENSE.md0000644000004100000410000000207315037757013016606 0ustar www-datawww-dataThe MIT License (MIT) Copyright (c) 2022 The Faraday Team 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. faraday-multipart-1.1.1/README.md0000644000004100000410000001173115037757013016462 0ustar www-datawww-data# Faraday Multipart [![ci](https://github.com/lostisland/faraday-multipart/actions/workflows/ci.yml/badge.svg)](https://github.com/lostisland/faraday-multipart/actions/workflows/ci.yml) [![Gem](https://img.shields.io/gem/v/faraday-multipart.svg?style=flat-square)](https://rubygems.org/gems/faraday-multipart) [![License](https://img.shields.io/github/license/lostisland/faraday-multipart.svg?style=flat-square)](LICENSE.md) The `Multipart` middleware converts a `Faraday::Request#body` Hash of key/value pairs into a multipart form request, but only under these conditions: * The request's Content-Type is "multipart/form-data" * Content-Type is unspecified, AND one of the values in the Body responds to `#content_type`. Faraday contains a couple helper classes for multipart values: * `Faraday::Multipart::FilePart` wraps binary file data with a Content-Type. The file data can be specified with a String path to a local file, or an IO object. * `Faraday::Multipart::ParamPart` wraps a String value with a Content-Type, and optionally a Content-ID. ## Installation Add this line to your application's Gemfile: ```ruby gem 'faraday-multipart' ``` And then execute: ```shell bundle install ``` Or install it yourself as: ```shell gem install faraday-multipart ``` ## Usage First of all, you'll need to add the multipart middleware to your Faraday connection: ```ruby require 'faraday' require 'faraday/multipart' conn = Faraday.new(...) do |f| f.request :multipart, **options # ... end ``` If you need to [specify a different content type for the multipart request](https://www.iana.org/assignments/media-types/media-types.xhtml#multipart), you can do so by providing the `content_type` option but it must start with `multipart/` otherwise it will default to `multipart/form-data`: ```ruby conn = Faraday.new(...) do |f| f.request :multipart, content_type: 'multipart/mixed' # ... end ``` Payload can be a mix of POST data and multipart values. ```ruby # regular POST form value payload = { string: 'value' } # filename for this value is File.basename(__FILE__) payload[:file] = Faraday::Multipart::FilePart.new(__FILE__, 'text/x-ruby') # specify filename because IO object doesn't know it payload[:file_with_name] = Faraday::Multipart::FilePart.new( File.open(__FILE__), 'text/x-ruby', File.basename(__FILE__) ) # Sets a custom Content-Disposition: # nil filename still defaults to File.basename(__FILE__) payload[:file_with_header] = Faraday::Multipart::FilePart.new( __FILE__, 'text/x-ruby', nil, 'Content-Disposition' => 'form-data; foo=1' ) # Upload raw json with content type payload[:raw_data] = Faraday::Multipart::ParamPart.new( { a: 1 }.to_json, 'application/json' ) # optionally sets Content-ID too payload[:raw_with_id] = Faraday::Multipart::ParamPart.new( { a: 1 }.to_json, 'application/json', 'foo-123' ) conn.post('/', payload) ``` ### Sending an array of documents Sometimes, the server you're calling will expect an array of documents or other values for the same key. The `multipart` middleware will automatically handle this scenario for you: ```ruby payload = { files: [ Faraday::Multipart::FilePart.new(__FILE__, 'text/x-ruby'), Faraday::Multipart::FilePart.new(__FILE__, 'text/x-pdf') ], url: [ 'http://mydomain.com/callback1', 'http://mydomain.com/callback2' ] } conn.post(url, payload) #=> POST url[]=http://mydomain.com/callback1&url[]=http://mydomain.com/callback2 #=> and includes both files in the request under the `files[]` name ``` However, by default these will be sent with `files[]` key and the URLs with `url[]`, similarly to arrays in URL parameters. Some servers (e.g. Mailgun) expect each document to have the same parameter key instead. You can instruct the `multipart` middleware to do so by providing the `flat_encode` option: ```ruby require 'faraday' require 'faraday/multipart' conn = Faraday.new(...) do |f| f.request :multipart, flat_encode: true # ... end payload = ... # see example above conn.post(url, payload) #=> POST url=http://mydomain.com/callback1&url=http://mydomain.com/callback2 #=> and includes both files in the request under the `files` name ``` This works for both `UploadIO` and normal parameters alike. ## Development After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/test` to run the tests. To install this gem onto your local machine, run `rake build`. ### Releasing a new version To release a new version, make a commit with a message such as "Bumped to 0.0.2", and change the _Unreleased_ heading in `CHANGELOG.md` to a heading like "0.0.2 (2022-01-01)", and then use GitHub Releases to author a release. A GitHub Actions workflow then publishes a new gem to [RubyGems.org](https://rubygems.org/gems/faraday-multipart). ## Contributing Bug reports and pull requests are welcome on [GitHub](https://github.com/lostisland/faraday-multipart). ## License The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). faraday-multipart-1.1.1/CHANGELOG.md0000644000004100000410000000454315037757013017017 0ustar www-datawww-data# Changelog ## [1.0.4](https://github.com/lostisland/faraday-multipart/releases/tag/v1.0.4) (2022-06-07) ### What's Changed * Drop support for 'multipart-post' < 2.0.0. This is not a breaking change as this gem's code didn't work with 1.x. * Change references to `UploadIO` and `Parts` according to class reorganization in the 'multipart-post' gem 2.2.0 (see [multipart-post gem PR #89](https://github.com/socketry/multipart-post/pull/89)) * Introduce a backwards compatible safeguard so the gem still works with previous 'multipart-post' 2.x releases. **Full Changelog**: https://github.com/lostisland/faraday-multipart/compare/v1.0.3...v1.0.4 ## [1.0.3](https://github.com/lostisland/faraday-multipart/releases/tag/v1.0.3) (2022-01-08) ### What's Changed * Add `Faraday::ParamPart` alias back by @iMacTia in https://github.com/lostisland/faraday-multipart/pull/2 **Full Changelog**: https://github.com/lostisland/faraday-multipart/compare/v1.0.2...v1.0.3 ## [1.0.2](https://github.com/lostisland/faraday-multipart/releases/tag/v1.0.2) (2022-01-06) ### Fixes * Add missing UploadIO alias * Re-add support for Ruby 2.4+ **Full Changelog**: https://github.com/lostisland/faraday-multipart/compare/v1.0.1...v1.0.2 ## [1.0.1](https://github.com/lostisland/faraday-multipart/releases/tag/v1.0.1) (2022-01-06) ### What's Changed * Add support for Faraday v1 by @iMacTia in https://github.com/lostisland/faraday-multipart/pull/1 **Full Changelog**: https://github.com/lostisland/faraday-multipart/compare/v1.0.0...v1.0.1 ## [1.0.0](https://github.com/lostisland/faraday-multipart/releases/tag/v1.0.0) (2022-01-04) ### Summary The initial release of the `faraday-multipart` gem. This middleware was previously bundled with Faraday but was removed as of v2.0. ### MIGRATION NOTES If you're upgrading from Faraday 1.0 and including this middleware as a gem, please be aware the namespacing for helper classes has changed: * `Faraday::FilePart` is now `Faraday::Multipart::FilePart` * `Faraday::Parts` is now `Faraday::Multipart::Parts` * `Faraday::CompositeReadIO` is now `Faraday::Multipart::CompositeReadIO` * `Faraday::ParamPart` is now `Faraday::Multipart::ParamPart` Moreover, in case you're adding the middleware to your faraday connection with the full qualified name rather than the `:multipart` alias, please be aware the middleware class is now `Faraday::Multipart::Middleware`.