revent 0.1 → 0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/lib/revent/CallEvent.as +25 -0
- data/lib/revent/Client.as +95 -0
- data/lib/revent/amf3/LICENSE +22 -0
- data/lib/revent/amf3/README +6 -0
- data/lib/revent/amf3/amf3.rb +11 -0
- data/lib/revent/amf3/app/configuration.rb +79 -0
- data/lib/revent/amf3/app/fault_object.rb +13 -0
- data/lib/revent/amf3/exception/rubyamf_exception.rb +95 -0
- data/lib/revent/amf3/io/amf_deserializer.rb +315 -0
- data/lib/revent/amf3/io/amf_serializer.rb +184 -0
- data/lib/revent/amf3/io/read_write.rb +308 -0
- data/lib/revent/amf3/util/string.rb +33 -0
- data/lib/revent/amf3/util/vo_helper.rb +121 -0
- data/lib/revent/as_r.rb +178 -0
- data/lib/revent/r_r.rb +176 -0
- data/test/as_r/Document.as +63 -0
- data/test/as_r/client.fla +0 -0
- data/test/as_r/client.swf +0 -0
- data/test/as_r/server.rb +48 -0
- data/{sample → test/r_r}/client.rb +0 -0
- data/{sample → test/r_r}/server.rb +0 -0
- metadata +31 -6
- data/lib/revent.rb +0 -166
@@ -0,0 +1,25 @@
|
|
1
|
+
package revent {
|
2
|
+
import flash.events.Event;
|
3
|
+
|
4
|
+
public class CallEvent extends Event {
|
5
|
+
// The connection has opened, communication is ready.
|
6
|
+
public static const CONNECT:String = "CONNECT";
|
7
|
+
// The connection has closed, do not do any communication.
|
8
|
+
public static const CLOSE:String = "CLOSE";
|
9
|
+
public static const IO_ERROR:String = "IO_ERROR";
|
10
|
+
public static const SECURITY_ERROR:String = "SECURITY_ERROR";
|
11
|
+
|
12
|
+
public static const CALL:String = "CALL";
|
13
|
+
public static const RESULT:String = "RESULT";
|
14
|
+
public static const ERROR:String = "ERROR";
|
15
|
+
|
16
|
+
public var cmd:Object;
|
17
|
+
public var value:Object;
|
18
|
+
|
19
|
+
public function CallEvent(type:String, cmd:Object, value:Object):void {
|
20
|
+
this.cmd = cmd;
|
21
|
+
this.value = value;
|
22
|
+
super(type);
|
23
|
+
}
|
24
|
+
}
|
25
|
+
}
|
@@ -0,0 +1,95 @@
|
|
1
|
+
package revent {
|
2
|
+
import flash.events.*;
|
3
|
+
import flash.net.Socket;
|
4
|
+
import flash.net.ObjectEncoding;
|
5
|
+
|
6
|
+
public class Client extends EventDispatcher {
|
7
|
+
private static const TYPE_CALL:int = 0;
|
8
|
+
private static const TYPE_RESULT:int = 1;
|
9
|
+
private static const TYPE_ERROR:int = 2;
|
10
|
+
|
11
|
+
private var _socket:Socket;
|
12
|
+
|
13
|
+
public function Client():void {
|
14
|
+
_socket = new Socket();
|
15
|
+
_socket.objectEncoding = ObjectEncoding.AMF3;
|
16
|
+
|
17
|
+
_socket.addEventListener(Event.CONNECT, onConnect);
|
18
|
+
_socket.addEventListener(Event.CLOSE, onClose);
|
19
|
+
_socket.addEventListener(IOErrorEvent.IO_ERROR, onIOError);
|
20
|
+
_socket.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSecurityError);
|
21
|
+
_socket.addEventListener(ProgressEvent.SOCKET_DATA, onData);
|
22
|
+
}
|
23
|
+
|
24
|
+
public function connect(host:String, port:int):void {
|
25
|
+
_socket.connect(host, port);
|
26
|
+
}
|
27
|
+
|
28
|
+
public function close():void {
|
29
|
+
_socket.close();
|
30
|
+
}
|
31
|
+
|
32
|
+
public function get connected():Boolean {
|
33
|
+
return _socket.connected;
|
34
|
+
}
|
35
|
+
|
36
|
+
public function call(cmd:Object, value:Object):void {
|
37
|
+
_socket.writeObject([TYPE_CALL, cmd, value]);
|
38
|
+
_socket.flush();
|
39
|
+
}
|
40
|
+
|
41
|
+
public function result(cmd:Object, value:Object):void {
|
42
|
+
_socket.writeObject([TYPE_RESULT, cmd, value]);
|
43
|
+
_socket.flush();
|
44
|
+
}
|
45
|
+
|
46
|
+
public function error(cmd:Object, value:Object):void {
|
47
|
+
_socket.writeObject([TYPE_ERROR, cmd, value]);
|
48
|
+
_socket.flush();
|
49
|
+
}
|
50
|
+
|
51
|
+
// ---------------------------------------------------------------------------
|
52
|
+
|
53
|
+
private function onConnect(event:Event):void {
|
54
|
+
dispatchEvent(new CallEvent(CallEvent.CONNECT, null, null));
|
55
|
+
}
|
56
|
+
|
57
|
+
private function onClose(event:Event):void {
|
58
|
+
dispatchEvent(new CallEvent(CallEvent.CLOSE, null, null));
|
59
|
+
}
|
60
|
+
|
61
|
+
private function onIOError(event:IOErrorEvent):void {
|
62
|
+
dispatchEvent(new CallEvent(CallEvent.IO_ERROR, null, null));
|
63
|
+
}
|
64
|
+
|
65
|
+
private function onSecurityError(event:SecurityErrorEvent):void {
|
66
|
+
dispatchEvent(new CallEvent(CallEvent.SECURITY_ERROR, null, null));
|
67
|
+
}
|
68
|
+
|
69
|
+
private function onData(event:ProgressEvent):void {
|
70
|
+
try {
|
71
|
+
while (true) {
|
72
|
+
var o:Array = _socket.readObject();
|
73
|
+
var type:int = o[0];
|
74
|
+
var cmd:Object = o[1];
|
75
|
+
var value:Object = o[2];
|
76
|
+
|
77
|
+
var e:CallEvent;
|
78
|
+
switch (type) {
|
79
|
+
case TYPE_CALL:
|
80
|
+
e = new CallEvent(CallEvent.CALL, cmd, value);
|
81
|
+
break;
|
82
|
+
case TYPE_RESULT:
|
83
|
+
e = new CallEvent(CallEvent.RESULT, cmd, value);
|
84
|
+
break;
|
85
|
+
case TYPE_ERROR:
|
86
|
+
e = new CallEvent(CallEvent.ERROR, cmd, value);
|
87
|
+
break;
|
88
|
+
}
|
89
|
+
dispatchEvent(e);
|
90
|
+
}
|
91
|
+
} catch (e:Error) {
|
92
|
+
}
|
93
|
+
}
|
94
|
+
}
|
95
|
+
}
|
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2007 Aaron Smith (aaron@rubyamf.org)
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
There is one exception to the above MIT license. WebORB may not use this code
|
11
|
+
base in any of their releases of WebORB for RoR.
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be included in
|
14
|
+
all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
22
|
+
THE SOFTWARE.
|
@@ -0,0 +1,11 @@
|
|
1
|
+
dir = File.dirname(__FILE__)
|
2
|
+
|
3
|
+
# utils must be first
|
4
|
+
require "#{dir}/util/string"
|
5
|
+
require "#{dir}/util/vo_helper"
|
6
|
+
|
7
|
+
require "#{dir}/app/fault_object"
|
8
|
+
|
9
|
+
require 'kconv' # For string.toutf8
|
10
|
+
require "#{dir}/io/amf_deserializer"
|
11
|
+
require "#{dir}/io/amf_serializer"
|
@@ -0,0 +1,79 @@
|
|
1
|
+
#This stores supporting configuration classes used in the config file to register class mappings and parameter mappings etc.
|
2
|
+
|
3
|
+
require "#{File.dirname(__FILE__)}/../exception/rubyamf_exception"
|
4
|
+
|
5
|
+
module RubyAMF
|
6
|
+
module Configuration
|
7
|
+
#ClassMappings configuration support class
|
8
|
+
class ClassMappings
|
9
|
+
|
10
|
+
# these NEED to be outside the class << self to work
|
11
|
+
@ignore_fields = ['created_at','created_on','updated_at','updated_on']
|
12
|
+
@translate_case = false
|
13
|
+
@class_mappings_by_ruby_class = {}
|
14
|
+
@class_mappings_by_actionscript_class = {}
|
15
|
+
@scoped_class_mappings_by_ruby_class = {}
|
16
|
+
@attribute_names = {}
|
17
|
+
@hash_key_access = :symbol
|
18
|
+
@translate_case = false
|
19
|
+
@force_active_record_ids = true
|
20
|
+
@assume_types = false
|
21
|
+
@use_ruby_date_time = false
|
22
|
+
@use_array_collection = false
|
23
|
+
@check_for_associations = true
|
24
|
+
|
25
|
+
# Aryk: I cleaned up how the class variables are called here. It doesnt matter if you use class variables or instance variables on the class level. Check out this simple tutorial
|
26
|
+
# - http://sporkmonger.com/2007/2/19/instance-variables-class-variables-and-inheritance-in-ruby
|
27
|
+
|
28
|
+
class << self
|
29
|
+
include RubyAMF::Exceptions
|
30
|
+
|
31
|
+
attr_accessor :ignore_fields, :use_array_collection, :default_mapping_scope, :force_active_record_ids, :attribute_names,
|
32
|
+
:use_ruby_date_time, :current_mapping_scope, :check_for_associations, :translate_case, :assume_types, :hash_key_access #the rails parameter mapping type
|
33
|
+
|
34
|
+
def register(mapping) #register a value object map
|
35
|
+
#build out ignore field logic
|
36
|
+
hashed_ignores = {}
|
37
|
+
ClassMappings.ignore_fields.to_a.each{|k| hashed_ignores[k] = true} # strings and nils will be put into an array with to_a
|
38
|
+
mapping[:ignore_fields].to_a.each{|k| hashed_ignores[k] = true}
|
39
|
+
mapping[:ignore_fields] = hashed_ignores # overwrite the original ignore fields
|
40
|
+
|
41
|
+
# if they specify custom attributes, ensure that AR ids are being passed as well if they opt for it.
|
42
|
+
if force_active_record_ids && mapping[:attributes] && mapping[:type]=="active_record" && !mapping[:attributes].include?("id")
|
43
|
+
mapping[:attributes] << "id"
|
44
|
+
end
|
45
|
+
|
46
|
+
# created caching hashes for mapping
|
47
|
+
@class_mappings_by_ruby_class[mapping[:ruby]] = mapping # for quick referencing purposes
|
48
|
+
@class_mappings_by_actionscript_class[mapping[:actionscript]] = mapping # for quick referencing purposes
|
49
|
+
@scoped_class_mappings_by_ruby_class[mapping[:ruby]] = {} # used later for caching based on scope (will get cached after the first run)
|
50
|
+
# for deserialization - looking up in a hash is faster than looking up in an array.
|
51
|
+
begin
|
52
|
+
if mapping[:type] == "active_record"
|
53
|
+
@attribute_names[mapping[:ruby]] = (mapping[:ruby].constantize.new.attribute_names + ["id"]).inject({}){|hash, attr| hash[attr]=true ; hash} # include the id attribute
|
54
|
+
end
|
55
|
+
rescue ActiveRecord::StatementInvalid => e
|
56
|
+
# This error occurs during migrations, since the AR constructed above will check its columns, but the table won't exist yet.
|
57
|
+
# We'll ignore the error if we're migrating.
|
58
|
+
raise unless ARGV.include?("migrate") or ARGV.include?("db:migrate")
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def get_vo_mapping_for_ruby_class(ruby_class)
|
63
|
+
return unless scoped_class_mapping = @scoped_class_mappings_by_ruby_class[ruby_class] # just in case they didnt specify a ClassMapping for this Ruby Class
|
64
|
+
scoped_class_mapping[@current_mapping_scope] ||= (if vo_mapping = @class_mappings_by_ruby_class[ruby_class]
|
65
|
+
vo_mapping = vo_mapping.dup # need to duplicate it or else we will overwrite the keys from the original mappings
|
66
|
+
vo_mapping[:attributes] = vo_mapping[:attributes][@current_mapping_scope]||[] if vo_mapping[:attributes].is_a?(Hash) # don't include any of these attributes if there is no scope
|
67
|
+
vo_mapping[:associations] = vo_mapping[:associations][@current_mapping_scope]||[] if vo_mapping[:associations].is_a?(Hash) # don't include any of these attributes
|
68
|
+
vo_mapping
|
69
|
+
end
|
70
|
+
)
|
71
|
+
end
|
72
|
+
|
73
|
+
def get_vo_mapping_for_actionscript_class(actionscript_class)
|
74
|
+
@class_mappings_by_actionscript_class[actionscript_class]
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
#This is a helper to return FaultObjects. Often times there are sitiuations with database logic that requires an "error state"
|
2
|
+
#to be set in Flash / Flex, but returning false isn't the best because it still get's mapped to the onResult handler, even returning a
|
3
|
+
#generic object with some specific keys set, such as ({error:'someError', code:3}). That is still a pain because it gets mapped to
|
4
|
+
#the onResult function still. So return one of these objects to RubyAMF and it will auto generate a faultObject to return to flash
|
5
|
+
#so that it maps correctly to the onFault handler.
|
6
|
+
class FaultObject < Hash
|
7
|
+
def initialize(message = '')
|
8
|
+
self['faultCode'] = 1
|
9
|
+
self['code'] = 1
|
10
|
+
self['message'] = message
|
11
|
+
self['faultString'] = message
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
module RubyAMF
|
2
|
+
module Exceptions
|
3
|
+
|
4
|
+
#Encompasses all rubyamf specific exceptions that occur
|
5
|
+
class RUBYAMFException < Exception
|
6
|
+
|
7
|
+
#when version is not 0 or 3
|
8
|
+
@VERSION_ERROR = 'RUBYAMF_AMF_VERSION_ERROR'
|
9
|
+
|
10
|
+
#when translating the target_uri of a body, there isn't a .(period) to map the service / method name
|
11
|
+
@SERVICE_TRANSLATION_ERROR = 'RUBYAMF_SERVICE_TRANSLATION_ERROR'
|
12
|
+
|
13
|
+
#when an authentication error occurs
|
14
|
+
@AUTHENTICATION_ERROR = 'RUBYAMF_ATUHENTICATION_ERROR'
|
15
|
+
|
16
|
+
#when a method is called, but the method is either private or doesn't exist
|
17
|
+
@METHOD_ACCESS_ERROR = 'RUBYAMF_METHOD_ACCESS_ERROR'
|
18
|
+
|
19
|
+
#when a mehod is undefined
|
20
|
+
@METHOD_UNDEFINED_METHOD_ERROR = 'RUBYAMF_UNDECLARED_METHOD_ERROR'
|
21
|
+
|
22
|
+
#when there is an error with session implementation
|
23
|
+
@SESSION_ERROR = 'RUBYAMF_SESSION_ERROR'
|
24
|
+
|
25
|
+
#when a general user error has occured
|
26
|
+
@USER_ERROR = 'RUBYAMF_USER_ERROR'
|
27
|
+
|
28
|
+
#when parsing AMF3, an undefined object reference
|
29
|
+
@UNDEFINED_OBJECT_REFERENCE_ERROR = 'RUBYAMF_UNDEFINED_OBJECT_REFERENCE_ERROR'
|
30
|
+
|
31
|
+
#when parsing AMF3, an undefined class definition
|
32
|
+
@UNDEFINED_DEFINITION_REFERENCE_ERROR = 'RUBYAMF_UNDEFINED_DEFINIITON_REFERENCE_ERROR'
|
33
|
+
|
34
|
+
#when parsing amf3, an undefined string reference
|
35
|
+
@UNDEFINED_STRING_REFERENCE_ERROR = 'RUBYAMF_UNDEFINED_STRING_REFERENCE_ERROR'
|
36
|
+
|
37
|
+
#unsupported AMF0 type
|
38
|
+
@UNSUPPORTED_AMF0_TYPE = 'UNSUPPORTED_AMF0_TYPE'
|
39
|
+
|
40
|
+
#when the Rails ActionController Filter chain haults
|
41
|
+
@FILTER_CHAIN_HAULTED = 'RAILS_ACTION_CONTROLLER_FILTER_CHAIN_HAULTED'
|
42
|
+
|
43
|
+
#when active record errors
|
44
|
+
@ACTIVE_RECORD_ERRORS = 'ACTIVE_RECORD_ERRORS'
|
45
|
+
|
46
|
+
#whan amf data is incomplete or incorrect
|
47
|
+
@AMF_ERROR = 'AMF_ERROR'
|
48
|
+
|
49
|
+
#vo errors
|
50
|
+
@VO_ERROR = 'VO_ERROR'
|
51
|
+
|
52
|
+
#when a parameter mapping error occurs
|
53
|
+
@PARAMETER_MAPPING_ERROR = "PARAMETER_MAPPING_ERROR"
|
54
|
+
|
55
|
+
attr_accessor :message
|
56
|
+
attr_accessor :etype
|
57
|
+
attr_accessor :ebacktrace
|
58
|
+
|
59
|
+
#static accessors
|
60
|
+
class << self
|
61
|
+
attr_accessor :VERSION_ERROR
|
62
|
+
attr_accessor :SERVICE_TRANSLATION_ERROR
|
63
|
+
attr_accessor :AUTHENTICATION_ERROR
|
64
|
+
attr_accessor :METHOD_ACCESS_ERROR
|
65
|
+
attr_accessor :METHOD_UNDEFINED_METHOD_ERROR
|
66
|
+
attr_accessor :SESSION_ERROR
|
67
|
+
attr_accessor :USER_ERROR
|
68
|
+
attr_accessor :UNDEFINED_OBJECT_REFERENCE_ERROR
|
69
|
+
attr_accessor :UNDEFINED_DEFINITION_REFERENCE_ERROR
|
70
|
+
attr_accessor :UNDEFINED_STRING_REFERENCE_ERROR
|
71
|
+
attr_accessor :UNSUPPORTED_TYPE
|
72
|
+
attr_accessor :ADAPTER_ERROR
|
73
|
+
attr_accessor :INTERNAL_ERROR
|
74
|
+
attr_accessor :UNSUPPORTED_AMF0_TYPE
|
75
|
+
attr_accessor :FILTER_CHAIN_HAULTED
|
76
|
+
attr_accessor :ACTIVE_RECORD_ERRORS
|
77
|
+
attr_accessor :VO_ERROR
|
78
|
+
attr_accessor :AMF_ERROR
|
79
|
+
attr_accessor :PARAMETER_MAPPING_ERROR
|
80
|
+
end
|
81
|
+
|
82
|
+
def initialize(type,msg)
|
83
|
+
super(msg)
|
84
|
+
@message = msg
|
85
|
+
@etype = type
|
86
|
+
end
|
87
|
+
|
88
|
+
# stringify the message
|
89
|
+
def to_s
|
90
|
+
@msg
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,315 @@
|
|
1
|
+
module RubyAMF
|
2
|
+
module IO
|
3
|
+
class AMFDeserializer
|
4
|
+
|
5
|
+
require "#{File.dirname(__FILE__)}/read_write"
|
6
|
+
|
7
|
+
include RubyAMF::Configuration
|
8
|
+
include RubyAMF::Exceptions
|
9
|
+
include RubyAMF::IO::BinaryReader
|
10
|
+
include RubyAMF::IO::Constants
|
11
|
+
include RubyAMF::VoHelper
|
12
|
+
|
13
|
+
attr_accessor :stream
|
14
|
+
attr_accessor :stream_position
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
reset
|
18
|
+
end
|
19
|
+
|
20
|
+
def reset
|
21
|
+
@stream = ''
|
22
|
+
@stream_position = 0
|
23
|
+
reset_referencables
|
24
|
+
end
|
25
|
+
|
26
|
+
def reset_referencables
|
27
|
+
@stored_strings = []
|
28
|
+
@stored_objects = []
|
29
|
+
@stored_defs = []
|
30
|
+
end
|
31
|
+
|
32
|
+
def read_amf3
|
33
|
+
type = read_word8
|
34
|
+
case type
|
35
|
+
when AMF3_UNDEFINED
|
36
|
+
nil
|
37
|
+
when AMF3_NULL
|
38
|
+
nil
|
39
|
+
when AMF3_FALSE
|
40
|
+
false
|
41
|
+
when AMF3_TRUE
|
42
|
+
true
|
43
|
+
when AMF3_INTEGER
|
44
|
+
read_amf3_integer
|
45
|
+
when AMF3_NUMBER
|
46
|
+
read_amf3_number
|
47
|
+
when AMF3_STRING
|
48
|
+
read_amf3_string
|
49
|
+
when AMF3_XML
|
50
|
+
read_amf3_xml_string
|
51
|
+
when AMF3_DATE
|
52
|
+
read_amf3_date
|
53
|
+
when AMF3_ARRAY
|
54
|
+
read_amf3_array
|
55
|
+
when AMF3_OBJECT
|
56
|
+
read_amf3_object
|
57
|
+
when AMF3_XML_STRING
|
58
|
+
read_amf3_xml
|
59
|
+
when AMF3_BYTE_ARRAY
|
60
|
+
read_amf3_byte_array
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def read_amf3_integer
|
65
|
+
n = 0
|
66
|
+
b = read_word8||0
|
67
|
+
result = 0
|
68
|
+
|
69
|
+
while ((b & 0x80) != 0 && n < 3)
|
70
|
+
result = result << 7
|
71
|
+
result = result | (b & 0x7f)
|
72
|
+
b = read_word8||0
|
73
|
+
n = n + 1
|
74
|
+
end
|
75
|
+
|
76
|
+
if (n < 3)
|
77
|
+
result = result << 7
|
78
|
+
result = result | b
|
79
|
+
else
|
80
|
+
#Use all 8 bits from the 4th byte
|
81
|
+
result = result << 8
|
82
|
+
result = result | b
|
83
|
+
|
84
|
+
#Check if the integer should be negative
|
85
|
+
if (result > AMF3_INTEGER_MAX)
|
86
|
+
result -= (1 << 29)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
return result
|
90
|
+
end
|
91
|
+
|
92
|
+
def read_amf3_string
|
93
|
+
type = read_amf3_integer
|
94
|
+
isReference = (type & 0x01) == 0
|
95
|
+
if isReference
|
96
|
+
reference = type >> 1
|
97
|
+
if reference < @stored_strings.length
|
98
|
+
if @stored_strings[reference] == nil
|
99
|
+
raise( RUBYAMFException.new(RUBYAMFException.UNDEFINED_OBJECT_REFERENCE_ERROR, "Reference to non existant string at index #{reference}, please tell aaron@rubyamf.org"))
|
100
|
+
end
|
101
|
+
return @stored_strings[reference]
|
102
|
+
else
|
103
|
+
raise( RUBYAMFException.new(RUBYAMFException.UNDEFINED_STRING_REFERENCE_ERROR, "Reference to non existant string at index #{reference}, please tell aaron@rubyamf.org") )
|
104
|
+
end
|
105
|
+
else
|
106
|
+
|
107
|
+
length = type >> 1
|
108
|
+
|
109
|
+
#Note that we have to read the string into a byte buffer and then
|
110
|
+
#convert to a UTF-8 string, because for standard readUTF() it
|
111
|
+
#reads an unsigned short to get the string length.
|
112
|
+
#A string isn't stored as a reference if it is the empty string
|
113
|
+
#thanks Karl von Randow for this
|
114
|
+
if length > 0
|
115
|
+
str = String.new(readn(length)) #specifically cast as string, as we're reading verbatim from the stream
|
116
|
+
str.toutf8 #convert to utf8
|
117
|
+
@stored_strings << str
|
118
|
+
end
|
119
|
+
return str
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def read_amf3_xml
|
124
|
+
type = read_amf3_integer
|
125
|
+
length = type >> 1
|
126
|
+
readn(length)
|
127
|
+
end
|
128
|
+
|
129
|
+
def read_amf3_date
|
130
|
+
type = read_amf3_integer
|
131
|
+
isReference = (type & 0x01) == 0
|
132
|
+
if isReference
|
133
|
+
reference = type >> 1
|
134
|
+
if reference < @stored_objects.length
|
135
|
+
if @stored_objects[reference] == nil
|
136
|
+
|
137
|
+
raise( RUBYAMFException.new(RUBYAMFException.UNDEFINED_OBJECT_REFERENCE_ERROR, "Reference to non existant date at index #{reference}, please tell aaron@rubyamf.org"))
|
138
|
+
end
|
139
|
+
return @stored_objects[reference]
|
140
|
+
else
|
141
|
+
raise( RUBYAMFException.new(RUBYAMFException.UNDEFINED_OBJECT_REFERENCE_ERROR, "Undefined date object reference when deserialing AMF3: #{reference}") )
|
142
|
+
end
|
143
|
+
else
|
144
|
+
seconds = read_double.to_f/1000
|
145
|
+
time = if (seconds < 0) || ClassMappings.use_ruby_date_time # we can't use Time if its a negative second value
|
146
|
+
DateTime.strptime(seconds.to_s, "%s")
|
147
|
+
else
|
148
|
+
Time.at(seconds)
|
149
|
+
end
|
150
|
+
@stored_objects << time
|
151
|
+
time
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def read_amf3_array
|
156
|
+
type = read_amf3_integer
|
157
|
+
isReference = (type & 0x01) == 0
|
158
|
+
|
159
|
+
if isReference
|
160
|
+
reference = type >> 1
|
161
|
+
if reference < @stored_objects.length
|
162
|
+
if @stored_objects[reference] == nil
|
163
|
+
raise(RUBYAMFException.new(RUBYAMFException.UNDEFINED_OBJECT_REFERENCE_ERROR, "Reference to non existant array at index #{reference}, please tell aaron@rubyamf.org"))
|
164
|
+
end
|
165
|
+
return @stored_objects[reference]
|
166
|
+
else
|
167
|
+
raise Exception.new("Reference to non-existent array at index #{reference}, please tell aaron@rubyamf.org")
|
168
|
+
end
|
169
|
+
else
|
170
|
+
length = type >> 1
|
171
|
+
propertyName = read_amf3_string
|
172
|
+
if propertyName != nil
|
173
|
+
array = {}
|
174
|
+
@stored_objects << array
|
175
|
+
begin
|
176
|
+
while(propertyName.length)
|
177
|
+
value = read_amf3
|
178
|
+
array[propertyName] = value
|
179
|
+
propertyName = read_amf3_string
|
180
|
+
end
|
181
|
+
rescue Exception => e #end of object exception, because propertyName.length will be non existent
|
182
|
+
end
|
183
|
+
0.upto(length - 1) do |i|
|
184
|
+
array["" + i.to_s] = read_amf3
|
185
|
+
end
|
186
|
+
else
|
187
|
+
array = []
|
188
|
+
@stored_objects << array
|
189
|
+
0.upto(length - 1) do
|
190
|
+
array << read_amf3
|
191
|
+
end
|
192
|
+
end
|
193
|
+
array
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def read_amf3_object
|
198
|
+
type = read_amf3_integer
|
199
|
+
isReference = (type & 0x01) == 0
|
200
|
+
|
201
|
+
if isReference
|
202
|
+
reference = type >> 1
|
203
|
+
if reference < @stored_objects.length
|
204
|
+
if @stored_objects[reference] == nil
|
205
|
+
raise( RUBYAMFException.new(RUBYAMFException.UNDEFINED_OBJECT_REFERENCE_ERROR, "Reference to non existant object at index #{reference}, please tell aaron@rubyamf.org."))
|
206
|
+
end
|
207
|
+
return @stored_objects[reference]
|
208
|
+
else
|
209
|
+
raise( RUBYAMFException.new(RUBYAMFException.UNDEFINED_OBJECT_REFERENCE_ERROR, "Reference to non existant object #{reference}"))
|
210
|
+
end
|
211
|
+
else
|
212
|
+
|
213
|
+
class_type = type >> 1
|
214
|
+
class_is_reference = (class_type & 0x01) == 0
|
215
|
+
|
216
|
+
if class_is_reference
|
217
|
+
class_reference = class_type >> 1
|
218
|
+
if class_reference < @stored_defs.length
|
219
|
+
class_definition = @stored_defs[class_reference]
|
220
|
+
else
|
221
|
+
raise RUBYAMFException.new(RUBYAMFException.UNDEFINED_DEFINITION_REFERENCE_ERROR, "Reference to non existant class definition #{class_reference}")
|
222
|
+
end
|
223
|
+
else
|
224
|
+
actionscript_class_name = read_amf3_string
|
225
|
+
externalizable = (class_type & 0x02) != 0
|
226
|
+
dynamic = (class_type & 0x04) != 0
|
227
|
+
attribute_count = class_type >> 3
|
228
|
+
|
229
|
+
class_attributes = []
|
230
|
+
attribute_count.times{class_attributes << read_amf3_string} # Read class members
|
231
|
+
|
232
|
+
class_definition = {"as_class_name" => actionscript_class_name, "members" => class_attributes, "externalizable" => externalizable, "dynamic" => dynamic}
|
233
|
+
@stored_defs << class_definition
|
234
|
+
end
|
235
|
+
action_class_name = class_definition['as_class_name'] #get the className according to type
|
236
|
+
|
237
|
+
# check to see if its the first main amf object or a flex message obj, because then we need a _explicitType field type and skip some things
|
238
|
+
skip_mapping = if action_class_name && action_class_name.include?("flex.messaging")
|
239
|
+
obj = VoHash.new # initialize an empty VoHash value holder
|
240
|
+
obj._explicitType = action_class_name
|
241
|
+
true
|
242
|
+
else # otherwise just use a normal hash
|
243
|
+
obj = {}
|
244
|
+
false
|
245
|
+
end
|
246
|
+
|
247
|
+
obj_position = @stored_objects.size # need to replace the object later for referencing (MUST be before inserting the object into stored_objs)
|
248
|
+
@stored_objects << obj
|
249
|
+
|
250
|
+
if class_definition['externalizable']
|
251
|
+
if ['flex.messaging.io.ObjectProxy','flex.messaging.io.ArrayCollection'].include?(action_class_name)
|
252
|
+
obj = read_amf3
|
253
|
+
else
|
254
|
+
raise( RUBYAMFException.new(RUBYAMFException.USER_ERROR, "Unable to read externalizable data type #{type}"))
|
255
|
+
end
|
256
|
+
else
|
257
|
+
translate_case = !skip_mapping&&ClassMappings.translate_case # remove the need for a method call / also, don't want to convert on main remoting object
|
258
|
+
class_definition['members'].each do |key|
|
259
|
+
value = read_amf3
|
260
|
+
#if (value)&& value != 'NaN'# have to read key to move the reader ahead in the stream
|
261
|
+
key.to_snake! if translate_case
|
262
|
+
obj[key] = value
|
263
|
+
#end
|
264
|
+
end
|
265
|
+
|
266
|
+
if class_definition['dynamic']
|
267
|
+
while (key = read_amf3_string) && key.length != 0 do # read next key
|
268
|
+
value = read_amf3
|
269
|
+
#if (value) && value != 'NaN'
|
270
|
+
key.to_snake! if translate_case
|
271
|
+
obj[key] = value
|
272
|
+
#end
|
273
|
+
end
|
274
|
+
end
|
275
|
+
obj = VoUtil.get_vo_for_incoming(obj,action_class_name) unless skip_mapping
|
276
|
+
end
|
277
|
+
@stored_objects[obj_position] = obj # put the new object into the same position as the original object since it was worked on
|
278
|
+
obj
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
def read_amf3_byte_array # according to the charles amf3 deserializer, they store byte array
|
283
|
+
type = read_amf3_integer
|
284
|
+
isReference = (type & 0x01) == 0
|
285
|
+
if isReference
|
286
|
+
reference = type >> 1
|
287
|
+
if reference < @stored_objects.length
|
288
|
+
if @stored_objects[reference] == nil
|
289
|
+
raise( RUBYAMFException.new(RUBYAMFException.UNDEFINED_OBJECT_REFERENCE_ERROR, "Reference to non existant byteArray at index #{reference}, please tell aaron@rubyamf.org"))
|
290
|
+
end
|
291
|
+
return @stored_objects[reference]
|
292
|
+
else
|
293
|
+
raise( RUBYAMFException.new(RUBYAMFException.UNDEFINED_OBJECT_REFERENCE_ERROR, "Reference to non existant byteArray #{reference}"))
|
294
|
+
end
|
295
|
+
else
|
296
|
+
length = type >> 1
|
297
|
+
begin # first assume its gzipped and rescue an exception if its not
|
298
|
+
inflated_stream = Zlib::Inflate.inflate( self.stream[self.stream_position,length] )
|
299
|
+
arr = inflated_stream.unpack('c'*inflated_stream.length)
|
300
|
+
rescue Exception => e
|
301
|
+
arr = self.stream[self.stream_position,length].unpack('c'*length)
|
302
|
+
end
|
303
|
+
self.stream_position += length
|
304
|
+
@stored_objects << arr
|
305
|
+
arr
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
def read_amf3_number
|
310
|
+
res = read_double
|
311
|
+
res.is_a?(Float)&&res.nan? ? nil : res # check for NaN and convert them to nil
|
312
|
+
end
|
313
|
+
end
|
314
|
+
end
|
315
|
+
end
|