1 // LoadableObject.cpp: abstraction of network-loadable AS object functions.
3 // Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010,
4 // 2011 Free Software Foundation, Inc
6 // This program is free software; you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation; either version 3 of the License, or
9 // (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software
18 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
21 #include "RunResources.h"
22 #include "LoadableObject.h"
25 #include "as_object.h"
26 #include "StreamProvider.h"
28 #include "namedStrings.h"
29 #include "movie_root.h"
31 #include "NativeFunction.h"
34 #include "GnashAlgorithm.h"
35 #include "Global_as.h"
36 #include "IOChannel.h"
40 #include <boost/tokenizer.hpp>
45 as_value
loadableobject_send(const fn_call
& fn
);
46 as_value
loadableobject_load(const fn_call
& fn
);
47 as_value
loadableobject_decode(const fn_call
& fn
);
48 as_value
loadableobject_sendAndLoad(const fn_call
& fn
);
49 as_value
loadableobject_getBytesTotal(const fn_call
& fn
);
50 as_value
loadableobject_getBytesLoaded(const fn_call
& fn
);
51 as_value
loadableobject_addRequestHeader(const fn_call
& fn
);
55 attachLoadableInterface(as_object
& o
, int flags
)
57 Global_as
& gl
= getGlobal(o
);
59 o
.init_member("addRequestHeader", gl
.createFunction(
60 loadableobject_addRequestHeader
), flags
);
61 o
.init_member("getBytesLoaded", gl
.createFunction(
62 loadableobject_getBytesLoaded
),flags
);
63 o
.init_member("getBytesTotal", gl
.createFunction(
64 loadableobject_getBytesTotal
), flags
);
68 registerLoadableNative(as_object
& o
)
72 vm
.registerNative(loadableobject_load
, 301, 0);
73 vm
.registerNative(loadableobject_send
, 301, 1);
74 vm
.registerNative(loadableobject_sendAndLoad
, 301, 2);
76 /// This is only automatically used in LoadVars.
77 vm
.registerNative(loadableobject_decode
, 301, 3);
80 /// Functors for use with foreachArray
87 WriteHeaders(NetworkAdapter::RequestHeaders
& headers
)
93 void operator()(const as_value
& val
)
95 // Store even elements and continue
101 // Both elements apparently must be strings, or we move onto the
103 if (!val
.is_string() || !_key
.is_string()) return;
104 _headers
[_key
.to_string()] = val
.to_string();
109 NetworkAdapter::RequestHeaders _headers
;
117 GetHeaders(as_object
& target
)
123 void operator()(const as_value
& val
)
125 // Store even elements and continue
131 // Both elements apparently must be strings, or we move onto the
133 if (!val
.is_string() || !_key
.is_string()) return;
134 callMethod(&_target
, NSV::PROP_PUSH
, _key
, val
);
144 loadableobject_getBytesLoaded(const fn_call
& fn
)
146 as_object
* ptr
= ensure
<ValidThis
>(fn
);
147 as_value bytesLoaded
;
148 ptr
->get_member(NSV::PROP_uBYTES_LOADED
, &bytesLoaded
);
153 loadableobject_getBytesTotal(const fn_call
& fn
)
155 as_object
* ptr
= ensure
<ValidThis
>(fn
);
157 ptr
->get_member(NSV::PROP_uBYTES_TOTAL
, &bytesTotal
);
161 /// Can take either a two strings as arguments or an array of strings,
162 /// alternately header and value.
164 loadableobject_addRequestHeader(const fn_call
& fn
)
167 as_value customHeaders
;
170 if (fn
.this_ptr
->get_member(NSV::PROP_uCUSTOM_HEADERS
, &customHeaders
))
172 array
= toObject(customHeaders
, getVM(fn
));
175 IF_VERBOSE_ASCODING_ERRORS(
176 log_aserror(_("XML.addRequestHeader: XML._customHeaders "
177 "is not an object"));
183 array
= getGlobal(fn
).createArray();
184 // This property is always initialized on the first call to
185 // addRequestHeaders. It has default properties.
186 fn
.this_ptr
->init_member(NSV::PROP_uCUSTOM_HEADERS
, array
);
191 // Return after having initialized the _customHeaders array.
192 IF_VERBOSE_ASCODING_ERRORS(
193 log_aserror(_("XML.addRequestHeader requires at least "
200 // This must be an array (or something like it). Keys / values are
201 // pushed in valid pairs to the _customHeaders array.
202 as_object
* headerArray
= toObject(fn
.arg(0), getVM(fn
));
205 IF_VERBOSE_ASCODING_ERRORS(
206 log_aserror(_("XML.addRequestHeader: single argument "
212 GetHeaders
gh(*array
);
213 foreachArray(*headerArray
, gh
);
219 IF_VERBOSE_ASCODING_ERRORS(
220 std::ostringstream ss
;
222 log_aserror(_("XML.addRequestHeader(%s): arguments after the"
223 "second will be discarded"), ss
.str());
227 // Push both to the _customHeaders array.
228 const as_value
& name
= fn
.arg(0);
229 const as_value
& val
= fn
.arg(1);
231 // Both arguments must be strings.
232 if (!name
.is_string() || !val
.is_string())
234 IF_VERBOSE_ASCODING_ERRORS(
235 std::ostringstream ss
;
237 log_aserror(_("XML.addRequestHeader(%s): both arguments "
238 "must be a string"), ss
.str());
243 callMethod(array
, NSV::PROP_PUSH
, name
, val
);
247 /// Decode method (ASnative 301, 3) can be applied to any as_object.
249 loadableobject_decode(const fn_call
& fn
)
251 as_object
* ptr
= ensure
<ValidThis
>(fn
);
253 if (!fn
.nargs
) return as_value(false);
255 typedef std::map
<std::string
, std::string
> ValuesMap
;
258 const int version
= getSWFVersion(fn
);
259 const std::string qs
= fn
.arg(0).to_string(version
);
261 if (qs
.empty()) return as_value();
263 typedef boost::char_separator
<char> Sep
;
264 typedef boost::tokenizer
<Sep
> Tok
;
265 Tok
t1(qs
, Sep("&"));
269 for (Tok::iterator tit
=t1
.begin(); tit
!=t1
.end(); ++tit
) {
271 const std::string
& nameval
= *tit
;
276 size_t eq
= nameval
.find("=");
277 if (eq
== std::string::npos
) name
= nameval
;
279 name
= nameval
.substr(0, eq
);
280 value
= nameval
.substr(eq
+ 1);
286 if (!name
.empty()) ptr
->set_member(getURI(vm
, name
), value
);
292 /// Returns true if the arguments are valid, otherwise false. The
293 /// success of the connection is irrelevant.
294 /// The second argument must be a loadable object (XML or LoadVars).
295 /// An optional third argument specifies the method ("GET", or by default
296 /// "POST"). The values are partly URL encoded if using GET.
298 loadableobject_sendAndLoad(const fn_call
& fn
)
300 as_object
* obj
= ensure
<ValidThis
>(fn
);
302 if ( fn
.nargs
< 2 ) {
303 IF_VERBOSE_ASCODING_ERRORS(
304 log_aserror(_("sendAndLoad() requires at least two arguments"));
306 return as_value(false);
309 const std::string
& urlstr
= fn
.arg(0).to_string();
310 if ( urlstr
.empty() ) {
311 IF_VERBOSE_ASCODING_ERRORS(
312 log_aserror(_("sendAndLoad(): invalid empty url"));
314 return as_value(false);
317 if (!fn
.arg(1).is_object()) {
318 IF_VERBOSE_ASCODING_ERRORS(
319 log_aserror(_("sendAndLoad(): invalid target (must be an "
320 "XML or LoadVars object)"));
322 return as_value(false);
325 // TODO: if this isn't an XML or LoadVars, it won't work, but we should
326 // check how far things get before it fails.
327 as_object
* target
= toObject(fn
.arg(1), getVM(fn
));
329 // According to the Flash 8 Cookbook (Joey Lott, Jeffrey Bardzell), p 427,
330 // this method sends by GET unless overridden, and always by GET in the
331 // standalone player. We have no tests for this, but a Twitter widget
332 // gets Bad Request from the server if we send via POST.
336 const std::string
& method
= fn
.arg(2).to_string();
337 StringNoCaseEqual nc
;
338 post
= nc(method
, "post");
341 const RunResources
& ri
= getRunResources(*obj
);
343 URL
url(urlstr
, ri
.streamProvider().baseURL());
345 std::auto_ptr
<IOChannel
> str
;
348 as_value customHeaders
;
350 NetworkAdapter::RequestHeaders headers
;
352 if (obj
->get_member(NSV::PROP_uCUSTOM_HEADERS
, &customHeaders
)) {
354 /// Read in our custom headers if they exist and are an
356 as_object
* array
= toObject(customHeaders
, getVM(fn
));
358 WriteHeaders
wh(headers
);
359 foreachArray(*array
, wh
);
363 as_value contentType
;
364 if (obj
->get_member(NSV::PROP_CONTENT_TYPE
, &contentType
)) {
365 // This should not overwrite anything set in
366 // LoadVars.addRequestHeader();
367 headers
.insert(std::make_pair("Content-Type",
368 contentType
.to_string()));
371 // Convert the object to a string to send. XML should
372 // not be URL encoded for the POST method, LoadVars
373 // is always URL encoded.
374 const std::string
& strval
= as_value(obj
).to_string();
376 /// It doesn't matter if there are no request headers.
377 str
= ri
.streamProvider().getStream(url
, strval
, headers
);
380 // Convert the object to a string to send. XML should
381 // not be URL encoded for the GET method.
382 const std::string
& dataString
= as_value(obj
).to_string();
384 // Any data must be added to the existing querystring.
385 if (!dataString
.empty()) {
387 std::string existingQS
= url
.querystring();
388 if (!existingQS
.empty()) existingQS
+= "&";
390 url
.set_querystring(existingQS
+ dataString
);
393 log_debug("Using GET method for sendAndLoad: %s", url
.str());
394 str
= ri
.streamProvider().getStream(url
.str());
397 log_security(_("Loading from url: '%s'"), url
.str());
399 movie_root
& mr
= getRoot(*obj
);
401 /// All objects get a loaded member, set to false.
402 target
->set_member(NSV::PROP_LOADED
, false);
404 mr
.addLoadableObject(target
, str
);
405 return as_value(true);
410 loadableobject_load(const fn_call
& fn
)
412 as_object
* obj
= ensure
<ValidThis
>(fn
);
416 IF_VERBOSE_ASCODING_ERRORS(
417 log_aserror(_("load() requires at least one argument"));
419 return as_value(false);
422 const std::string
& urlstr
= fn
.arg(0).to_string();
423 if ( urlstr
.empty() )
425 IF_VERBOSE_ASCODING_ERRORS(
426 log_aserror(_("load(): invalid empty url"));
428 return as_value(false);
431 // Set loaded property to false; will be updated (hopefully)
432 // when loading is complete.
433 obj
->set_member(NSV::PROP_LOADED
, false);
435 const RunResources
& ri
= getRunResources(*obj
);
437 URL
url(urlstr
, ri
.streamProvider().baseURL());
439 // Checks whether access is allowed.
440 std::auto_ptr
<IOChannel
> str(ri
.streamProvider().getStream(url
));
442 movie_root
& mr
= getRoot(fn
);
443 mr
.addLoadableObject(obj
, str
);
445 obj
->set_member(NSV::PROP_uBYTES_LOADED
, 0.0);
446 obj
->set_member(NSV::PROP_uBYTES_TOTAL
, as_value());
448 return as_value(true);
454 loadableobject_send(const fn_call
& fn
)
456 as_object
* obj
= ensure
<ValidThis
>(fn
);
464 return as_value(false);
466 method
= fn
.arg(2).to_string();
468 target
= fn
.arg(1).to_string();
470 url
= fn
.arg(0).to_string();
474 StringNoCaseEqual noCaseCompare
;
476 // POST is the default in a browser, GET supposedly default
477 // in a Flash test environment (whatever that is).
478 MovieClip::VariablesMethod meth
= noCaseCompare(method
, "get") ?
479 MovieClip::METHOD_GET
: MovieClip::METHOD_POST
;
481 // Encode the data in the default way for the type.
482 std::ostringstream data
;
484 movie_root
& m
= getRoot(fn
);
486 // Encode the object for HTTP. If post is true,
487 // XML should not be encoded. LoadVars is always
489 // TODO: test properly.
490 const std::string
& str
= as_value(obj
).to_string();
492 m
.getURL(url
, target
, str
, meth
);
494 return as_value(true);
497 } // anonymous namespace