forked from OpenNeo/impress
We discovered a previous string encoding bug fix that was causing crashes for some items, was casuing *other* items to get reencoded incorrectly. In this change, we make the reencoding conditional, only if parsing as UTF-8 is failing. We also include a temporary repair script, to run in production then delete—but held here in git history for posterity.
126 lines
3.7 KiB
Ruby
126 lines
3.7 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={})
|
|
response_body = if options[:timeout]
|
|
Timeout.timeout(options[:timeout], ConnectionError) do
|
|
send_request(options)
|
|
end
|
|
else
|
|
send_request(options)
|
|
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: Older items in Neopets' database have Windows-1250 encoding,
|
|
# while newer items use proper UTF-8. We detect which encoding was used
|
|
# by checking if the string is valid UTF-8, and only re-encode if needed.
|
|
#
|
|
# Example of Windows-1250 item: Patchwork Staff (57311), whose
|
|
# description contains byte 0x96 (en-dash in Windows-1250).
|
|
#
|
|
# Example of UTF-8 item: Carnival Party Décor (80042), whose name
|
|
# contains proper UTF-8 bytes [195, 169] for the é character.
|
|
result.messages[0].data.body.tap do |body|
|
|
reencode_strings_if_needed! 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(options={})
|
|
url = @action.service.gateway.uri
|
|
headers = options.fetch(:headers, []).to_a
|
|
body = envelope.serialize
|
|
|
|
begin
|
|
Sync do
|
|
DTIRequests.post(url, headers, body) do |response|
|
|
if response.status != 200
|
|
raise ConnectionError,
|
|
"expected status 200 but got #{response.status} (#{url})"
|
|
end
|
|
|
|
response.read
|
|
end
|
|
end
|
|
rescue Exception => e
|
|
raise ConnectionError, e.message
|
|
end
|
|
end
|
|
|
|
def reencode_strings_if_needed!(target, from, to)
|
|
if target.is_a? String
|
|
# Only re-encode if the string is not valid UTF-8
|
|
# (indicating it's in the old Windows-1250 encoding)
|
|
unless target.valid_encoding?
|
|
target.force_encoding(from).encode!(to)
|
|
end
|
|
elsif target.is_a? Array
|
|
target.each { |x| reencode_strings_if_needed!(x, from, to) }
|
|
elsif target.is_a? Hash
|
|
target.values.each { |x| reencode_strings_if_needed!(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
|