1
// NetConnection.cpp: Open local connections for FLV files or URLs.
3
// Copyright (C) 2005, 2006, 2007, 2008 Free Software Foundation, Inc.
5
// This program is free software; you can redistribute it and/or modify
6
// it under the terms of the GNU General Public License as published by
7
// the Free Software Foundation; either version 3 of the License, or
8
// (at your option) any later version.
10
// This program is distributed in the hope that it will be useful,
11
// but WITHOUT ANY WARRANTY; without even the implied warranty of
12
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
// GNU General Public License for more details.
15
// You should have received a copy of the GNU General Public License
16
// along with this program; if not, write to the Free Software
17
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
22
#include "gnashconfig.h"
27
#include <boost/scoped_ptr.hpp>
29
// FIXME: Get rid of this crap.
30
#if defined(HAVE_WINSOCK_H) && !defined(__OS2__)
33
#include <arpa/inet.h> // for htons
36
#include "NetConnection.h"
38
#include "GnashException.h"
39
#include "builtin_function.h"
40
#include "movie_root.h"
41
#include "Object.h" // for getObjectInterface
43
#include "StreamProvider.h"
44
#include "URLAccessManager.h"
47
// for NetConnection.call()
51
#include "SimpleBuffer.h"
53
#include "namedStrings.h"
56
// #define GNASH_DEBUG_REMOTING
60
static as_value netconnection_new(const fn_call& fn);
62
/// \class NetConnection
63
/// \brief Opens a local connection through which you can play
64
/// back video (FLV) files from an HTTP address or from the local file
65
/// system, using curl.
66
NetConnection::NetConnection()
68
as_object(getNetConnectionInterface()),
76
std::string NetConnection::validateURL(const std::string& url)
78
std::string completeUrl;
79
if (_prefixUrl.size() > 0) {
81
completeUrl += _prefixUrl + "/" + url;
83
completeUrl += _prefixUrl;
89
URL uri(completeUrl, get_base_url());
91
std::string uriStr(uri.str());
92
assert(uriStr.find("://") != std::string::npos);
94
// Check if we're allowed to open url
95
if (!URLAccessManager::allow(uri)) {
96
log_security(_("Gnash is not allowed to open this url: %s"), uriStr);
100
log_debug(_("Connection to movie: %s"), uriStr);
107
NetConnection::addToURL(const std::string& url)
109
// What is this ? It is NOT documented in the header !!
110
//if (url == "null" || url == "NULL") return;
112
// If there already is something in _prefixUrl, then we already have a url,
113
// so no need to renew it. This may not correct, needs some testing.
114
if (_prefixUrl.size() > 0) return;
120
/// \brief callback to instantiate a new NetConnection object.
121
/// \param fn the parameters from the Flash movie
122
/// \return nothing from the function call.
123
/// \note The return value is returned through the fn.result member.
125
netconnection_new(const fn_call& /* fn */)
127
GNASH_REPORT_FUNCTION;
129
NetConnection *netconnection_obj = new NetConnection;
131
return as_value(netconnection_obj);
135
NetConnection::connect_method(const fn_call& fn)
139
// NetConnection::connect() is *documented*, I repeat, *documented*, to require the
140
// "url" argument to be NULL in AS <= 2. This is *legal* and *required*. Anything
141
// other than NULL is undocumented behaviour, and I would like to know if there
142
// are any movies out there relying on it. --bjacques.
144
GNASH_REPORT_FUNCTION;
146
boost::intrusive_ptr<NetConnection> ptr = ensureType<NetConnection>(fn.this_ptr);
150
IF_VERBOSE_ASCODING_ERRORS(
151
log_aserror(_("NetConnection.connect(): needs at least one argument"));
153
return as_value(false);
156
const as_value& url_val = fn.arg(0);
158
// Check first arg for validity
159
if ( url_val.is_null())
161
// Null URL was passed. This is expected. Of course, it also makes this
162
// function (and, this class) rather useless. We return true, even though
163
// returning true has no meaning.
165
return as_value(true);
168
// The remainder of this function is undocumented.
170
if (url_val.is_undefined()) {
171
IF_VERBOSE_ASCODING_ERRORS(
172
log_aserror(_("NetConnection.connect(): first argument shouldn't be undefined"));
174
return as_value(false);
178
/// .. TODO: checkme ... addToURL ?? shoudnl't we attempt a connection ??
179
ptr->addToURL(url_val.to_string());
183
std::stringstream ss; fn.dump_args(ss);
184
log_unimpl("NetConnection.connect(%s): args after the first are not supported", ss.str());
188
// TODO: FIXME: should return true *or false* for RTMP connections
189
return as_value(true);
194
NetConnection::addHeader_method(const fn_call& fn)
196
boost::intrusive_ptr<NetConnection> ptr = ensureType<NetConnection>(fn.this_ptr);
199
log_unimpl("NetConnection.addHeader()");
203
static boost::uint16_t
204
readNetworkShort(const boost::uint8_t* buf) {
205
boost::uint16_t s = buf[0] << 8 | buf[1];
209
static boost::uint32_t
210
readNetworkLong(const boost::uint8_t* buf) {
211
boost::uint32_t s = buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3];
215
/// Queue of remoting calls
217
/// This class in made to handle data and do defered processing for
218
/// NetConnection::call()
222
/// pass a URL to the constructor
224
/// call enqueue with a SimpleBuffer containing an encoded AMF call. If action
225
/// script specified a callback function, use the optional parameters to specify
226
/// the identifier (which must be unique) and the callback object as an as_value
231
static const int NCCALLREPLYMAX=200000;
233
typedef std::map<std::string, boost::intrusive_ptr<as_object> > CallbacksMap;
234
CallbacksMap callbacks;
236
SimpleBuffer postdata;
238
boost::scoped_ptr<IOChannel> _connection;
246
AMFQueue(NetConnection& nc, URL url)
252
reply(NCCALLREPLYMAX),
258
// leave space for header
259
postdata.append("\000\000\000\000\000\000", 6);
266
void enqueue(const SimpleBuffer &amf, const std::string& identifier, boost::intrusive_ptr<as_object> callback) {
268
push_callback(identifier, callback);
269
//log_aserror("NetConnection::call(): called with a non-object as the callback");
271
void enqueue(const SimpleBuffer &amf) {
275
// tick is called automatically on intervals (hopefully only between
276
// actionscript frames)
278
// it handles all networking for NetConnection::call() and dispatches
279
// callbacks when needed
282
#ifdef GNASH_DEBUG_REMOTING
283
log_debug("tick running");
288
VM& vm = _nc.getVM();
290
#ifdef GNASH_DEBUG_REMOTING
291
log_debug("have connection");
293
int read = _connection->readNonBlocking(reply.data() + reply_end, NCCALLREPLYMAX - reply_end);
295
#ifdef GNASH_DEBUG_REMOTING
296
log_debug("read '%1%' bytes: %2%", read, hexify(reply.data() + reply_end, read, false));
301
// There is no way to tell if we have a whole amf reply without
302
// parsing everything
304
// The reply format has a header field which specifies the
305
// number of bytes in the reply, but potlatch sends 0xffffffff
306
// and works fine in the proprietary player
308
// For now we just wait until we have the full reply.
310
// FIXME make this parse on other conditions, including: 1) when
311
// the buffer is full, 2) when we have a "length in bytes" value
314
if(_connection->get_error())
316
log_debug("connection is in error condition, calling NetConnection.onStatus");
319
//log_debug("deleting connection");
320
_connection.reset(); // reset connection before calling the callback
322
// FIXME: should only call NetConnection's onStatus
323
// if the IOChannel is in error condition.
325
// When the response is empty, just nothing happens.
326
_nc.callMethod(NSV::PROP_ON_STATUS, as_value());
329
else if(_connection->eof() )
333
std::vector<as_object*> objRefs;
335
#ifdef GNASH_DEBUG_REMOTING
336
log_debug("hit eof");
340
boost::uint8_t *b = reply.data() + reply_start;
341
boost::uint8_t *end = reply.data() + reply_end;
344
b += 2; // skip version indicator and client id
346
// NOTE: this looks much like parsing of an OBJECT_AMF0
347
si = readNetworkShort(b); b += 2; // number of headers
348
uint8_t headers_ok = 1;
351
#ifdef GNASH_DEBUG_REMOTING
352
log_debug("NetConnection::call(): amf headers section parsing");
355
for(int i = si; i > 0; --i)
361
si = readNetworkShort(b); b += 2; // name length
366
std::string headerName((char*)b, si); // end-b);
367
#ifdef GNASH_DEBUG_REMOTING
368
log_debug("Header name %s", headerName);
375
b += 5; // skip past bool and length long
376
if( !tmp.readAMF0(b, end, -1, objRefs, vm) )
381
#ifdef GNASH_DEBUG_REMOTING
382
log_debug("Header value %s", tmp);
385
{ // method call for each header
386
// FIXME: it seems to me that the call should happen
387
VM& vm = _nc.getVM();
388
string_table& st = vm.getStringTable();
389
string_table::key key = st.find(headerName);
390
#ifdef GNASH_DEBUG_REMOTING
391
log_debug("Calling NetConnection.%s(%s)", headerName, tmp);
393
_nc.callMethod(key, tmp);
398
if(headers_ok == 1) {
400
si = readNetworkShort(b); b += 2; // number of replies
402
// TODO consider counting number of replies we
403
// actually parse and doing something if it
404
// doesn't match this value (does it matter?
406
// parse replies until we get a parse error or we reach the end of the buffer
408
if(b + 2 > end) break;
409
si = readNetworkShort(b); b += 2; // reply length
411
log_error("NetConnection::call(): reply message name too short");
414
if(b + si > end) break;
415
// TODO check that the last 9 bytes are "/onResult"
416
// this should either split on the 2nd / or require onResult or onStatus
417
std::string id(reinterpret_cast<char*>(b), si - 9);
420
// parse past unused string in header
421
if(b + 2 > end) break;
422
si = readNetworkShort(b); b += 2; // reply length
423
if(b + si > end) break;
426
// this field is supposed to hold the
427
// total number of bytes in the rest of
428
// this particular reply value, but
429
// openstreetmap.org (which works great
430
// in the adobe player) sends
431
// 0xffffffff. So we just ignore it
432
if(b + 4 > end) break;
433
li = readNetworkLong(b); b += 4; // reply length
435
#ifdef GNASH_DEBUG_REMOTING
436
log_debug("about to parse amf value");
438
// this updates b to point to the next unparsed byte
439
as_value reply_as_value;
440
if ( ! reply_as_value.readAMF0(b, end, -1, objRefs, vm) )
442
log_error("parse amf failed");
443
// this will happen if we get
444
// bogus data, or if the data runs
445
// off the end of the buffer
446
// provided, or if we get data we
447
// don't know how to parse
450
#ifdef GNASH_DEBUG_REMOTING
451
log_debug("parsed amf");
454
// update variable to show how much we've parsed
455
reply_start = b - reply.data();
457
// if actionscript specified a callback object, call it
458
boost::intrusive_ptr<as_object> callback = pop_callback(id);
460
#ifdef GNASH_DEBUG_REMOTING
461
log_debug("calling onResult callback");
463
// FIXME check if above line can fail and we have to react
464
callback->callMethod(NSV::PROP_ON_RESULT, reply_as_value);
465
#ifdef GNASH_DEBUG_REMOTING
466
log_debug("callback called");
469
#ifdef GNASH_DEBUG_REMOTING
470
log_debug("couldn't find callback object");
479
log_error("Response from remoting service < 8 bytes");
482
#ifdef GNASH_DEBUG_REMOTING
483
log_debug("deleting connection");
491
if(!_connection && queued_count > 0) {
492
#ifdef GNASH_DEBUG_REMOTING
493
log_debug("creating connection");
495
// set the "number of bodies" header
496
(reinterpret_cast<boost::uint16_t*>(postdata.data() + 4))[0] = htons(queued_count);
497
std::string postdata_str(reinterpret_cast<char*>(postdata.data()), postdata.size());
498
#ifdef GNASH_DEBUG_REMOTING
499
log_debug("NetConnection.call(): encoded args from %1% calls: %2%", queued_count, hexify(postdata.data(), postdata.size(), false));
502
_connection.reset(StreamProvider::getDefaultInstance().getStream(url, postdata_str).release());
504
#ifdef GNASH_DEBUG_REMOTING
505
log_debug("connection created");
509
if(_connection == 0 && queued_count == 0) {
510
#ifdef GNASH_DEBUG_REMOTING
511
log_debug("stopping ticking");
514
#ifdef GNASH_DEBUG_REMOTING
515
log_debug("ticking stopped");
520
static as_value amfqueue_tick_wrapper(const fn_call& fn)
522
boost::intrusive_ptr<NetConnection> ptr = ensureType<NetConnection>(fn.this_ptr);
523
// FIXME check if it's possible for the URL of a NetConnection to change between call()s
524
ptr->_callQueue->tick();
528
void markReachableResources() const
530
for (CallbacksMap::const_iterator i=callbacks.begin(), e=callbacks.end(); i!=e; ++i)
532
i->second->setReachable();
542
boost::intrusive_ptr<builtin_function> ticker_as =
543
new builtin_function(&AMFQueue::amfqueue_tick_wrapper);
545
std::auto_ptr<Timer> timer(new Timer);
546
unsigned long delayMS = 50; // FIXME crank up to 50 or so
547
timer->setInterval(*ticker_as, delayMS, &_nc);
548
ticker = _nc.getVM().getRoot().add_interval_timer(timer, true);
551
void push_amf(const SimpleBuffer &amf)
553
GNASH_REPORT_FUNCTION;
555
postdata.append(amf.data(), amf.size());
564
_nc.getVM().getRoot().clear_interval_timer(ticker);
568
void push_callback(const std::string& id, boost::intrusive_ptr<as_object> callback) {
569
callbacks.insert(std::pair<std::string, boost::intrusive_ptr<as_object> >(id, callback));
572
boost::intrusive_ptr<as_object> pop_callback(std::string id)
574
CallbacksMap::iterator it = callbacks.find(id);
575
if (it != callbacks.end()) {
576
boost::intrusive_ptr<as_object> callback = it->second;
577
//boost::intrusive_ptr<as_object> callback;
578
//callback = it.second;
589
NetConnection::call_method(const fn_call& fn)
591
static int call_number = 0;
592
boost::intrusive_ptr<NetConnection> ptr = ensureType<NetConnection>(fn.this_ptr);
596
IF_VERBOSE_ASCODING_ERRORS(
597
log_aserror(_("NetConnection.call(): needs at least one argument"));
599
return as_value(false); // FIXME should we return true anyway?
602
const as_value& methodName_as = fn.arg(0);
603
if (!methodName_as.is_string()) {
604
IF_VERBOSE_ASCODING_ERRORS(
605
std::stringstream ss; fn.dump_args(ss);
606
log_aserror(_("NetConnection.call(%s): first argument (methodName) must be a string"), ss.str());
608
return as_value(false); // FIXME should we return true anyway?
611
std::stringstream ss; fn.dump_args(ss);
612
#ifdef GNASH_DEBUG_REMOTING
613
log_debug("NetConnection.call(%s)", ss.str());
616
// TODO: arg(1) is the response object. let it know when data comes back
617
boost::intrusive_ptr<as_object> asCallback = 0;
620
if(fn.arg(1).is_object()) {
621
asCallback = (fn.arg(1).to_object());
625
IF_VERBOSE_ASCODING_ERRORS(
626
std::stringstream ss; fn.dump_args(ss);
627
log_aserror("NetConnection::call(%s): second argument must be an object", ss.str());
632
boost::scoped_ptr<SimpleBuffer> buf ( new SimpleBuffer(32) );
634
std::string methodName = methodName_as.to_string();
636
// junk at the top (version, client id, 0 headers, 1 body)
637
// this is done by AMFQueue now: buf->append("\000\000\000\000\000\001", 6);
640
buf->appendNetworkShort(methodName.size());
641
buf->append(methodName.c_str(), methodName.size());
643
// client id (result number) as counted string
644
// the convention seems to be / followed by a unique (ascending) number
647
std::ostringstream os;
648
os << "/" << call_number;
649
const std::string callNumberString = os.str();
651
buf->appendNetworkShort(callNumberString.size());
652
buf->append(callNumberString.c_str(), callNumberString.size());
654
size_t total_size_offset = buf->size();
655
buf->append("\000\000\000\000", 4); // total size to be filled in later
657
std::map<as_object*, size_t> offsetTable;
658
VM& vm = ptr->getVM();
660
// encode array of arguments to remote method
661
buf->appendByte(amf::Element::STRICT_ARRAY_AMF0);
662
buf->appendNetworkLong(fn.nargs - 2);
665
for (unsigned int i = 2; i < fn.nargs; ++i)
667
const as_value& arg = fn.arg(i);
668
if ( ! arg.writeAMF0(*buf, offsetTable, vm) )
670
log_error("Could not serialize NetConnection.call argument %d", i);
675
// Set the "total size" parameter.
676
*(reinterpret_cast<uint32_t*>(buf->data() + total_size_offset)) = htonl(buf->size() - 4 - total_size_offset);
679
#ifdef GNASH_DEBUG_REMOTING
680
log_debug(_("NetConnection.call(): encoded args: %s"), hexify(buf->data(), buf->size(), false));
683
// FIXME check that ptr->_prefixURL is valid
684
URL url(ptr->validateURL(std::string()));
687
// FIXME check if it's possible for the URL of a NetConnection to change between call()s
688
if (! ptr->_callQueue.get()) {
689
ptr->_callQueue.reset(new AMFQueue(*ptr, url));
693
//boost::intrusive_ptr<as_object> intrusive_callback(asCallback);
694
#ifdef GNASH_DEBUG_REMOTING
695
log_debug("calling enqueue with callback");
697
ptr->_callQueue->enqueue(*buf, callNumberString, asCallback);
698
//? delete asCallback;
702
#ifdef GNASH_DEBUG_REMOTING
703
log_debug("calling enqueue without callback");
705
ptr->_callQueue->enqueue(*buf);
707
#ifdef GNASH_DEBUG_REMOTING
708
log_debug("called enqueue");
715
NetConnection::close_method(const fn_call& fn)
717
boost::intrusive_ptr<NetConnection> ptr = ensureType<NetConnection>(fn.this_ptr);
720
log_unimpl("NetConnection.close()");
725
NetConnection::isConnected_getset(const fn_call& fn)
727
boost::intrusive_ptr<NetConnection> ptr = ensureType<NetConnection>(fn.this_ptr);
730
if ( fn.nargs == 0 ) // getter
732
log_unimpl("NetConnection.isConnected get");
737
IF_VERBOSE_ASCODING_ERRORS(
738
log_aserror("Tried to set read-only property NetConnection.isConnected");
745
NetConnection::uri_getset(const fn_call& fn)
747
boost::intrusive_ptr<NetConnection> ptr = ensureType<NetConnection>(fn.this_ptr);
750
if ( fn.nargs == 0 ) // getter
752
log_unimpl("NetConnection.uri get");
757
log_unimpl("NetConnection.uri set");
764
NetConnection::attachNetConnectionInterface(as_object& o)
766
o.init_member("connect", new builtin_function(NetConnection::connect_method));
767
o.init_member("addHeader", new builtin_function(NetConnection::addHeader_method));
768
o.init_member("call", new builtin_function(NetConnection::call_method));
769
o.init_member("close", new builtin_function(NetConnection::close_method));
774
NetConnection::attachProperties()
776
init_property("isConnected", &NetConnection::isConnected_getset, &NetConnection::isConnected_getset);
777
init_property("uri", &NetConnection::uri_getset, &NetConnection::uri_getset);
781
NetConnection::getNetConnectionInterface()
784
static boost::intrusive_ptr<as_object> o;
787
o = new as_object(getObjectInterface());
788
NetConnection::attachNetConnectionInterface(*o);
795
NetConnection::registerConstructor(as_object& global)
798
// This is going to be the global NetConnection "class"/"function"
799
static boost::intrusive_ptr<builtin_function> cl;
803
cl=new builtin_function(&netconnection_new, getNetConnectionInterface());
804
// replicate all interface to class, to be able to access
805
// all methods as static functions
806
// TODO: this is probably wrong !
807
NetConnection::attachNetConnectionInterface(*cl);
811
// Register _global.String
812
global.init_member("NetConnection", cl.get());
816
// extern (used by Global.cpp)
817
void netconnection_class_init(as_object& global)
819
NetConnection::registerConstructor(global);
822
// here to have AMFQueue definition available
823
NetConnection::~NetConnection()
828
NetConnection::markReachableResources() const
830
if ( _callQueue.get() ) _callQueue->markReachableResources();
831
markAsObjectReachable();
836
} // end of gnash namespace