use ObjectURI more consistently
[gnash.git] / libcore / asobj / NetConnection_as.cpp
blob0672e2e27e503e807b5bb004895f07096e5664f5
1 // NetConnection_as.cpp: Open local connections for FLV files or URLs.
2 //
3 // Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Free Software
4 // Foundation, Inc
5 //
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.
10 //
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
22 #ifdef HAVE_CONFIG_H
23 #include "gnashconfig.h"
24 #endif
26 #include "NetConnection_as.h"
28 #include <iostream>
29 #include <string>
30 #include <utility>
31 #include <boost/scoped_ptr.hpp>
32 #include <boost/lexical_cast.hpp>
33 #include <boost/noncopyable.hpp>
34 #include <boost/mem_fn.hpp>
35 #include <iomanip>
37 #include "GnashSystemNetHeaders.h"
38 #include "log.h"
39 #include "GnashException.h"
40 #include "builtin_function.h"
41 #include "movie_root.h"
42 #include "StreamProvider.h"
43 #include "URL.h"
44 #include "VM.h"
45 #include "SimpleBuffer.h"
46 #include "namedStrings.h"
47 #include "GnashAlgorithm.h"
48 #include "fn_call.h"
49 #include "Global_as.h"
50 #include "AMFConverter.h"
51 #include "AMF.h"
52 #include "smart_ptr.h"
53 #include "RunResources.h"
54 #include "IOChannel.h"
55 #include "RTMP.h"
57 //#define GNASH_DEBUG_REMOTING
59 namespace gnash {
61 // Forward declarations.
62 namespace {
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
90 public:
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;
130 size_t callNo() {
131 return ++_numCalls;
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);
141 return callback;
143 return 0;
146 protected:
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)
156 _nc(nc),
157 _numCalls(0)
161 void pushCallback(size_t id, as_object* callback) {
162 _callbacks[id] = callback;
165 // Object handling connection status messages
166 NetConnection_as& _nc;
168 private:
170 CallbacksMap _callbacks;
172 size_t _numCalls;
175 // Connection types.
176 namespace {
178 class HTTPRequest
180 public:
181 HTTPRequest(Connection& h)
183 _handler(h),
184 _calls(0)
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());
194 ++_calls;
197 void send(const URL& url, NetConnection_as& nc);
199 bool process(NetConnection_as& nc);
201 private:
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.
214 SimpleBuffer _data;
216 /// A buffer for the reply.
217 SimpleBuffer _reply;
219 /// The number of separate remoting calls to be encoded in this request.
220 size_t _calls;
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
236 public:
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)
243 Connection(nc),
244 _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);
259 private:
261 const URL _url;
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
273 public:
275 RTMPConnection(NetConnection_as& nc, const URL& url)
277 Connection(nc),
278 _connectionComplete(false),
279 _url(url)
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)
289 SimpleBuffer buf;
290 amf::Writer aw(buf);
291 aw.writeString(methodName);
292 const size_t id = asCallback ? callNo() : 0;
293 aw.writeNumber(id);
295 for (size_t i = 0; i < args.size(); ++i) {
296 args[i].writeAMF0(aw);
298 _rtmp.call(buf);
299 if (asCallback) {
300 pushCallback(id, asCallback);
304 bool hasPendingCalls() const {
305 return false;
308 virtual bool advance() {
310 _rtmp.update();
312 if (_rtmp.error() && !_connectionComplete) {
313 _nc.notifyStatus(NetConnection_as::CONNECT_FAILED);
314 return false;
316 if (_connectionComplete && _rtmp.error()) {
317 _nc.notifyStatus(NetConnection_as::CONNECT_CLOSED);
318 return false;
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());
332 // Connection object
333 as_object* o = createObject(gl);
335 const int flags = 0;
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(),
340 flags);
341 o->init_member("swfUrl", r.streamProvider().baseURL().str(),
342 flags);
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;
361 args.push_back(o);
363 call(cb, "connect", args);
365 // Send bandwidth check; the pp appears to do this
366 // automatically.
367 sendServerBW(_rtmp);
371 boost::shared_ptr<SimpleBuffer> b = _rtmp.getMessage();
373 if (b && !_nc.isConnected()) {
374 _nc.setConnected();
377 /// Retrieve messages.
378 while (b.get()) {
379 handleInvoke(b->data() + rtmp::RTMPHeader::headerSize,
380 b->data() + b->size());
381 b = _rtmp.getMessage();
384 return true;
387 private:
389 void handleInvoke(const boost::uint8_t* payload, const boost::uint8_t* end);
391 rtmp::RTMP _rtmp;
392 bool _connectionComplete;
393 const URL _url;
397 } // anonymous namespace
399 NetConnection_as::NetConnection_as(as_object* owner)
401 ActiveRelay(owner),
402 _isConnected(false)
406 // here to have HTTPConnection definition available
407 NetConnection_as::~NetConnection_as()
411 // extern (used by Global.cpp)
412 void
413 netconnection_class_init(as_object& where, const ObjectURI& uri)
415 registerBuiltinClass(where, netconnection_new,
416 attachNetConnectionInterface, 0, uri);
419 void
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.
432 std::string
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);
444 return "";
447 log_debug(_("Connection to movie: %s"), uriStr);
449 return uriStr;
452 void
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()));
460 const int flags = 0;
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.
472 void
473 NetConnection_as::connect()
475 // Close any current connections.
476 close();
477 _isConnected = true;
478 notifyStatus(CONNECT_SUCCESS);
482 bool
483 NetConnection_as::connect(const std::string& uri)
485 // Close any current connections.
486 close();
487 assert(!_isConnected);
489 // TODO: check for other kind of invalidities here...
490 if (uri.empty()) {
491 notifyStatus(CONNECT_FAILED);
492 return false;
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);
501 return false;
504 // Attempt connection.
505 if (url.protocol() == "https" || url.protocol() == "http") {
506 _currentConnection.reset(new HTTPConnection(*this, url));
508 else if (url.protocol() == "rtmp") {
509 try {
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);
515 return false;
517 startAdvanceTimer();
519 else if (url.protocol() == "rtmpt" || url.protocol() == "rtmpts") {
520 log_unimpl("NetConnection.connect(%s): unsupported connection "
521 "protocol", url);
522 notifyStatus(CONNECT_FAILED);
523 return false;
525 else {
526 log_error("NetConnection.connect(%s): unknown connection "
527 "protocol", url);
528 notifyStatus(CONNECT_FAILED);
529 return false;
531 return true;
535 /// FIXME: This should close an active connection as well as setting the
536 /// appropriate properties.
537 void
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);
559 void
560 NetConnection_as::setURI(const std::string& uri)
562 owner().init_readonly_property("uri", &netconnection_uri);
563 _uri = uri;
566 void
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");
572 return;
575 _currentConnection->call(asCallback, methodName, args);
577 startAdvanceTimer();
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());
600 void
601 NetConnection_as::startAdvanceTimer()
603 getRoot(owner()).addAdvanceCallback(this);
606 void
607 NetConnection_as::stopAdvanceTimer()
609 getRoot(owner()).removeAdvanceCallback(this);
612 void
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.
618 // This may change!
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);
627 else ++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()) {
641 stopAdvanceTimer();
645 // Anonymous namespace for NetConnection interface implementation.
646 namespace {
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.
652 as_value
653 netconnection_call(const fn_call& fn)
655 NetConnection_as* ptr = ensure<ThisIsNative<NetConnection_as> >(fn);
657 if (fn.nargs < 1) {
658 IF_VERBOSE_ASCODING_ERRORS(
659 log_aserror(_("NetConnection.call(): needs at least one argument"));
661 return as_value();
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());
670 #endif
672 // TODO: arg(1) is the response object. let it know when data comes back
673 boost::intrusive_ptr<as_object> asCallback;
674 if (fn.nargs > 1) {
676 if (fn.arg(1).is_object()) {
677 asCallback = (toObject(fn.arg(1), getVM(fn)));
679 else {
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;
689 if (fn.nargs > 2) {
690 args = std::vector<as_value>(fn.getArgs().begin() + 2,
691 fn.getArgs().end());
693 ptr->call(asCallback.get(), methodName, args);
695 return as_value();
698 as_value
699 netconnection_close(const fn_call& fn)
701 NetConnection_as* ptr = ensure<ThisIsNative<NetConnection_as> >(fn);
702 ptr->close();
703 return as_value();
706 // Read-only
707 as_value
708 netconnection_isConnected(const fn_call& fn)
710 NetConnection_as* ptr = ensure<ThisIsNative<NetConnection_as> >(fn);
711 return as_value(ptr->isConnected());
714 as_value
715 netconnection_uri(const fn_call& fn)
717 NetConnection_as* ptr = ensure<ThisIsNative<NetConnection_as> >(fn);
718 return as_value(ptr->getURI());
721 void
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));
732 void
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.
742 as_value
743 netconnection_new(const fn_call& fn)
745 as_object* obj = ensure<ValidThis>(fn);
746 obj->setRelay(new NetConnection_as(obj));
747 attachProperties(*obj);
748 return as_value();
752 /// For remoting, NetConnection.connect() takes a URL. For all other streams,
753 /// it takes null.
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().
764 as_value
765 netconnection_connect(const fn_call& fn)
768 NetConnection_as* ptr = ensure<ThisIsNative<NetConnection_as> >(fn);
770 if (fn.nargs < 1) {
771 IF_VERBOSE_ASCODING_ERRORS(
772 log_aserror(_("NetConnection.connect(): needs at least "
773 "one argument"));
775 return as_value();
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.
784 ptr->setURI(uriStr);
786 // Check first arg for validity
787 if (uri.is_null() || (getSWFVersion(fn) > 6 && uri.is_undefined())) {
788 ptr->connect();
789 return as_value(true);
792 if (fn.nargs > 1) {
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));
803 as_value
804 netconnection_addHeader(const fn_call& fn)
806 NetConnection_as* ptr = ensure<ThisIsNative<NetConnection_as> >(fn);
807 UNUSED(ptr);
809 log_unimpl("NetConnection.addHeader()");
810 return as_value();
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
819 /// connect returns.
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.
824 as_value
825 local_onResult(const fn_call& fn)
827 as_object* obj = fn.this_ptr;
829 if (obj) {
830 const ObjectURI conn = getURI(getVM(fn), "_conn");
831 as_value f = getMember(*obj, conn);
832 as_object* nc = toObject(f, getVM(fn));
833 if (nc) {
835 const as_value arg = fn.nargs ? fn.arg(0) : as_value();
836 callMethod(nc, NSV::PROP_ON_STATUS, arg);
838 return as_value();
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.
846 switch (code) {
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");
861 default:
862 std::abort();
867 void
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);
873 b += 2;
875 if (!invokecount) return;
877 for (size_t i = invokecount; i > 0; --i) {
878 if (b + 2 > end) {
879 throw amf::AMFException("Invoke buffer too short");
881 const boost::uint16_t namelength = amf::readNetworkShort(b);
882 b += 2;
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);
890 #endif
891 b += namelength;
892 if (b + 5 > end) {
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.
898 as_value arg;
899 if (!rd(arg)) {
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);
907 #endif
908 callMethod(&owner, key, arg);
913 /// Process any replies to server functions we invoked.
915 /// Note that fatal errors will throw an amf::AMFException.
916 void
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.
932 while (b < end) {
934 if (b + 2 > end) return;
936 const boost::uint16_t replylength = amf::readNetworkShort(b);
937 b += 2;
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;
952 try {
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);
963 b += replylength;
965 // parse past unused string in header
966 if (b + 2 > end) return;
967 const boost::uint16_t unusedlength = amf::readNetworkShort(b);
969 b += 2;
970 if (b + unusedlength > end) return;
971 b += unusedlength;
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;
978 b += 4;
980 // this updates b to point to the next unparsed byte
981 as_value replyval;
982 if (!rd(replyval)) {
983 throw amf::AMFException("Could not parse argument value");
986 // if actionscript specified a callback object,
987 // call it
988 as_object* callback = _handler.popCallback(callbackID);
990 if (!callback) {
991 log_error("Unknown HTTP Remoting response identifier '%s'", id);
992 // There's no parsing error, so continue.
993 continue;
996 ObjectURI methodKey;
997 if (methodName == "onResult") {
998 methodKey = NSV::PROP_ON_RESULT;
1000 else if (methodName == "onStatus") {
1001 methodKey = NSV::PROP_ON_STATUS;
1003 else {
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());
1009 continue;
1012 #ifdef GNASH_DEBUG_REMOTING
1013 log_debug("callback called");
1014 #endif
1016 callMethod(callback, methodKey, replyval);
1021 bool
1022 HTTPConnection::advance()
1024 // If there is data waiting to be sent, send it and push it
1025 // to the queue.
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);
1038 else ++i;
1041 return true;
1044 void
1045 HTTPRequest::send(const URL& url, NetConnection_as& nc)
1047 // We should never have a request without any calls.
1048 assert(_calls);
1049 log_debug("creating connection");
1051 // Fill in header
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));
1058 #endif
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.
1068 bool
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);
1079 #endif
1081 // See if we need to allocate more bytes for the next
1082 // read chunk
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);
1091 #endif
1093 _reply.reserve(newCapacity);
1096 const int read = _connection->readNonBlocking(_reply.data() + _reply.size(),
1097 toRead);
1099 if (read > 0) {
1100 #ifdef GNASH_DEBUG_REMOTING
1101 log_debug("read '%1%' bytes: %2%", read,
1102 hexify(_reply.data() + _reply.size(), read, false));
1103 #endif
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());
1126 return false;
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
1133 // it.
1134 if (_reply.size() > 8) {
1136 #ifdef GNASH_DEBUG_REMOTING
1137 log_debug("hit eof");
1138 #endif
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
1145 b += 2;
1147 try {
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.
1162 return false;
1165 void
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;
1183 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.
1215 if (asCallback) {
1216 pushCallback(callID, asCallback);
1220 void
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");
1230 return;
1233 ++payload;
1234 std::string method = amf::readString(payload, end);
1236 log_debug("Invoke: read method string %s", method);
1237 if (*payload != amf::NUMBER_AMF0) return;
1238 ++payload;
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
1245 // by us.
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));
1251 as_value arg;
1253 amf::Reader rd(payload, end, getGlobal(_nc.owner()));
1254 // TODO: use all args and check the order! We currently only use
1255 // the last one!
1256 while (rd(arg)) {
1257 log_debug("Value: %s", arg);
1260 as_object* o = popCallback(id);
1261 callMethod(o, NSV::PROP_ON_RESULT, arg);
1262 return;
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);
1275 else {
1276 log_error("Server called _onbwcheck without a callback");
1278 return;
1281 if (method == "_onbwdone") {
1283 if (*payload != amf::NULL_AMF0) return;
1284 ++payload;
1285 #ifdef GNASH_DEBUG_REMOTING
1286 log_debug("AMF buffer for _onbwdone: %s\n",
1287 hexify(payload, end - payload, false));
1288 #endif
1289 double latency = amf::readNumber(payload, end);
1290 double bandwidth = amf::readNumber(payload, end);
1291 log_debug("Latency: %s, bandwidth %s", latency, bandwidth);
1292 return;
1295 if (method == "_error") {
1296 _nc.notifyStatus(NetConnection_as::CALL_FAILED);
1297 log_error( "rtmp server sent error");
1298 return;
1301 // Call method on the NetConnection object.
1302 callMethod(&_nc.owner(), methodname);
1306 void
1307 replyBWCheck(rtmp::RTMP& r, double txn)
1309 SimpleBuffer buf;
1310 amf::write(buf, "_result");
1311 amf::write(buf, txn);
1312 buf.appendByte(amf::NULL_AMF0);
1313 amf::write(buf, 0.0);
1314 r.call(buf);
1317 } // anonymous namespace
1318 } // end of gnash namespace
1320 // local Variables:
1321 // mode: C++
1322 // indent-tabs-mode: t
1323 // End: