require "faraday" require "json" module Cloudinary::BaseApi @adapter = nil class Error < CloudinaryException; end class NotFound < Error; end class NotAllowed < Error; end class AlreadyExists < Error; end class RateLimited < Error; end class BadRequest < Error; end class GeneralError < Error; end class AuthorizationRequired < Error; end class Response < Hash attr_reader :rate_limit_reset_at, :rate_limit_remaining, :rate_limit_allowed def initialize(response=nil) unless response return end # This sets the instantiated self as the response Hash update Cloudinary::Api.parse_json_response response # According to RFC 2616, header names are case-insensitive. lc_headers = response.headers.transform_keys(&:downcase) @rate_limit_allowed = lc_headers["x-featureratelimit-limit"].to_i if lc_headers["x-featureratelimit-limit"] @rate_limit_reset_at = Time.parse(lc_headers["x-featureratelimit-reset"]) if lc_headers["x-featureratelimit-reset"] @rate_limit_remaining = lc_headers["x-featureratelimit-remaining"].to_i if lc_headers["x-featureratelimit-remaining"] end end def self.extended(base) [Error, NotFound, NotAllowed, AlreadyExists, RateLimited, BadRequest, GeneralError, AuthorizationRequired, Response].each do |constant| base.const_set(constant.name.split("::").last, constant) end end def call_json_api(method, api_url, payload, timeout, headers, proxy = nil, user = nil, password = nil) conn = Faraday.new(url: api_url) do |faraday| faraday.proxy = proxy if proxy faraday.request :json faraday.adapter @adapter || Faraday.default_adapter end response = conn.run_request(method.downcase.to_sym, nil, payload, headers) do |req| req.options.timeout = timeout if timeout req.basic_auth(user, password) if user && password end return Response.new(response) if response.status == 200 exception_class = case response.status when 400 then BadRequest when 401 then AuthorizationRequired when 403 then NotAllowed when 404 then NotFound when 409 then AlreadyExists when 420 then RateLimited when 500 then GeneralError else raise GeneralError.new("Server returned unexpected status code - #{response.status} - #{response.body}") end json = Cloudinary::Api.parse_json_response(response) raise exception_class.new(json["error"]["message"]) end private def call_cloudinary_api(method, uri, auth, params, options, &api_url_builder) cloudinary = options[:upload_prefix] || Cloudinary.config.upload_prefix || 'https://api.cloudinary.com' api_url = Cloudinary::Utils.smart_escape(api_url_builder.call(cloudinary, uri).flatten.join('/')) timeout = options[:timeout] || Cloudinary.config.timeout || 60 proxy = options[:api_proxy] || Cloudinary.config.api_proxy headers = { "User-Agent" => Cloudinary::USER_AGENT } headers.merge!("Authorization" => get_authorization_header_value(auth)) if options[:content_type] == :json payload = params.to_json headers.merge!("Content-Type" => "application/json", "Accept" => "application/json") else payload = params.reject { |_, v| v.nil? || v == "" } end call_json_api(method, api_url, payload, timeout, headers, proxy) end def get_authorization_header_value(auth) if auth[:oauth_token].present? "Bearer #{auth[:oauth_token]}" else "Basic #{Base64.urlsafe_encode64("#{auth[:key]}:#{auth[:secret]}")}" end end def validate_authorization(api_key, api_secret, oauth_token) return if oauth_token.present? raise("Must supply api_key") if api_key.nil? raise("Must supply api_secret") if api_secret.nil? end end