lib/ethon/easies/form.rb



require 'ethon/easies/util'

module Ethon
  module Easies

    # This class represents a form and is used to send a payload in the
    # request body via POST/PUT.
    # It handles multipart forms, too.
    class Form
      include Ethon::Easies::Util
      attr_accessor :escape

      # Return a new Form.
      #
      # @example Return a new Form.
      #   Form.new({})
      #
      # @param [ Hash ] params The parameter to initialize the form with.
      #
      # @return [ Form ] A new Form.
      def initialize(params)
        @params = params || {}
        ObjectSpace.define_finalizer(self, self.class.finalizer(self))
      end

      # Frees form in libcurl if necessary.
      #
      # @example Free the form
      #   Form.finalizer(form)
      #
      # @param [ Form ] form The form to free.
      def self.finalizer(form)
        proc { Curl.formfree(form.first) if form.multipart? }
      end

      # Return a pointer to the first form element in libcurl.
      #
      # @example Return the first form element.
      #   form.first
      #
      # @return [ FFI::Pointer ] The first element.
      def first
        @first ||= FFI::MemoryPointer.new(:pointer)
      end

      # Return a pointer to the last form element in libcurl.
      #
      # @example Return the last form element.
      #   form.last
      #
      # @return [ FFI::Pointer ] The last element.
      def last
        @last ||= FFI::MemoryPointer.new(:pointer)
      end

      # Return if form is multipart. The form is multipart,
      # when it contains a file.
      #
      # @example Return if form is multipart.
      #   form.multipart?
      #
      # @return [ Boolean ] True if form is multipart, else false.
      def multipart?
        query_pairs.any?{|pair| pair.last.is_a?(Array)}
      end

      # Return the query pairs.
      #
      # @example Return the query pairs.
      #   form.query_pairs
      #
      # @return [ Array ] The query pairs.
      def query_pairs
        @query_pairs ||= build_query_pairs_from_hash(@params)
      end

      # Return the string representation of the form. This makes only
      # sense when the form is not multipart.
      #
      # @example Return string representation.
      #   form.to_s
      #
      # @return [ String ] The string representation.
      def to_s
        query_pairs.map{|pair| pair.map{|e| escape ? CGI::escape(e.to_s) : e }.join("=")}.join('&')
      end

      # Return wether there are elements in the form or not.
      #
      # @example Return if form is empty.
      #   form.empty?
      #
      # @return [ Boolean ] True if form is empty, else false.
      def empty?
        @params.empty?
      end

      # Add form elements to libcurl.
      #
      # @example Add form to libcurl.
      #   form.materialize
      def materialize
        query_pairs.each { |pair| form_add(pair.first.to_s, pair.last) }
      end

      private

      def form_add(name, content)
        case content
        when Array
          Curl.formadd(first, last,
                       :form_option, :copyname, :pointer, name,
                       :form_option, :namelength, :long, name.bytesize,
                       :form_option, :file, :string, content[2],
                       :form_option, :filename, :string, content[0],
                       :form_option, :contenttype, :string, content[1],
                       :form_option, :end
                      )
        else
          Curl.formadd(first, last,
                       :form_option, :copyname, :pointer, name,
                       :form_option, :namelength, :long, name.bytesize,
                       :form_option, :copycontents, :pointer, content,
                       :form_option, :contentslength, :long, content.bytesize,
                       :form_option, :end
                      )
        end
      end
    end
  end
end