impress/lib/rocketamf_extensions/remote_gateway/request.rb
Emi Matchu 06a89689d8 Oops, fix crash when modeling Patchwork Staff (AMFPHP string encoding!)
See comment for details! I wonder if other items have been affected by
this in the past. I think probably what happened before was that we
successfully created this item, but failed to create the *translation*,
so when migrating over the Patchwork Staff all its translated fields
were empty? (That's what I found looking in the database today.)

But yeah, thankfully our crash logging at health.openneo.net gave me
the name of a pet someone was trying to model, and so I was able to
find the bug and fix it!
2024-09-27 15:18:43 -07:00

142 lines
4.2 KiB
Ruby

require 'timeout'
module RocketAMFExtensions
class RemoteGateway
class Request
ERROR_CODE = 'AMFPHP_RUNTIME_ERROR'
def initialize(action, params)
@action = action
@params = params
end
def post(options={})
uri = @action.service.gateway.uri
data = envelope.serialize
req = Net::HTTP::Post.new(uri.request_uri)
req.body = data
headers = options[:headers] || {}
headers.each do |key, value|
req[key] = value
end
res = nil
if options[:timeout]
Timeout.timeout(options[:timeout], ConnectionError) do
res = send_request(uri, req)
end
else
res = send_request(uri, req)
end
if res.is_a?(Net::HTTPSuccess)
response_body = res.body
else
error = nil
begin
res.error!
rescue Exception => scoped_error
error = scoped_error
end
raise ConnectionError, error.message
end
begin
result = RocketAMF::Envelope.new.populate_from_stream(response_body)
rescue Exception => e
raise ConnectionError, e.message, e.backtrace
end
first_message_data = HashWithIndifferentAccess.new(result.messages[0].data)
if first_message_data.respond_to?(:[]) && first_message_data[:code] == ERROR_CODE
raise RocketAMF::AMFError.new(first_message_data)
end
# HACK: It seems to me that these messages come back with Windows-1250
# (or similar) encoding on the strings? I'm basing this on the
# Patchwork Staff item, whose description arrives as:
#
# "That staff is cute, but dont use it as a walking stick \x96 I " +
# "dont think it will hold you up!"
#
# And the `\x96` is meant to represent an endash, which it doesn't in
# UTF-8 or in most extended ASCII encodings, but *does* in Windows's
# specific extended ASCII.
#
# Idk if this is something to do with the AMFPHP spec or how the AMFPHP
# server code they use serializes strings (I couldn't find any
# reference to it?), or just their internal database encoding being
# passed along as-is, or what? But this seems to be the most correct
# interpretation I know how to do, so, let's do it!
result.messages[0].data.body.tap do |body|
reencode_strings! body, "Windows-1250", "UTF-8"
end
end
private
def envelope
output = RocketAMF::Envelope.new
output.messages << wrapper_message
output
end
def wrapper_message
message = RocketAMF::Message.new 'null', '/1', [remoting_message]
end
def remoting_message
message = RocketAMF::Values::RemotingMessage.new
message.source = @action.service.name
message.operation = @action.name
message.body = @params
message
end
def send_request(uri, req)
begin
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true if uri.instance_of? URI::HTTPS
return http.request(req)
rescue Exception => e
raise ConnectionError, e.message
end
end
def reencode_strings!(target, from, to)
if target.is_a? String
target.force_encoding(from).encode!(to)
elsif target.is_a? Array
target.each { |x| reencode_strings!(x, from, to) }
elsif target.is_a? Hash
target.values.each { |x| reencode_strings!(x, from, to) }
end
end
end
class ConnectionError < RuntimeError
def initialize(message)
@message = message
end
def message
"Error connecting to gateway: #{@message}"
end
end
class AMFError < RuntimeError
DATA_KEYS = [:details, :line, :code]
attr_reader *DATA_KEYS
attr_reader :message
def initialize(data)
DATA_KEYS.each do |key|
instance_variable_set "@#{key}", data[key]
end
@message = data[:description]
end
end
end
end