require "savon/options"
require "savon/block_interface"
require "savon/request"
require "savon/builder"
require "savon/response"
require "savon/request_logger"
require "savon/http_error"
require "mail"
require 'faraday/gzip'
module Savon
class Operation
SOAP_REQUEST_TYPE = {
1 => "text/xml",
2 => "application/soap+xml"
}
SOAP_REQUEST_TYPE_MTOM = "application/xop+xml"
def self.create(operation_name, wsdl, globals)
if wsdl.document?
ensure_name_is_symbol! operation_name
ensure_exists! operation_name, wsdl
end
new(operation_name, wsdl, globals)
end
def self.ensure_exists!(operation_name, wsdl)
unless wsdl.soap_actions.include? operation_name
raise UnknownOperationError, "Unable to find SOAP operation: #{operation_name.inspect}\n" \
"Operations provided by your service: #{wsdl.soap_actions.inspect}"
end
rescue Wasabi::Resolver::HTTPError => e
raise HTTPError.new(e.response)
end
def self.ensure_name_is_symbol!(operation_name)
unless operation_name.kind_of? Symbol
raise ArgumentError, "Expected the first parameter (the name of the operation to call) to be a symbol\n" \
"Actual: #{operation_name.inspect} (#{operation_name.class})"
end
end
def initialize(name, wsdl, globals)
@name = name
@wsdl = wsdl
@globals = globals
@logger = RequestLogger.new(globals)
end
def build(locals = {}, &block)
set_locals(locals, block)
Builder.new(@name, @wsdl, @globals, @locals)
end
def call(locals = {}, &block)
builder = build(locals, &block)
response = Savon.notify_observers(@name, builder, @globals, @locals)
response ||= call_with_logging build_connection(builder)
raise_expected_faraday_response! unless response.kind_of?(Faraday::Response)
create_response(response)
end
def request(locals = {}, &block)
builder = build(locals, &block)
connection = build_connection(builder)
connection.build_request(:post) do |req|
req.url(@globals[:endpoint])
req.body = @locals[:body]
end
end
private
def create_response(response)
Response.new(response, @globals, @locals)
end
def set_locals(locals, block)
locals = LocalOptions.new(locals)
BlockInterface.new(locals).evaluate(block) if block
@locals = locals
end
def call_with_logging(connection)
ntlm_auth = handle_ntlm(connection) if @globals.include?(:ntlm)
@logger.log_response(connection.post(@globals[:endpoint]) { |request|
request.body = @locals[:body]
request.['Authorization'] = "NTLM #{ntlm_auth.encode64}" if ntlm_auth
@logger.log_request(request)
})
end
def handle_ntlm(connection)
ntlm_message = Net::NTLM::Message
response = connection.get(@globals[:endpoint]) do |request|
request.['Authorization'] = 'NTLM ' + ntlm_message::Type1.new.encode64
end
challenge = response.['www-authenticate'][/(?:NTLM|Negotiate) (.*)$/, 1]
message = ntlm_message::Type2.decode64(challenge)
message.response([:user, :password, :domain].zip(@globals[:ntlm]).to_h)
end
def build_connection(builder)
@globals[:endpoint] ||= endpoint
@locals[:soap_action] ||= soap_action
@locals[:body] = builder.to_s
@connection = SOAPRequest.new(@globals).build(
:soap_action => soap_action,
:cookies => @locals[:cookies],
:headers => @locals[:headers]
) do |connection|
if builder.multipart
= ["multipart/related"]
if @locals[:mtom]
<< "type=\"#{SOAP_REQUEST_TYPE_MTOM}\""
<< "start-info=\"text/xml\""
else
<< "type=\"#{SOAP_REQUEST_TYPE[@globals[:soap_version]]}\""
connection.request :gzip
end
connection.["Content-Type"] = ( + ["start=\"#{builder.multipart[:start]}\"",
"boundary=\"#{builder.multipart[:multipart_boundary]}\""]).join("; ")
connection.["MIME-Version"] = "1.0"
end
connection.["Content-Length"] = @locals[:body].bytesize.to_s
end
end
def soap_action
return if @locals.include?(:soap_action) && !@locals[:soap_action]
@locals[:soap_action] ||
@wsdl.document? && @wsdl.soap_action(@name.to_sym) ||
Gyoku.xml_tag(@name, :key_converter => @globals[:convert_request_keys_to])
end
def endpoint
@globals[:endpoint] || @wsdl.endpoint.tap do |url|
if @globals[:host]
host_url = URI.parse(@globals[:host])
url.host = host_url.host
url.port = host_url.port
end
end
end
def raise_expected_faraday_response!
raise Error, "Observers need to return a Faraday::Response to mock " \
"the request or nil to execute the request."
end
end
end