1 // NetConnection_as.cpp: Open local connections for FLV files or URLs.
3 // Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Free Software
6 // This program is free software; you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation; either version 3 of the License, or
9 // (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software
18 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
23 #include "gnashconfig.h"
26 #include "NetConnection_as.h"
31 #include <boost/scoped_ptr.hpp>
32 #include <boost/lexical_cast.hpp>
33 #include <boost/noncopyable.hpp>
34 #include <boost/mem_fn.hpp>
37 #include "GnashSystemNetHeaders.h"
39 #include "GnashException.h"
40 #include "builtin_function.h"
41 #include "movie_root.h"
42 #include "StreamProvider.h"
45 #include "SimpleBuffer.h"
46 #include "namedStrings.h"
47 #include "GnashAlgorithm.h"
49 #include "Global_as.h"
50 #include "AMFConverter.h"
52 #include "smart_ptr.h"
53 #include "RunResources.h"
54 #include "IOChannel.h"
57 //#define GNASH_DEBUG_REMOTING
61 // Forward declarations.
63 void attachProperties(as_object
& o
);
64 void attachNetConnectionInterface(as_object
& o
);
65 as_value
netconnection_isConnected(const fn_call
& fn
);
66 as_value
netconnection_uri(const fn_call
& fn
);
67 as_value
netconnection_connect(const fn_call
& fn
);
68 as_value
netconnection_close(const fn_call
& fn
);
69 as_value
netconnection_call(const fn_call
& fn
);
70 as_value
netconnection_addHeader(const fn_call
& fn
);
71 as_value
netconnection_new(const fn_call
& fn
);
72 as_value
local_onResult(const fn_call
& fn
);
73 std::pair
<std::string
, std::string
>
74 getStatusCodeInfo(NetConnection_as::StatusCode code
);
76 /// Parse and send any invoke messages from an HTTP connection.
77 void handleAMFInvoke(amf::Reader
& rd
, const boost::uint8_t*& b
,
78 const boost::uint8_t* end
, as_object
& owner
);
80 void replyBWCheck(rtmp::RTMP
& r
, double txn
);
84 /// Abstract connection handler class
86 /// This class abstract operations on network connections,
87 /// specifically RPC and streams fetching.
88 class Connection
: boost::noncopyable
92 typedef std::map
<size_t, as_object
*> CallbacksMap
;
94 /// @param methodName A string identifying the remote procedure to call
95 /// @param responseHandler Object to invoke response methods on.
96 /// @param args A vector of arguments
97 virtual void call(as_object
* asCallback
, const std::string
& methodName
,
98 const std::vector
<as_value
>& args
) = 0;
100 /// Get an stream by name
102 /// @param name Stream identifier
103 virtual std::auto_ptr
<IOChannel
> getStream(const std::string
& /*name*/) {
104 log_unimpl("%s doesn't support fetching streams", typeName(*this));
105 return std::auto_ptr
<IOChannel
>(0);
108 /// Process pending traffic, out or in bound
110 /// Handles all networking for NetConnection::call() and dispatches
111 /// callbacks when needed.
113 /// @return false if no further advance is needed. The only
114 /// case for this is an error.
115 virtual bool advance() = 0;
117 /// Connections may store references to as_objects
118 void setReachable() const {
119 foreachSecond(_callbacks
.begin(), _callbacks
.end(),
120 std::mem_fun(&as_object::setReachable
));
123 /// Return true if the connection has pending calls
125 /// This will be used on NetConnection.close(): if current
126 /// connection has pending calls to process it will be
127 /// queued and only really dropped when advance returns false
128 virtual bool hasPendingCalls() const = 0;
134 virtual ~Connection() {}
136 as_object
* popCallback(size_t id
) {
137 CallbacksMap::iterator it
= _callbacks
.find(id
);
138 if (it
!= _callbacks
.end()) {
139 as_object
* callback
= it
->second
;
140 _callbacks
.erase(it
);
148 /// Construct a connection handler bound to the given NetConnection object
150 /// The binding is used to notify statuses and errors
152 /// The NetConnection_as owns all Connections, so there is no
153 /// need to mark it reachable.
154 Connection(NetConnection_as
& nc
)
161 void pushCallback(size_t id
, as_object
* callback
) {
162 _callbacks
[id
] = callback
;
165 // Object handling connection status messages
166 NetConnection_as
& _nc
;
170 CallbacksMap _callbacks
;
181 HTTPRequest(Connection
& h
)
186 // leave space for header
187 _data
.append("\000\000\000\000\000\000", 6);
188 _headers
["Content-Type"] = "application/x-amf";
191 /// Add AMF data to this request.
192 void addData(const SimpleBuffer
& amf
) {
193 _data
.append(amf
.data(), amf
.size());
197 void send(const URL
& url
, NetConnection_as
& nc
);
199 bool process(NetConnection_as
& nc
);
203 static const size_t NCCALLREPLYCHUNK
= 1024 * 200;
205 /// Handle replies to server functions we invoked with a callback.
207 /// This needs access to the stored callbacks.
208 void handleAMFReplies(amf::Reader
& rd
, const boost::uint8_t*& b
,
209 const boost::uint8_t* end
);
211 Connection
& _handler
;
213 /// The data to be sent by POST with this request.
216 /// A buffer for the reply.
219 /// The number of separate remoting calls to be encoded in this request.
222 /// A single HTTP request.
223 boost::scoped_ptr
<IOChannel
> _connection
;
225 /// Headers to be sent with this request.
226 NetworkAdapter::RequestHeaders _headers
;
230 /// Queue of remoting calls
232 /// This is a single conception HTTP remoting connection, which in reality
233 /// comprises a queue of separate HTTP requests.
234 class HTTPConnection
: public Connection
237 /// Create a handler for HTTP remoting
239 /// @param nc The NetConnection AS object to send status/error events to
240 /// @param url URL to post calls to
241 HTTPConnection(NetConnection_as
& nc
, const URL
& url
)
248 virtual bool hasPendingCalls() const {
249 return _currentRequest
.get() || !_requestQueue
.empty();
252 /// Queue current request, process all live requests.
253 virtual bool advance();
255 /// Check if there is a current request. If not, make one.
256 virtual void call(as_object
* asCallback
, const std::string
& methodName
,
257 const std::vector
<as_value
>& args
);
263 /// The queue of sent requests.
264 std::vector
<boost::shared_ptr
<HTTPRequest
> > _requestQueue
;
266 /// The current request.
267 boost::shared_ptr
<HTTPRequest
> _currentRequest
;
271 class RTMPConnection
: public Connection
275 RTMPConnection(NetConnection_as
& nc
, const URL
& url
)
278 _connectionComplete(false),
281 // Throw exception if this fails.
282 const bool ret
= _rtmp
.connect(url
);
283 if (!ret
) throw GnashException("Connection failed");
286 virtual void call(as_object
* asCallback
, const std::string
& methodName
,
287 const std::vector
<as_value
>& args
)
291 aw
.writeString(methodName
);
292 const size_t id
= asCallback
? callNo() : 0;
295 for (size_t i
= 0; i
< args
.size(); ++i
) {
296 args
[i
].writeAMF0(aw
);
300 pushCallback(id
, asCallback
);
304 bool hasPendingCalls() const {
308 virtual bool advance() {
312 if (_rtmp
.error() && !_connectionComplete
) {
313 _nc
.notifyStatus(NetConnection_as::CONNECT_FAILED
);
316 if (_connectionComplete
&& _rtmp
.error()) {
317 _nc
.notifyStatus(NetConnection_as::CONNECT_CLOSED
);
321 // Nothing to do yet, but we don't want to be dropped.
322 if (!_connectionComplete
) {
324 if (!_rtmp
.connected()) return true;
326 _connectionComplete
= true;
327 log_debug("Initial connection complete");
329 const RunResources
& r
= getRunResources(_nc
.owner());
330 Global_as
& gl
= getGlobal(_nc
.owner());
333 as_object
* o
= createObject(gl
);
336 o
->init_member("app", _url
.path().substr(1), flags
);
338 // TODO: check where it gets these data from.
339 o
->init_member("flashVer", getVM(_nc
.owner()).getPlayerVersion(),
341 o
->init_member("swfUrl", r
.streamProvider().baseURL().str(),
343 o
->init_member("tcUrl", _url
.str(), flags
);
345 // TODO: implement this properly
346 o
->init_member("fpad", false, flags
);
347 o
->init_member("capabilities", 15.0, flags
);
348 o
->init_member("audioCodecs", 3191.0, flags
);
349 o
->init_member("videoCodecs", 252.0, flags
);
350 o
->init_member("videoFunction", 1.0, flags
);
351 o
->init_member("pageUrl", as_value(), flags
);
353 // Set up the callback object.
354 as_object
* cb
= createObject(getGlobal(_nc
.owner()));
355 cb
->init_member(NSV::PROP_ON_RESULT
,
356 gl
.createFunction(local_onResult
), 0);
358 cb
->init_member("_conn", &_nc
.owner(), 0);
360 std::vector
<as_value
> args
;
363 call(cb
, "connect", args
);
365 // Send bandwidth check; the pp appears to do this
371 boost::shared_ptr
<SimpleBuffer
> b
= _rtmp
.getMessage();
373 if (b
&& !_nc
.isConnected()) {
377 /// Retrieve messages.
379 handleInvoke(b
->data() + rtmp::RTMPHeader::headerSize
,
380 b
->data() + b
->size());
381 b
= _rtmp
.getMessage();
389 void handleInvoke(const boost::uint8_t* payload
, const boost::uint8_t* end
);
392 bool _connectionComplete
;
397 } // anonymous namespace
399 NetConnection_as::NetConnection_as(as_object
* owner
)
406 // here to have HTTPConnection definition available
407 NetConnection_as::~NetConnection_as()
411 // extern (used by Global.cpp)
413 netconnection_class_init(as_object
& where
, const ObjectURI
& uri
)
415 registerBuiltinClass(where
, netconnection_new
,
416 attachNetConnectionInterface
, 0, uri
);
420 NetConnection_as::markReachableResources() const
422 owner().setReachable();
423 std::for_each(_oldConnections
.begin(), _oldConnections
.end(),
424 boost::mem_fn(&Connection::setReachable
));
425 if (_currentConnection
.get()) _currentConnection
->setReachable();
428 /// FIXME: this should not use _uri, but rather take a URL argument.
429 /// Validation should probably be done on connect() only and return a
430 /// bool indicating validity. That can be used to return a failure
431 /// for invalid or blocked URLs.
433 NetConnection_as::validateURL() const
435 const RunResources
& r
= getRunResources(owner());
436 URL
uri(_uri
, r
.streamProvider().baseURL());
438 std::string
uriStr(uri
.str());
439 assert(uriStr
.find("://") != std::string::npos
);
441 // Check if we're allowed to open url
442 if (!r
.streamProvider().allow(uri
)) {
443 log_security(_("Gnash is not allowed to open this url: %s"), uriStr
);
447 log_debug(_("Connection to movie: %s"), uriStr
);
453 NetConnection_as::notifyStatus(StatusCode code
)
455 std::pair
<std::string
, std::string
> info
= getStatusCodeInfo(code
);
457 /// This is a new normal object each time (see NetConnection.as)
458 as_object
* o
= createObject(getGlobal(owner()));
462 o
->init_member("code", info
.first
, flags
);
463 o
->init_member("level", info
.second
, flags
);
465 callMethod(&owner(), NSV::PROP_ON_STATUS
, o
);
469 /// Called on NetConnection.connect(null).
471 /// The status notification happens immediately, isConnected becomes true.
473 NetConnection_as::connect()
475 // Close any current connections.
478 notifyStatus(CONNECT_SUCCESS
);
483 NetConnection_as::connect(const std::string
& uri
)
485 // Close any current connections.
487 assert(!_isConnected
);
489 // TODO: check for other kind of invalidities here...
491 notifyStatus(CONNECT_FAILED
);
495 const RunResources
& r
= getRunResources(owner());
496 URL
url(_uri
, r
.streamProvider().baseURL());
498 if (!r
.streamProvider().allow(url
)) {
499 log_security(_("Gnash is not allowed to connect " "to %s"), url
);
500 notifyStatus(CONNECT_FAILED
);
504 // Attempt connection.
505 if (url
.protocol() == "https" || url
.protocol() == "http") {
506 _currentConnection
.reset(new HTTPConnection(*this, url
));
508 else if (url
.protocol() == "rtmp") {
510 _currentConnection
.reset(new RTMPConnection(*this, url
));
512 catch (const GnashException
&) {
513 // This happens if the connect cannot even be attempted.
514 notifyStatus(CONNECT_FAILED
);
519 else if (url
.protocol() == "rtmpt" || url
.protocol() == "rtmpts") {
520 log_unimpl("NetConnection.connect(%s): unsupported connection "
522 notifyStatus(CONNECT_FAILED
);
526 log_error("NetConnection.connect(%s): unknown connection "
528 notifyStatus(CONNECT_FAILED
);
535 /// FIXME: This should close an active connection as well as setting the
536 /// appropriate properties.
538 NetConnection_as::close()
540 // Send close event if a connection is in progress or connected is true.
541 const bool needSendClosedStatus
= _currentConnection
.get() || _isConnected
;
543 /// Queue the current call queue if it has pending calls
544 if (_currentConnection
.get() && _currentConnection
->hasPendingCalls()) {
545 boost::shared_ptr
<Connection
> c(_currentConnection
.release());
546 _oldConnections
.push_back(c
);
549 /// TODO: what should actually happen here? Should an attached
550 /// NetStream object be interrupted?
551 _isConnected
= false;
553 if (needSendClosedStatus
) {
554 notifyStatus(CONNECT_CLOSED
);
560 NetConnection_as::setURI(const std::string
& uri
)
562 owner().init_readonly_property("uri", &netconnection_uri
);
567 NetConnection_as::call(as_object
* asCallback
, const std::string
& methodName
,
568 const std::vector
<as_value
>& args
)
570 if (!_currentConnection
.get()) {
571 log_aserror("NetConnection.call: can't call while not connected");
575 _currentConnection
->call(asCallback
, methodName
, args
);
580 std::auto_ptr
<IOChannel
>
581 NetConnection_as::getStream(const std::string
& name
)
583 const RunResources
& ri
= getRunResources(owner());
585 const StreamProvider
& streamProvider
= ri
.streamProvider();
587 // Construct URL with base URL (assuming not connected to RTMP server..)
588 // TODO: For RTMP return the named stream from an existing RTMP connection.
589 // If name is a full or relative URL passed from NetStream.play(), it
590 // must be constructed against the base URL, not the NetConnection uri,
591 // which should always be null in this case.
592 const RcInitFile
& rcfile
= RcInitFile::getDefaultInstance();
594 URL
url(name
, streamProvider
.baseURL());
596 return streamProvider
.getStream(url
, rcfile
.saveStreamingMedia());
601 NetConnection_as::startAdvanceTimer()
603 getRoot(owner()).addAdvanceCallback(this);
607 NetConnection_as::stopAdvanceTimer()
609 getRoot(owner()).removeAdvanceCallback(this);
613 NetConnection_as::update()
616 // Handle unfinished actions in any closed connections. Currently we
617 // only expect HTTP connections here, as RTMP is closed immediately.
619 for (Connections::iterator i
= _oldConnections
.begin();
620 i
!= _oldConnections
.end(); ) {
622 Connection
& ch
= **i
;
623 // Remove on error or if there are no more actions.
624 if (!ch
.advance() || !ch
.hasPendingCalls()) {
625 i
= _oldConnections
.erase(i
);
630 // Advance current connection, but reset if there's an error.
632 // TODO: notify relevant status.
633 if (_currentConnection
.get()) {
634 if (!_currentConnection
->advance()) {
635 _currentConnection
.reset();
639 /// If there are no connections we can stop the timer.
640 if (_oldConnections
.empty() && !_currentConnection
.get()) {
645 // Anonymous namespace for NetConnection interface implementation.
648 /// NetConnection.call()
650 /// Documented to return void, and current tests suggest this might be
651 /// correct, though they don't test with any calls that might succeed.
653 netconnection_call(const fn_call
& fn
)
655 NetConnection_as
* ptr
= ensure
<ThisIsNative
<NetConnection_as
> >(fn
);
658 IF_VERBOSE_ASCODING_ERRORS(
659 log_aserror(_("NetConnection.call(): needs at least one argument"));
664 const as_value
& methodName_as
= fn
.arg(0);
665 std::string methodName
= methodName_as
.to_string();
667 #ifdef GNASH_DEBUG_REMOTING
668 std::stringstream ss
; fn
.dump_args(ss
);
669 log_debug("NetConnection.call(%s)", ss
.str());
672 // TODO: arg(1) is the response object. let it know when data comes back
673 boost::intrusive_ptr
<as_object
> asCallback
;
676 if (fn
.arg(1).is_object()) {
677 asCallback
= (toObject(fn
.arg(1), getVM(fn
)));
680 IF_VERBOSE_ASCODING_ERRORS(
681 std::stringstream ss
; fn
.dump_args(ss
);
682 log_aserror("NetConnection.call(%s): second argument must be "
683 "an object", ss
.str());
688 std::vector
<as_value
> args
;
690 args
= std::vector
<as_value
>(fn
.getArgs().begin() + 2,
693 ptr
->call(asCallback
.get(), methodName
, args
);
699 netconnection_close(const fn_call
& fn
)
701 NetConnection_as
* ptr
= ensure
<ThisIsNative
<NetConnection_as
> >(fn
);
708 netconnection_isConnected(const fn_call
& fn
)
710 NetConnection_as
* ptr
= ensure
<ThisIsNative
<NetConnection_as
> >(fn
);
711 return as_value(ptr
->isConnected());
715 netconnection_uri(const fn_call
& fn
)
717 NetConnection_as
* ptr
= ensure
<ThisIsNative
<NetConnection_as
> >(fn
);
718 return as_value(ptr
->getURI());
722 attachNetConnectionInterface(as_object
& o
)
724 Global_as
& gl
= getGlobal(o
);
726 o
.init_member("connect", gl
.createFunction(netconnection_connect
));
727 o
.init_member("addHeader", gl
.createFunction(netconnection_addHeader
));
728 o
.init_member("call", gl
.createFunction(netconnection_call
));
729 o
.init_member("close", gl
.createFunction(netconnection_close
));
733 attachProperties(as_object
& o
)
735 o
.init_readonly_property("isConnected", &netconnection_isConnected
);
738 /// \brief callback to instantiate a new NetConnection object.
739 /// \param fn the parameters from the Flash movie
740 /// \return nothing from the function call.
741 /// \note The return value is returned through the fn.result member.
743 netconnection_new(const fn_call
& fn
)
745 as_object
* obj
= ensure
<ValidThis
>(fn
);
746 obj
->setRelay(new NetConnection_as(obj
));
747 attachProperties(*obj
);
752 /// For remoting, NetConnection.connect() takes a URL. For all other streams,
755 /// For non-remoting streams:
757 /// Returns undefined if there are no arguments, true if the first
758 /// argument is null, otherwise whether the connection is allowed. The
759 /// actual result of the connection is sent with an onStatus call later.
761 /// Undefined is also a valid argument for SWF7 and above.
763 /// The isConnected property is set to the result of connect().
765 netconnection_connect(const fn_call
& fn
)
768 NetConnection_as
* ptr
= ensure
<ThisIsNative
<NetConnection_as
> >(fn
);
771 IF_VERBOSE_ASCODING_ERRORS(
772 log_aserror(_("NetConnection.connect(): needs at least "
778 const as_value
& uri
= fn
.arg(0);
780 const VM
& vm
= getVM(fn
);
781 const std::string
& uriStr
= uri
.to_string(vm
.getSWFVersion());
783 // This is always set without validification.
786 // Check first arg for validity
787 if (uri
.is_null() || (getSWFVersion(fn
) > 6 && uri
.is_undefined())) {
789 return as_value(true);
793 std::stringstream ss
; fn
.dump_args(ss
);
794 log_unimpl("NetConnection.connect(%s): args after the first are "
795 "not supported", ss
.str());
798 return as_value(ptr
->connect(uriStr
));
804 netconnection_addHeader(const fn_call
& fn
)
806 NetConnection_as
* ptr
= ensure
<ThisIsNative
<NetConnection_as
> >(fn
);
809 log_unimpl("NetConnection.addHeader()");
813 /// This creates a local callback function to handle the return from connect()
815 /// NetStream does this using a builtin function, but this is more complicated
816 /// because it needs the native NetConnection type.
818 /// This stores the NetConnection object so that it can be updated when
821 /// We don't know if this is the best way to do it, but:
823 /// 1. the connect call *does* return a callback ID.
825 local_onResult(const fn_call
& fn
)
827 as_object
* obj
= fn
.this_ptr
;
830 const ObjectURI conn
= getURI(getVM(fn
), "_conn");
831 as_value f
= getMember(*obj
, conn
);
832 as_object
* nc
= toObject(f
, getVM(fn
));
835 const as_value arg
= fn
.nargs
? fn
.arg(0) : as_value();
836 callMethod(nc
, NSV::PROP_ON_STATUS
, arg
);
842 std::pair
<std::string
, std::string
>
843 getStatusCodeInfo(NetConnection_as::StatusCode code
)
845 /// The Call statuses do exist, but this implementation is a guess.
847 case NetConnection_as::CONNECT_SUCCESS
:
848 return std::make_pair("NetConnection.Connect.Success", "status");
849 case NetConnection_as::CONNECT_FAILED
:
850 return std::make_pair("NetConnection.Connect.Failed", "error");
851 case NetConnection_as::CONNECT_APPSHUTDOWN
:
852 return std::make_pair("NetConnection.Connect.AppShutdown", "error");
853 case NetConnection_as::CONNECT_REJECTED
:
854 return std::make_pair("NetConnection.Connect.Rejected", "error");
855 case NetConnection_as::CONNECT_CLOSED
:
856 return std::make_pair("NetConnection.Connect.Closed", "status");
857 case NetConnection_as::CALL_FAILED
:
858 return std::make_pair("NetConnection.Call.Failed", "error");
859 case NetConnection_as::CALL_BADVERSION
:
860 return std::make_pair("NetConnection.Call.BadVersion", "status");
868 handleAMFInvoke(amf::Reader
& rd
, const boost::uint8_t*& b
,
869 const boost::uint8_t* end
, as_object
& owner
)
872 const boost::uint16_t invokecount
= amf::readNetworkShort(b
);
875 if (!invokecount
) return;
877 for (size_t i
= invokecount
; i
> 0; --i
) {
879 throw amf::AMFException("Invoke buffer too short");
881 const boost::uint16_t namelength
= amf::readNetworkShort(b
);
883 if (b
+ namelength
> end
) {
884 throw amf::AMFException("Invoke buffer too short");
886 std::string
headerName((char*)b
, namelength
);
888 #ifdef GNASH_DEBUG_REMOTING
889 log_debug("Invoke name %s", headerName
);
893 throw amf::AMFException("Invoke buffer too short");
895 b
+= 5; // skip past bool and length long
897 // It seems there must be exactly one argument.
900 throw amf::AMFException("Invoke argument not present");
903 VM
& vm
= getVM(owner
);
904 ObjectURI key
= getURI(vm
, headerName
);
905 #ifdef GNASH_DEBUG_REMOTING
906 log_debug("Invoking %s(%s)", headerName
, arg
);
908 callMethod(&owner
, key
, arg
);
913 /// Process any replies to server functions we invoked.
915 /// Note that fatal errors will throw an amf::AMFException.
917 HTTPRequest::handleAMFReplies(amf::Reader
& rd
, const boost::uint8_t*& b
,
918 const boost::uint8_t* end
)
920 const boost::uint16_t numreplies
= amf::readNetworkShort(b
);
921 b
+= 2; // number of replies
923 // TODO: test if this value is relevant at all.
924 if (!numreplies
) return;
926 // There should be only three loop control mechanisms in this loop:
927 // 1. continue: there was an error, but parsing is still okay.
928 // 2. return: we've finished, but there was no problem.
929 // 3. amf::AMFException: parsing failed and we can do nothing more.
931 // We haven't tested this very rigorously.
934 if (b
+ 2 > end
) return;
936 const boost::uint16_t replylength
= amf::readNetworkShort(b
);
939 if (replylength
< 4 || b
+ replylength
> end
) {
940 throw amf::AMFException("Reply message too short");
943 // Reply message is: '/id/methodName'
944 int ns
= 1; // next slash position
945 while (ns
< replylength
- 1 && *(b
+ ns
) != '/') ++ns
;
946 if (ns
>= replylength
- 1) {
947 throw amf::AMFException("Invalid reply message name");
950 std::string
id(reinterpret_cast<const char*>(b
+ 1), ns
- 1);
951 size_t callbackID
= 0;
953 callbackID
= boost::lexical_cast
<size_t>(id
);
955 catch (const boost::bad_lexical_cast
&) {
956 // Do we need to abort parsing here?
957 throw amf::AMFException("Invalid callback ID");
960 const std::string
methodName(reinterpret_cast<const char*>(b
+ ns
+ 1),
961 replylength
- ns
- 1);
965 // parse past unused string in header
966 if (b
+ 2 > end
) return;
967 const boost::uint16_t unusedlength
= amf::readNetworkShort(b
);
970 if (b
+ unusedlength
> end
) return;
973 // this field is supposed to hold the total number of bytes in the
974 // rest of this particular reply value, but openstreetmap.org
975 // (which works great in the adobe player) sends 0xffffffff.
976 // So we just ignore it.
977 if (b
+ 4 > end
) break;
980 // this updates b to point to the next unparsed byte
983 throw amf::AMFException("Could not parse argument value");
986 // if actionscript specified a callback object,
988 as_object
* callback
= _handler
.popCallback(callbackID
);
991 log_error("Unknown HTTP Remoting response identifier '%s'", id
);
992 // There's no parsing error, so continue.
997 if (methodName
== "onResult") {
998 methodKey
= NSV::PROP_ON_RESULT
;
1000 else if (methodName
== "onStatus") {
1001 methodKey
= NSV::PROP_ON_STATUS
;
1004 // NOTE: the pp is known to actually
1005 // invoke the custom method, but with 7
1006 // undefined arguments (?)
1007 log_error("Unsupported HTTP Remoting response callback: '%s' "
1008 "(size %d)", methodName
, methodName
.size());
1012 #ifdef GNASH_DEBUG_REMOTING
1013 log_debug("callback called");
1016 callMethod(callback
, methodKey
, replyval
);
1022 HTTPConnection::advance()
1024 // If there is data waiting to be sent, send it and push it
1026 if (_currentRequest
.get()) {
1027 _currentRequest
->send(_url
, _nc
);
1028 _requestQueue
.push_back(_currentRequest
);
1030 // Clear the current request for the next go.
1031 _currentRequest
.reset();
1034 // Process all replies and clear finished requests.
1035 for (std::vector
<boost::shared_ptr
<HTTPRequest
> >::iterator i
=
1036 _requestQueue
.begin(); i
!= _requestQueue
.end();) {
1037 if (!(*i
)->process(_nc
)) i
= _requestQueue
.erase(i
);
1045 HTTPRequest::send(const URL
& url
, NetConnection_as
& nc
)
1047 // We should never have a request without any calls.
1049 log_debug("creating connection");
1052 (reinterpret_cast<boost::uint16_t*>(_data
.data() + 4))[0] = htons(_calls
);
1053 std::string
postdata(reinterpret_cast<char*>(_data
.data()), _data
.size());
1055 #ifdef GNASH_DEBUG_REMOTING
1056 log_debug("NetConnection.call(): encoded args from %1% calls: %2%",
1057 _calls
, hexify(_data
.data(), _data
.size(), false));
1060 const StreamProvider
& sp
= getRunResources(nc
.owner()).streamProvider();
1061 _connection
.reset(sp
.getStream(url
, postdata
, _headers
).release());
1065 /// An AMF remoting reply comprises two main sections: first the invoke
1066 /// commands to be called on the NetConnection object, and second the
1067 /// replies to any client invoke messages that requested a callback.
1069 HTTPRequest::process(NetConnection_as
& nc
)
1071 assert(_connection
);
1073 // Fill last chunk before reading in the next
1074 size_t toRead
= _reply
.capacity() - _reply
.size();
1075 if (!toRead
) toRead
= NCCALLREPLYCHUNK
;
1077 #ifdef GNASH_DEBUG_REMOTING
1078 log_debug("Attempt to read %d bytes", toRead
);
1081 // See if we need to allocate more bytes for the next
1083 if (_reply
.capacity() < _reply
.size() + toRead
) {
1084 const size_t newCapacity
= _reply
.size() + toRead
;
1086 #ifdef GNASH_DEBUG_REMOTING
1087 log_debug("NetConnection.call: reply buffer capacity (%d) "
1088 "is too small to accept next %d bytes of chunk "
1089 "(current size is %d). Reserving %d bytes.",
1090 _reply
.capacity(), toRead
, _reply
.size(), newCapacity
);
1093 _reply
.reserve(newCapacity
);
1096 const int read
= _connection
->readNonBlocking(_reply
.data() + _reply
.size(),
1100 #ifdef GNASH_DEBUG_REMOTING
1101 log_debug("read '%1%' bytes: %2%", read
,
1102 hexify(_reply
.data() + _reply
.size(), read
, false));
1104 _reply
.resize(_reply
.size() + read
);
1107 // There is no way to tell if we have a whole amf reply without
1108 // parsing everything
1110 // The reply format has a header field which specifies the
1111 // number of bytes in the reply, but potlatch sends 0xffffffff
1112 // and works fine in the proprietary player
1114 // For now we just wait until we have the full reply.
1116 // FIXME make this parse on other conditions, including: 1) when
1117 // the buffer is full, 2) when we have a "length in bytes" value
1118 // thas is satisfied
1119 if (_connection
->bad()) {
1120 log_debug("connection is in error condition, calling "
1121 "NetConnection.onStatus");
1123 // If the connection fails, it is manually verified
1124 // that the pp calls onStatus with 1 undefined argument.
1125 callMethod(&nc
.owner(), NSV::PROP_ON_STATUS
, as_value());
1129 // Not all data was received, so carry on.
1130 if (!_connection
->eof()) return true;
1132 // If it's less than 8 we didn't expect a response, so just ignore
1134 if (_reply
.size() > 8) {
1136 #ifdef GNASH_DEBUG_REMOTING
1137 log_debug("hit eof");
1139 const boost::uint8_t *b
= _reply
.data();
1140 const boost::uint8_t *end
= _reply
.data() + _reply
.size();
1142 amf::Reader
rd(b
, end
, getGlobal(nc
.owner()));
1144 // skip version indicator and client id
1148 handleAMFInvoke(rd
, b
, end
, nc
.owner());
1149 handleAMFReplies(rd
, b
, end
);
1151 catch (const amf::AMFException
& e
) {
1153 // Any fatal error should be signalled by throwing an
1154 // exception. In this case onStatus is called with an
1155 // undefined argument.
1156 log_error("Error parsing server AMF: %s", e
.what());
1157 callMethod(&nc
.owner(), NSV::PROP_ON_STATUS
, as_value());
1161 // We've finished with this connection.
1166 HTTPConnection::call(as_object
* asCallback
, const std::string
& methodName
,
1167 const std::vector
<as_value
>& args
)
1169 if (!_currentRequest
.get()) {
1170 _currentRequest
.reset(new HTTPRequest(*this));
1173 // Create AMF buffer for this call.
1174 SimpleBuffer
buf(32);
1176 amf::writePlainString(buf
, methodName
, amf::STRING_AMF0
);
1178 const size_t callID
= callNo();
1180 // client id (result number) as counted string
1181 // the convention seems to be / followed by a unique (ascending) number
1182 std::ostringstream os
;
1184 // Call number is not used if the callback is undefined
1185 if (asCallback
) os
<< callID
;
1187 // Encode callback number.
1188 amf::writePlainString(buf
, os
.str(), amf::STRING_AMF0
);
1190 size_t total_size_offset
= buf
.size();
1191 buf
.append("\000\000\000\000", 4); // total size to be filled in later
1193 // encode array of arguments to remote method
1194 buf
.appendByte(amf::STRICT_ARRAY_AMF0
);
1195 buf
.appendNetworkLong(args
.size());
1197 // STRICT_ARRAY encoding is allowed for remoting
1198 amf::Writer
w(buf
, true);
1200 for (size_t i
= 0; i
< args
.size(); ++i
) {
1201 const as_value
& arg
= args
[i
];
1202 if (!arg
.writeAMF0(w
)) {
1203 log_error("Could not serialize NetConnection.call argument %d", i
);
1207 // Set the "total size" parameter.
1208 *(reinterpret_cast<uint32_t*>(buf
.data() + total_size_offset
)) =
1209 htonl(buf
.size() - 4 - total_size_offset
);
1211 // Add data to the current HTTPRequest.
1212 _currentRequest
->addData(buf
);
1214 // Remember the callback object.
1216 pushCallback(callID
, asCallback
);
1221 RTMPConnection::handleInvoke(const boost::uint8_t* payload
,
1222 const boost::uint8_t* end
)
1225 assert(payload
!= end
);
1227 // make sure it is a string method name we start with
1228 if (payload
[0] != 0x02) {
1229 log_error( "Sanity failed. no string method in invoke packet");
1234 std::string method
= amf::readString(payload
, end
);
1236 log_debug("Invoke: read method string %s", method
);
1237 if (*payload
!= amf::NUMBER_AMF0
) return;
1240 log_debug( "Server invoking <%s>", method
);
1242 const ObjectURI methodname
= getURI(getVM(_nc
.owner()), method
);
1244 // _result means it's the answer to a remote method call initiated
1246 if (method
== "_result") {
1247 const double id
= amf::readNumber(payload
, end
);
1248 log_debug("Received result for method call %s",
1249 boost::io::group(std::setprecision(15), id
));
1253 amf::Reader
rd(payload
, end
, getGlobal(_nc
.owner()));
1254 // TODO: use all args and check the order! We currently only use
1257 log_debug("Value: %s", arg
);
1260 as_object
* o
= popCallback(id
);
1261 callMethod(o
, NSV::PROP_ON_RESULT
, arg
);
1265 /// These are remote function calls initiated by the server.
1266 const double id
= amf::readNumber(payload
, end
);
1267 log_debug("Received server call %s %s",
1268 boost::io::group(std::setprecision(15), id
),
1269 id
? "" : "(no reply expected)");
1271 /// If the server sends this, we reply (the call should contain a
1272 /// callback object!).
1273 if (method
== "_onbwcheck") {
1274 if (id
) replyBWCheck(_rtmp
, id
);
1276 log_error("Server called _onbwcheck without a callback");
1281 if (method
== "_onbwdone") {
1283 if (*payload
!= amf::NULL_AMF0
) return;
1285 #ifdef GNASH_DEBUG_REMOTING
1286 log_debug("AMF buffer for _onbwdone: %s\n",
1287 hexify(payload
, end
- payload
, false));
1289 double latency
= amf::readNumber(payload
, end
);
1290 double bandwidth
= amf::readNumber(payload
, end
);
1291 log_debug("Latency: %s, bandwidth %s", latency
, bandwidth
);
1295 if (method
== "_error") {
1296 _nc
.notifyStatus(NetConnection_as::CALL_FAILED
);
1297 log_error( "rtmp server sent error");
1301 // Call method on the NetConnection object.
1302 callMethod(&_nc
.owner(), methodname
);
1307 replyBWCheck(rtmp::RTMP
& r
, double txn
)
1310 amf::write(buf
, "_result");
1311 amf::write(buf
, txn
);
1312 buf
.appendByte(amf::NULL_AMF0
);
1313 amf::write(buf
, 0.0);
1317 } // anonymous namespace
1318 } // end of gnash namespace
1322 // indent-tabs-mode: t