update copyright date
[gnash.git] / libcore / asobj / NetConnection_as.cpp
blob6c3e27556456357f8b6fe916a15ee012f836725d
1 // NetConnection_as.cpp: Open local connections for FLV files or URLs.
2 //
3 // Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010,
4 // 2011 Free Software 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
21 #ifdef HAVE_CONFIG_H
22 #include "gnashconfig.h"
23 #endif
25 #include "NetConnection_as.h"
27 #include <string>
28 #include <utility>
29 #include <boost/scoped_ptr.hpp>
30 #include <boost/lexical_cast.hpp>
31 #include <boost/noncopyable.hpp>
32 #include <boost/mem_fn.hpp>
33 #include <iomanip>
35 #include "GnashSystemNetHeaders.h"
36 #include "log.h"
37 #include "GnashException.h"
38 #include "movie_root.h"
39 #include "StreamProvider.h"
40 #include "URL.h"
41 #include "VM.h"
42 #include "SimpleBuffer.h"
43 #include "namedStrings.h"
44 #include "GnashAlgorithm.h"
45 #include "fn_call.h"
46 #include "Global_as.h"
47 #include "AMFConverter.h"
48 #include "AMF.h"
49 #include "as_function.h"
50 #include "RunResources.h"
51 #include "IOChannel.h"
52 #include "RTMP.h"
54 //#define GNASH_DEBUG_REMOTING
56 namespace gnash {
58 // Forward declarations.
59 namespace {
60 void attachProperties(as_object& o);
61 void attachNetConnectionInterface(as_object& o);
62 as_value netconnection_isConnected(const fn_call& fn);
63 as_value netconnection_uri(const fn_call& fn);
64 as_value netconnection_connect(const fn_call& fn);
65 as_value netconnection_close(const fn_call& fn);
66 as_value netconnection_call(const fn_call& fn);
67 as_value netconnection_addHeader(const fn_call& fn);
68 as_value netconnection_new(const fn_call& fn);
69 as_value local_onResult(const fn_call& fn);
70 std::pair<std::string, std::string>
71 getStatusCodeInfo(NetConnection_as::StatusCode code);
73 /// Parse and send any invoke messages from an HTTP connection.
74 void handleAMFInvoke(amf::Reader& rd, const boost::uint8_t*& b,
75 const boost::uint8_t* end, as_object& owner);
77 void replyBWCheck(rtmp::RTMP& r, double txn);
81 /// Abstract connection handler class
83 /// This class abstract operations on network connections,
84 /// specifically RPC and streams fetching.
85 class Connection : boost::noncopyable
87 public:
89 typedef std::map<size_t, as_object*> CallbacksMap;
91 /// @param methodName A string identifying the remote procedure to call
92 /// @param responseHandler Object to invoke response methods on.
93 /// @param args A vector of arguments
94 virtual void call(as_object* asCallback, const std::string& methodName,
95 const std::vector<as_value>& args) = 0;
97 /// Get an stream by name
99 /// @param name Stream identifier
100 virtual std::auto_ptr<IOChannel> getStream(const std::string& /*name*/) {
101 log_unimpl("%s doesn't support fetching streams", typeName(*this));
102 return std::auto_ptr<IOChannel>(0);
105 /// Process pending traffic, out or in bound
107 /// Handles all networking for NetConnection::call() and dispatches
108 /// callbacks when needed.
110 /// @return false if no further advance is needed. The only
111 /// case for this is an error.
112 virtual bool advance() = 0;
114 /// Connections may store references to as_objects
115 void setReachable() const {
116 foreachSecond(_callbacks.begin(), _callbacks.end(),
117 std::mem_fun(&as_object::setReachable));
120 /// Return true if the connection has pending calls
122 /// This will be used on NetConnection.close(): if current
123 /// connection has pending calls to process it will be
124 /// queued and only really dropped when advance returns false
125 virtual bool hasPendingCalls() const = 0;
127 size_t callNo() {
128 return ++_numCalls;
131 virtual ~Connection() {}
133 as_object* popCallback(size_t id) {
134 CallbacksMap::iterator it = _callbacks.find(id);
135 if (it != _callbacks.end()) {
136 as_object* callback = it->second;
137 _callbacks.erase(it);
138 return callback;
140 return 0;
143 protected:
145 /// Construct a connection handler bound to the given NetConnection object
147 /// The binding is used to notify statuses and errors
149 /// The NetConnection_as owns all Connections, so there is no
150 /// need to mark it reachable.
151 Connection(NetConnection_as& nc)
153 _nc(nc),
154 _numCalls(0)
158 void pushCallback(size_t id, as_object* callback) {
159 _callbacks[id] = callback;
162 // Object handling connection status messages
163 NetConnection_as& _nc;
165 private:
167 CallbacksMap _callbacks;
169 size_t _numCalls;
172 // Connection types.
173 namespace {
175 class HTTPRequest
177 public:
178 HTTPRequest(Connection& h)
180 _handler(h),
181 _calls(0)
183 // leave space for header
184 _data.append("\000\000\000\000\000\000", 6);
185 _headers["Content-Type"] = "application/x-amf";
188 /// Add AMF data to this request.
189 void addData(const SimpleBuffer& amf) {
190 _data.append(amf.data(), amf.size());
191 ++_calls;
194 void send(const URL& url, NetConnection_as& nc);
196 bool process(NetConnection_as& nc);
198 private:
200 static const size_t NCCALLREPLYCHUNK = 1024 * 200;
202 /// Handle replies to server functions we invoked with a callback.
204 /// This needs access to the stored callbacks.
205 void handleAMFReplies(amf::Reader& rd, const boost::uint8_t*& b,
206 const boost::uint8_t* end);
208 Connection& _handler;
210 /// The data to be sent by POST with this request.
211 SimpleBuffer _data;
213 /// A buffer for the reply.
214 SimpleBuffer _reply;
216 /// The number of separate remoting calls to be encoded in this request.
217 size_t _calls;
219 /// A single HTTP request.
220 boost::scoped_ptr<IOChannel> _connection;
222 /// Headers to be sent with this request.
223 NetworkAdapter::RequestHeaders _headers;
227 /// Queue of remoting calls
229 /// This is a single conception HTTP remoting connection, which in reality
230 /// comprises a queue of separate HTTP requests.
231 class HTTPConnection : public Connection
233 public:
234 /// Create a handler for HTTP remoting
236 /// @param nc The NetConnection AS object to send status/error events to
237 /// @param url URL to post calls to
238 HTTPConnection(NetConnection_as& nc, const URL& url)
240 Connection(nc),
241 _url(url)
245 virtual bool hasPendingCalls() const {
246 return _currentRequest.get() || !_requestQueue.empty();
249 /// Queue current request, process all live requests.
250 virtual bool advance();
252 /// Check if there is a current request. If not, make one.
253 virtual void call(as_object* asCallback, const std::string& methodName,
254 const std::vector<as_value>& args);
256 private:
258 const URL _url;
260 /// The queue of sent requests.
261 std::vector<boost::shared_ptr<HTTPRequest> > _requestQueue;
263 /// The current request.
264 boost::shared_ptr<HTTPRequest> _currentRequest;
268 class RTMPConnection : public Connection
270 public:
272 RTMPConnection(NetConnection_as& nc, const URL& url)
274 Connection(nc),
275 _connectionComplete(false),
276 _url(url)
278 // Throw exception if this fails.
279 const bool ret = _rtmp.connect(url);
280 if (!ret) throw GnashException("Connection failed");
283 virtual void call(as_object* asCallback, const std::string& methodName,
284 const std::vector<as_value>& args)
286 SimpleBuffer buf;
287 amf::Writer aw(buf);
288 aw.writeString(methodName);
289 const size_t id = asCallback ? callNo() : 0;
290 aw.writeNumber(id);
292 for (size_t i = 0; i < args.size(); ++i) {
293 args[i].writeAMF0(aw);
295 _rtmp.call(buf);
296 if (asCallback) {
297 pushCallback(id, asCallback);
301 bool hasPendingCalls() const {
302 return false;
305 virtual bool advance() {
307 _rtmp.update();
309 if (_rtmp.error() && !_connectionComplete) {
310 _nc.notifyStatus(NetConnection_as::CONNECT_FAILED);
311 return false;
313 if (_connectionComplete && _rtmp.error()) {
314 _nc.notifyStatus(NetConnection_as::CONNECT_CLOSED);
315 return false;
318 // Nothing to do yet, but we don't want to be dropped.
319 if (!_connectionComplete) {
321 if (!_rtmp.connected()) return true;
323 _connectionComplete = true;
324 log_debug("Initial connection complete");
326 const RunResources& r = getRunResources(_nc.owner());
327 Global_as& gl = getGlobal(_nc.owner());
329 // Connection object
330 as_object* o = createObject(gl);
332 const int flags = 0;
333 o->init_member("app", _url.path().substr(1), flags);
335 // TODO: check where it gets these data from.
336 o->init_member("flashVer", getVM(_nc.owner()).getPlayerVersion(),
337 flags);
338 o->init_member("swfUrl", r.streamProvider().baseURL().str(),
339 flags);
340 o->init_member("tcUrl", _url.str(), flags);
342 // TODO: implement this properly
343 o->init_member("fpad", false, flags);
344 o->init_member("capabilities", 15.0, flags);
345 o->init_member("audioCodecs", 3191.0, flags);
346 o->init_member("videoCodecs", 252.0, flags);
347 o->init_member("videoFunction", 1.0, flags);
348 o->init_member("pageUrl", as_value(), flags);
350 // Set up the callback object.
351 as_object* cb = createObject(getGlobal(_nc.owner()));
352 cb->init_member(NSV::PROP_ON_RESULT,
353 gl.createFunction(local_onResult), 0);
355 cb->init_member("_conn", &_nc.owner(), 0);
357 std::vector<as_value> args;
358 args.push_back(o);
360 call(cb, "connect", args);
362 // Send bandwidth check; the pp appears to do this
363 // automatically.
364 sendServerBW(_rtmp);
368 boost::shared_ptr<SimpleBuffer> b = _rtmp.getMessage();
370 if (b && !_nc.isConnected()) {
371 _nc.setConnected();
374 /// Retrieve messages.
375 while (b.get()) {
376 handleInvoke(b->data() + rtmp::RTMPHeader::headerSize,
377 b->data() + b->size());
378 b = _rtmp.getMessage();
381 return true;
384 private:
386 void handleInvoke(const boost::uint8_t* payload, const boost::uint8_t* end);
388 rtmp::RTMP _rtmp;
389 bool _connectionComplete;
390 const URL _url;
394 } // anonymous namespace
396 NetConnection_as::NetConnection_as(as_object* owner)
398 ActiveRelay(owner),
399 _isConnected(false)
403 // here to have HTTPConnection definition available
404 NetConnection_as::~NetConnection_as()
408 // extern (used by Global.cpp)
409 void
410 netconnection_class_init(as_object& where, const ObjectURI& uri)
412 registerBuiltinClass(where, netconnection_new,
413 attachNetConnectionInterface, 0, uri);
416 void
417 NetConnection_as::markReachableResources() const
419 owner().setReachable();
420 std::for_each(_oldConnections.begin(), _oldConnections.end(),
421 boost::mem_fn(&Connection::setReachable));
422 if (_currentConnection.get()) _currentConnection->setReachable();
425 /// FIXME: this should not use _uri, but rather take a URL argument.
426 /// Validation should probably be done on connect() only and return a
427 /// bool indicating validity. That can be used to return a failure
428 /// for invalid or blocked URLs.
429 std::string
430 NetConnection_as::validateURL() const
432 const RunResources& r = getRunResources(owner());
433 URL uri(_uri, r.streamProvider().baseURL());
435 std::string uriStr(uri.str());
436 assert(uriStr.find("://") != std::string::npos);
438 // Check if we're allowed to open url
439 if (!r.streamProvider().allow(uri)) {
440 log_security(_("Gnash is not allowed to open this url: %s"), uriStr);
441 return "";
444 log_debug(_("Connection to movie: %s"), uriStr);
446 return uriStr;
449 void
450 NetConnection_as::notifyStatus(StatusCode code)
452 std::pair<std::string, std::string> info = getStatusCodeInfo(code);
454 /// This is a new normal object each time (see NetConnection.as)
455 as_object* o = createObject(getGlobal(owner()));
457 const int flags = 0;
459 o->init_member("code", info.first, flags);
460 o->init_member("level", info.second, flags);
462 callMethod(&owner(), NSV::PROP_ON_STATUS, o);
466 /// Called on NetConnection.connect(null).
468 /// The status notification happens immediately, isConnected becomes true.
469 void
470 NetConnection_as::connect()
472 // Close any current connections.
473 close();
474 _isConnected = true;
475 notifyStatus(CONNECT_SUCCESS);
479 bool
480 NetConnection_as::connect(const std::string& uri)
482 // Close any current connections.
483 close();
484 assert(!_isConnected);
486 // TODO: check for other kind of invalidities here...
487 if (uri.empty()) {
488 notifyStatus(CONNECT_FAILED);
489 return false;
492 const RunResources& r = getRunResources(owner());
493 URL url(_uri, r.streamProvider().baseURL());
495 if (!r.streamProvider().allow(url)) {
496 log_security(_("Gnash is not allowed to connect " "to %s"), url);
497 notifyStatus(CONNECT_FAILED);
498 return false;
501 // Attempt connection.
502 if (url.protocol() == "https" || url.protocol() == "http") {
503 _currentConnection.reset(new HTTPConnection(*this, url));
505 else if (url.protocol() == "rtmp") {
506 try {
507 _currentConnection.reset(new RTMPConnection(*this, url));
509 catch (const GnashException&) {
510 // This happens if the connect cannot even be attempted.
511 notifyStatus(CONNECT_FAILED);
512 return false;
514 startAdvanceTimer();
516 else if (url.protocol() == "rtmpt" || url.protocol() == "rtmpts") {
517 log_unimpl("NetConnection.connect(%s): unsupported connection "
518 "protocol", url);
519 notifyStatus(CONNECT_FAILED);
520 return false;
522 else {
523 log_error("NetConnection.connect(%s): unknown connection "
524 "protocol", url);
525 notifyStatus(CONNECT_FAILED);
526 return false;
528 return true;
532 /// FIXME: This should close an active connection as well as setting the
533 /// appropriate properties.
534 void
535 NetConnection_as::close()
537 // Send close event if a connection is in progress or connected is true.
538 const bool needSendClosedStatus = _currentConnection.get() || _isConnected;
540 /// Queue the current call queue if it has pending calls
541 if (_currentConnection.get() && _currentConnection->hasPendingCalls()) {
542 boost::shared_ptr<Connection> c(_currentConnection.release());
543 _oldConnections.push_back(c);
546 /// TODO: what should actually happen here? Should an attached
547 /// NetStream object be interrupted?
548 _isConnected = false;
550 if (needSendClosedStatus) {
551 notifyStatus(CONNECT_CLOSED);
556 void
557 NetConnection_as::setURI(const std::string& uri)
559 owner().init_readonly_property("uri", &netconnection_uri);
560 _uri = uri;
563 void
564 NetConnection_as::call(as_object* asCallback, const std::string& methodName,
565 const std::vector<as_value>& args)
567 if (!_currentConnection.get()) {
568 IF_VERBOSE_ASCODING_ERRORS(
569 log_aserror("NetConnection.call: can't call while not connected");
571 return;
574 _currentConnection->call(asCallback, methodName, args);
576 startAdvanceTimer();
579 std::auto_ptr<IOChannel>
580 NetConnection_as::getStream(const std::string& name)
582 const RunResources& ri = getRunResources(owner());
584 const StreamProvider& streamProvider = ri.streamProvider();
586 // Construct URL with base URL (assuming not connected to RTMP server..)
587 // TODO: For RTMP return the named stream from an existing RTMP connection.
588 // If name is a full or relative URL passed from NetStream.play(), it
589 // must be constructed against the base URL, not the NetConnection uri,
590 // which should always be null in this case.
591 const RcInitFile& rcfile = RcInitFile::getDefaultInstance();
593 URL url(name, streamProvider.baseURL());
595 return streamProvider.getStream(url, rcfile.saveStreamingMedia());
599 void
600 NetConnection_as::startAdvanceTimer()
602 getRoot(owner()).addAdvanceCallback(this);
605 void
606 NetConnection_as::stopAdvanceTimer()
608 getRoot(owner()).removeAdvanceCallback(this);
611 void
612 NetConnection_as::update()
615 // Handle unfinished actions in any closed connections. Currently we
616 // only expect HTTP connections here, as RTMP is closed immediately.
617 // This may change!
618 for (Connections::iterator i = _oldConnections.begin();
619 i != _oldConnections.end(); ) {
621 Connection& ch = **i;
622 // Remove on error or if there are no more actions.
623 if (!ch.advance() || !ch.hasPendingCalls()) {
624 i = _oldConnections.erase(i);
626 else ++i;
629 // Advance current connection, but reset if there's an error.
631 // TODO: notify relevant status.
632 if (_currentConnection.get()) {
633 if (!_currentConnection->advance()) {
634 _currentConnection.reset();
638 /// If there are no connections we can stop the timer.
639 if (_oldConnections.empty() && !_currentConnection.get()) {
640 stopAdvanceTimer();
644 // Anonymous namespace for NetConnection interface implementation.
645 namespace {
647 /// NetConnection.call()
649 /// Documented to return void, and current tests suggest this might be
650 /// correct, though they don't test with any calls that might succeed.
651 as_value
652 netconnection_call(const fn_call& fn)
654 NetConnection_as* ptr = ensure<ThisIsNative<NetConnection_as> >(fn);
656 if (fn.nargs < 1) {
657 IF_VERBOSE_ASCODING_ERRORS(
658 log_aserror(_("NetConnection.call(): needs at least one argument"));
660 return as_value();
663 const as_value& methodName_as = fn.arg(0);
664 std::string methodName = methodName_as.to_string();
666 #ifdef GNASH_DEBUG_REMOTING
667 std::stringstream ss; fn.dump_args(ss);
668 log_debug("NetConnection.call(%s)", ss.str());
669 #endif
671 // TODO: arg(1) is the response object. let it know when data comes back
672 as_object* asCallback(0);
673 if (fn.nargs > 1) {
675 if (fn.arg(1).is_object()) {
676 asCallback = (toObject(fn.arg(1), getVM(fn)));
678 else {
679 IF_VERBOSE_ASCODING_ERRORS(
680 std::stringstream ss; fn.dump_args(ss);
681 log_aserror("NetConnection.call(%s): second argument must be "
682 "an object", ss.str());
687 std::vector<as_value> args;
688 if (fn.nargs > 2) {
689 args = std::vector<as_value>(fn.getArgs().begin() + 2,
690 fn.getArgs().end());
692 ptr->call(asCallback, methodName, args);
694 return as_value();
697 as_value
698 netconnection_close(const fn_call& fn)
700 NetConnection_as* ptr = ensure<ThisIsNative<NetConnection_as> >(fn);
701 ptr->close();
702 return as_value();
705 // Read-only
706 as_value
707 netconnection_isConnected(const fn_call& fn)
709 NetConnection_as* ptr = ensure<ThisIsNative<NetConnection_as> >(fn);
710 return as_value(ptr->isConnected());
713 as_value
714 netconnection_uri(const fn_call& fn)
716 NetConnection_as* ptr = ensure<ThisIsNative<NetConnection_as> >(fn);
717 return as_value(ptr->getURI());
720 void
721 attachNetConnectionInterface(as_object& o)
723 Global_as& gl = getGlobal(o);
725 o.init_member("connect", gl.createFunction(netconnection_connect));
726 o.init_member("addHeader", gl.createFunction(netconnection_addHeader));
727 o.init_member("call", gl.createFunction(netconnection_call));
728 o.init_member("close", gl.createFunction(netconnection_close));
731 void
732 attachProperties(as_object& o)
734 o.init_readonly_property("isConnected", &netconnection_isConnected);
737 /// \brief callback to instantiate a new NetConnection object.
738 /// \param fn the parameters from the Flash movie
739 /// \return nothing from the function call.
740 /// \note The return value is returned through the fn.result member.
741 as_value
742 netconnection_new(const fn_call& fn)
744 as_object* obj = ensure<ValidThis>(fn);
745 obj->setRelay(new NetConnection_as(obj));
746 attachProperties(*obj);
747 return as_value();
751 /// For remoting, NetConnection.connect() takes a URL. For all other streams,
752 /// it takes null.
754 /// For non-remoting streams:
756 /// Returns undefined if there are no arguments, true if the first
757 /// argument is null, otherwise whether the connection is allowed. The
758 /// actual result of the connection is sent with an onStatus call later.
760 /// Undefined is also a valid argument for SWF7 and above.
762 /// The isConnected property is set to the result of connect().
763 as_value
764 netconnection_connect(const fn_call& fn)
767 NetConnection_as* ptr = ensure<ThisIsNative<NetConnection_as> >(fn);
769 if (fn.nargs < 1) {
770 IF_VERBOSE_ASCODING_ERRORS(
771 log_aserror(_("NetConnection.connect(): needs at least "
772 "one argument"));
774 return as_value();
777 const as_value& uri = fn.arg(0);
779 const VM& vm = getVM(fn);
780 const std::string& uriStr = uri.to_string(vm.getSWFVersion());
782 // This is always set without validification.
783 ptr->setURI(uriStr);
785 // Check first arg for validity
786 if (uri.is_null() || (getSWFVersion(fn) > 6 && uri.is_undefined())) {
787 ptr->connect();
788 return as_value(true);
791 if (fn.nargs > 1) {
792 std::stringstream ss; fn.dump_args(ss);
793 log_unimpl("NetConnection.connect(%s): args after the first are "
794 "not supported", ss.str());
797 return as_value(ptr->connect(uriStr));
802 as_value
803 netconnection_addHeader(const fn_call& fn)
805 NetConnection_as* ptr = ensure<ThisIsNative<NetConnection_as> >(fn);
806 UNUSED(ptr);
808 log_unimpl("NetConnection.addHeader()");
809 return as_value();
812 /// This creates a local callback function to handle the return from connect()
814 /// NetStream does this using a builtin function, but this is more complicated
815 /// because it needs the native NetConnection type.
817 /// This stores the NetConnection object so that it can be updated when
818 /// connect returns.
820 /// We don't know if this is the best way to do it, but:
822 /// 1. the connect call *does* return a callback ID.
823 as_value
824 local_onResult(const fn_call& fn)
826 as_object* obj = fn.this_ptr;
828 if (obj) {
829 const ObjectURI conn = getURI(getVM(fn), "_conn");
830 as_value f = getMember(*obj, conn);
831 as_object* nc = toObject(f, getVM(fn));
832 if (nc) {
834 const as_value arg = fn.nargs ? fn.arg(0) : as_value();
835 callMethod(nc, NSV::PROP_ON_STATUS, arg);
837 return as_value();
841 std::pair<std::string, std::string>
842 getStatusCodeInfo(NetConnection_as::StatusCode code)
844 /// The Call statuses do exist, but this implementation is a guess.
845 switch (code) {
846 case NetConnection_as::CONNECT_SUCCESS:
847 return std::make_pair("NetConnection.Connect.Success", "status");
848 case NetConnection_as::CONNECT_FAILED:
849 return std::make_pair("NetConnection.Connect.Failed", "error");
850 case NetConnection_as::CONNECT_APPSHUTDOWN:
851 return std::make_pair("NetConnection.Connect.AppShutdown", "error");
852 case NetConnection_as::CONNECT_REJECTED:
853 return std::make_pair("NetConnection.Connect.Rejected", "error");
854 case NetConnection_as::CONNECT_CLOSED:
855 return std::make_pair("NetConnection.Connect.Closed", "status");
856 case NetConnection_as::CALL_FAILED:
857 return std::make_pair("NetConnection.Call.Failed", "error");
858 case NetConnection_as::CALL_BADVERSION:
859 return std::make_pair("NetConnection.Call.BadVersion", "status");
860 default:
861 std::abort();
866 void
867 handleAMFInvoke(amf::Reader& rd, const boost::uint8_t*& b,
868 const boost::uint8_t* end, as_object& owner)
871 const boost::uint16_t invokecount = amf::readNetworkShort(b);
872 b += 2;
874 if (!invokecount) return;
876 for (size_t i = invokecount; i > 0; --i) {
877 if (b + 2 > end) {
878 throw amf::AMFException("Invoke buffer too short");
880 const boost::uint16_t namelength = amf::readNetworkShort(b);
881 b += 2;
882 if (b + namelength > end) {
883 throw amf::AMFException("Invoke buffer too short");
885 std::string headerName((char*)b, namelength);
887 #ifdef GNASH_DEBUG_REMOTING
888 log_debug("Invoke name %s", headerName);
889 #endif
890 b += namelength;
891 if (b + 5 > end) {
892 throw amf::AMFException("Invoke buffer too short");
894 b += 5; // skip past bool and length long
896 // It seems there must be exactly one argument.
897 as_value arg;
898 if (!rd(arg)) {
899 throw amf::AMFException("Invoke argument not present");
902 VM& vm = getVM(owner);
903 ObjectURI key = getURI(vm, headerName);
904 #ifdef GNASH_DEBUG_REMOTING
905 log_debug("Invoking %s(%s)", headerName, arg);
906 #endif
907 callMethod(&owner, key, arg);
912 /// Process any replies to server functions we invoked.
914 /// Note that fatal errors will throw an amf::AMFException.
915 void
916 HTTPRequest::handleAMFReplies(amf::Reader& rd, const boost::uint8_t*& b,
917 const boost::uint8_t* end)
919 const boost::uint16_t numreplies = amf::readNetworkShort(b);
920 b += 2; // number of replies
922 // TODO: test if this value is relevant at all.
923 if (!numreplies) return;
925 // There should be only three loop control mechanisms in this loop:
926 // 1. continue: there was an error, but parsing is still okay.
927 // 2. return: we've finished, but there was no problem.
928 // 3. amf::AMFException: parsing failed and we can do nothing more.
930 // We haven't tested this very rigorously.
931 while (b < end) {
933 if (b + 2 > end) return;
935 const boost::uint16_t replylength = amf::readNetworkShort(b);
936 b += 2;
938 if (replylength < 4 || b + replylength > end) {
939 throw amf::AMFException("Reply message too short");
942 // Reply message is: '/id/methodName'
943 int ns = 1; // next slash position
944 while (ns < replylength - 1 && *(b + ns) != '/') ++ns;
945 if (ns >= replylength - 1) {
946 throw amf::AMFException("Invalid reply message name");
949 std::string id(reinterpret_cast<const char*>(b + 1), ns - 1);
950 size_t callbackID = 0;
951 try {
952 callbackID = boost::lexical_cast<size_t>(id);
954 catch (const boost::bad_lexical_cast&) {
955 // Do we need to abort parsing here?
956 throw amf::AMFException("Invalid callback ID");
959 const std::string methodName(reinterpret_cast<const char*>(b + ns + 1),
960 replylength - ns - 1);
962 b += replylength;
964 // parse past unused string in header
965 if (b + 2 > end) return;
966 const boost::uint16_t unusedlength = amf::readNetworkShort(b);
968 b += 2;
969 if (b + unusedlength > end) return;
970 b += unusedlength;
972 // this field is supposed to hold the total number of bytes in the
973 // rest of this particular reply value, but openstreetmap.org
974 // (which works great in the adobe player) sends 0xffffffff.
975 // So we just ignore it.
976 if (b + 4 > end) break;
977 b += 4;
979 // this updates b to point to the next unparsed byte
980 as_value replyval;
981 if (!rd(replyval)) {
982 throw amf::AMFException("Could not parse argument value");
985 // if actionscript specified a callback object,
986 // call it
987 as_object* callback = _handler.popCallback(callbackID);
989 if (!callback) {
990 log_error("Unknown HTTP Remoting response identifier '%s'", id);
991 // There's no parsing error, so continue.
992 continue;
995 ObjectURI methodKey;
996 if (methodName == "onResult") {
997 methodKey = NSV::PROP_ON_RESULT;
999 else if (methodName == "onStatus") {
1000 methodKey = NSV::PROP_ON_STATUS;
1002 else {
1003 // NOTE: the pp is known to actually
1004 // invoke the custom method, but with 7
1005 // undefined arguments (?)
1006 log_error("Unsupported HTTP Remoting response callback: '%s' "
1007 "(size %d)", methodName, methodName.size());
1008 continue;
1011 #ifdef GNASH_DEBUG_REMOTING
1012 log_debug("callback called");
1013 #endif
1015 callMethod(callback, methodKey, replyval);
1020 bool
1021 HTTPConnection::advance()
1023 // If there is data waiting to be sent, send it and push it
1024 // to the queue.
1025 if (_currentRequest.get()) {
1026 _currentRequest->send(_url, _nc);
1027 _requestQueue.push_back(_currentRequest);
1029 // Clear the current request for the next go.
1030 _currentRequest.reset();
1033 // Process all replies and clear finished requests.
1034 for (std::vector<boost::shared_ptr<HTTPRequest> >::iterator i =
1035 _requestQueue.begin(); i != _requestQueue.end();) {
1036 if (!(*i)->process(_nc)) i = _requestQueue.erase(i);
1037 else ++i;
1040 return true;
1043 void
1044 HTTPRequest::send(const URL& url, NetConnection_as& nc)
1046 // We should never have a request without any calls.
1047 assert(_calls);
1048 log_debug("creating connection");
1050 // Fill in header
1051 (reinterpret_cast<boost::uint16_t*>(_data.data() + 4))[0] = htons(_calls);
1052 std::string postdata(reinterpret_cast<char*>(_data.data()), _data.size());
1054 #ifdef GNASH_DEBUG_REMOTING
1055 log_debug("NetConnection.call(): encoded args from %1% calls: %2%",
1056 _calls, hexify(_data.data(), _data.size(), false));
1057 #endif
1059 const StreamProvider& sp = getRunResources(nc.owner()).streamProvider();
1060 _connection.reset(sp.getStream(url, postdata, _headers).release());
1064 /// An AMF remoting reply comprises two main sections: first the invoke
1065 /// commands to be called on the NetConnection object, and second the
1066 /// replies to any client invoke messages that requested a callback.
1067 bool
1068 HTTPRequest::process(NetConnection_as& nc)
1070 assert(_connection);
1072 // Fill last chunk before reading in the next
1073 size_t toRead = _reply.capacity() - _reply.size();
1074 if (!toRead) toRead = NCCALLREPLYCHUNK;
1076 #ifdef GNASH_DEBUG_REMOTING
1077 log_debug("Attempt to read %d bytes", toRead);
1078 #endif
1080 // See if we need to allocate more bytes for the next
1081 // read chunk
1082 if (_reply.capacity() < _reply.size() + toRead) {
1083 const size_t newCapacity = _reply.size() + toRead;
1085 #ifdef GNASH_DEBUG_REMOTING
1086 log_debug("NetConnection.call: reply buffer capacity (%d) "
1087 "is too small to accept next %d bytes of chunk "
1088 "(current size is %d). Reserving %d bytes.",
1089 _reply.capacity(), toRead, _reply.size(), newCapacity);
1090 #endif
1092 _reply.reserve(newCapacity);
1095 const int read = _connection->readNonBlocking(_reply.data() + _reply.size(),
1096 toRead);
1098 if (read > 0) {
1099 #ifdef GNASH_DEBUG_REMOTING
1100 log_debug("read '%1%' bytes: %2%", read,
1101 hexify(_reply.data() + _reply.size(), read, false));
1102 #endif
1103 _reply.resize(_reply.size() + read);
1106 // There is no way to tell if we have a whole amf reply without
1107 // parsing everything
1109 // The reply format has a header field which specifies the
1110 // number of bytes in the reply, but potlatch sends 0xffffffff
1111 // and works fine in the proprietary player
1113 // For now we just wait until we have the full reply.
1115 // FIXME make this parse on other conditions, including: 1) when
1116 // the buffer is full, 2) when we have a "length in bytes" value
1117 // thas is satisfied
1118 if (_connection->bad()) {
1119 log_debug("connection is in error condition, calling "
1120 "NetConnection.onStatus");
1122 // If the connection fails, it is manually verified
1123 // that the pp calls onStatus with 1 undefined argument.
1124 callMethod(&nc.owner(), NSV::PROP_ON_STATUS, as_value());
1125 return false;
1128 // Not all data was received, so carry on.
1129 if (!_connection->eof()) return true;
1131 // If it's less than 8 we didn't expect a response, so just ignore
1132 // it.
1133 if (_reply.size() > 8) {
1135 #ifdef GNASH_DEBUG_REMOTING
1136 log_debug("hit eof");
1137 #endif
1138 const boost::uint8_t *b = _reply.data();
1139 const boost::uint8_t *end = _reply.data() + _reply.size();
1141 amf::Reader rd(b, end, getGlobal(nc.owner()));
1143 // skip version indicator and client id
1144 b += 2;
1146 try {
1147 handleAMFInvoke(rd, b, end, nc.owner());
1148 handleAMFReplies(rd, b, end);
1150 catch (const amf::AMFException& e) {
1152 // Any fatal error should be signalled by throwing an
1153 // exception. In this case onStatus is called with an
1154 // undefined argument.
1155 log_error("Error parsing server AMF: %s", e.what());
1156 callMethod(&nc.owner(), NSV::PROP_ON_STATUS, as_value());
1160 // We've finished with this connection.
1161 return false;
1164 void
1165 HTTPConnection::call(as_object* asCallback, const std::string& methodName,
1166 const std::vector<as_value>& args)
1168 if (!_currentRequest.get()) {
1169 _currentRequest.reset(new HTTPRequest(*this));
1172 // Create AMF buffer for this call.
1173 SimpleBuffer buf(32);
1175 amf::writePlainString(buf, methodName, amf::STRING_AMF0);
1177 const size_t callID = callNo();
1179 // client id (result number) as counted string
1180 // the convention seems to be / followed by a unique (ascending) number
1181 std::ostringstream os;
1182 os << "/";
1183 // Call number is not used if the callback is undefined
1184 if (asCallback) os << callID;
1186 // Encode callback number.
1187 amf::writePlainString(buf, os.str(), amf::STRING_AMF0);
1189 size_t total_size_offset = buf.size();
1190 buf.append("\000\000\000\000", 4); // total size to be filled in later
1192 // encode array of arguments to remote method
1193 buf.appendByte(amf::STRICT_ARRAY_AMF0);
1194 buf.appendNetworkLong(args.size());
1196 // STRICT_ARRAY encoding is allowed for remoting
1197 amf::Writer w(buf, true);
1199 for (size_t i = 0; i < args.size(); ++i) {
1200 const as_value& arg = args[i];
1201 if (!arg.writeAMF0(w)) {
1202 log_error("Could not serialize NetConnection.call argument %d", i);
1206 // Set the "total size" parameter.
1207 *(reinterpret_cast<uint32_t*>(buf.data() + total_size_offset)) =
1208 htonl(buf.size() - 4 - total_size_offset);
1210 // Add data to the current HTTPRequest.
1211 _currentRequest->addData(buf);
1213 // Remember the callback object.
1214 if (asCallback) {
1215 pushCallback(callID, asCallback);
1219 void
1220 RTMPConnection::handleInvoke(const boost::uint8_t* payload,
1221 const boost::uint8_t* end)
1223 // TODO: clean up the logic in this function to reduce duplication.
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") {
1297 as_value arg;
1299 amf::Reader rd(payload, end, getGlobal(_nc.owner()));
1300 // TODO: use all args and check the order! We currently only use
1301 // the last one!
1302 while (rd(arg)) {
1303 log_debug("Value: %s", arg);
1306 log_error( "rtmp server sent error");
1308 callMethod(&_nc.owner(), NSV::PROP_ON_STATUS, arg);
1309 return;
1312 // Parse any arguments.
1313 as_value arg;
1315 amf::Reader rd(payload, end, getGlobal(_nc.owner()));
1316 // TODO: use all args and check the order! We currently only use
1317 // the last one!
1318 while (rd(arg)) {
1319 log_debug("Value: %s", arg);
1322 // Call method on the NetConnection object.
1323 callMethod(&_nc.owner(), methodname, arg);
1327 void
1328 replyBWCheck(rtmp::RTMP& r, double txn)
1330 SimpleBuffer buf;
1331 amf::write(buf, "_result");
1332 amf::write(buf, txn);
1333 buf.appendByte(amf::NULL_AMF0);
1334 amf::write(buf, 0.0);
1335 r.call(buf);
1338 } // anonymous namespace
1339 } // end of gnash namespace
1341 // local Variables:
1342 // mode: C++
1343 // indent-tabs-mode: t
1344 // End: