big merge from master, fix rpm creation, drop fetching swfdec
[gnash.git] / libcore / asobj / LoadableObject.cpp
blobf794c00ba6be6d6521965aaf9933ae8be00e5258
1 // LoadableObject.cpp: abstraction of network-loadable AS object functions.
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 #include "RunResources.h"
22 #include "LoadableObject.h"
23 #include "log.h"
24 #include "Array_as.h"
25 #include "as_object.h"
26 #include "StreamProvider.h"
27 #include "URL.h"
28 #include "namedStrings.h"
29 #include "movie_root.h"
30 #include "VM.h"
31 #include "NativeFunction.h"
32 #include "utf8.h"
33 #include "fn_call.h"
34 #include "GnashAlgorithm.h"
35 #include "Global_as.h"
36 #include "IOChannel.h"
38 #include <sstream>
39 #include <map>
40 #include <boost/tokenizer.hpp>
42 namespace gnash {
44 namespace {
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);
54 void
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);
67 void
68 registerLoadableNative(as_object& o)
70 VM& vm = getVM(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
81 namespace {
83 class WriteHeaders
85 public:
87 WriteHeaders(NetworkAdapter::RequestHeaders& headers)
89 _headers(headers),
90 _i(0)
93 void operator()(const as_value& val)
95 // Store even elements and continue
96 if (!(_i++ % 2)) {
97 _key = val;
98 return;
101 // Both elements apparently must be strings, or we move onto the
102 // next pair.
103 if (!val.is_string() || !_key.is_string()) return;
104 _headers[_key.to_string()] = val.to_string();
107 private:
108 as_value _key;
109 NetworkAdapter::RequestHeaders _headers;
110 size_t _i;
113 class GetHeaders
115 public:
117 GetHeaders(as_object& target)
119 _target(target),
120 _i(0)
123 void operator()(const as_value& val)
125 // Store even elements and continue
126 if (!(_i++ % 2)) {
127 _key = val;
128 return;
131 // Both elements apparently must be strings, or we move onto the
132 // next pair.
133 if (!val.is_string() || !_key.is_string()) return;
134 callMethod(&_target, NSV::PROP_PUSH, _key, val);
137 private:
138 as_value _key;
139 as_object& _target;
140 size_t _i;
143 as_value
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);
149 return bytesLoaded;
152 as_value
153 loadableobject_getBytesTotal(const fn_call& fn)
155 as_object* ptr = ensure<ValidThis>(fn);
156 as_value bytesTotal;
157 ptr->get_member(NSV::PROP_uBYTES_TOTAL, &bytesTotal);
158 return bytesTotal;
161 /// Can take either a two strings as arguments or an array of strings,
162 /// alternately header and value.
163 as_value
164 loadableobject_addRequestHeader(const fn_call& fn)
167 as_value customHeaders;
168 as_object* array;
170 if (fn.this_ptr->get_member(NSV::PROP_uCUSTOM_HEADERS, &customHeaders))
172 array = toObject(customHeaders, getVM(fn));
173 if (!array)
175 IF_VERBOSE_ASCODING_ERRORS(
176 log_aserror(_("XML.addRequestHeader: XML._customHeaders "
177 "is not an object"));
179 return as_value();
182 else {
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);
189 if (fn.nargs == 0)
191 // Return after having initialized the _customHeaders array.
192 IF_VERBOSE_ASCODING_ERRORS(
193 log_aserror(_("XML.addRequestHeader requires at least "
194 "one argument"));
196 return as_value();
199 if (fn.nargs == 1) {
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));
204 if (!headerArray) {
205 IF_VERBOSE_ASCODING_ERRORS(
206 log_aserror(_("XML.addRequestHeader: single argument "
207 "is not an array"));
209 return as_value();
212 GetHeaders gh(*array);
213 foreachArray(*headerArray, gh);
214 return as_value();
217 if (fn.nargs > 2)
219 IF_VERBOSE_ASCODING_ERRORS(
220 std::ostringstream ss;
221 fn.dump_args(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;
236 fn.dump_args(ss);
237 log_aserror(_("XML.addRequestHeader(%s): both arguments "
238 "must be a string"), ss.str());
240 return as_value();
243 callMethod(array, NSV::PROP_PUSH, name, val);
245 return as_value();
247 /// Decode method (ASnative 301, 3) can be applied to any as_object.
248 as_value
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;
256 ValuesMap vals;
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("&"));
267 VM& vm = getVM(fn);
269 for (Tok::iterator tit=t1.begin(); tit!=t1.end(); ++tit) {
271 const std::string& nameval = *tit;
273 std::string name;
274 std::string value;
276 size_t eq = nameval.find("=");
277 if (eq == std::string::npos) name = nameval;
278 else {
279 name = nameval.substr(0, eq);
280 value = nameval.substr(eq + 1);
283 URL::decode(name);
284 URL::decode(value);
286 if (!name.empty()) ptr->set_member(getURI(vm, name), value);
289 return as_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.
297 as_value
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.
333 bool post = false;
335 if (fn.nargs > 2) {
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;
347 if (post) {
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
355 /// array.
356 as_object* array = toObject(customHeaders, getVM(fn));
357 if (array) {
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);
379 else {
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);
409 as_value
410 loadableobject_load(const fn_call& fn)
412 as_object* obj = ensure<ValidThis>(fn);
414 if ( fn.nargs < 1 )
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);
453 as_value
454 loadableobject_send(const fn_call& fn)
456 as_object* obj = ensure<ValidThis>(fn);
458 std::string target;
459 std::string url;
460 std::string method;
462 switch (fn.nargs) {
463 case 0:
464 return as_value(false);
465 case 3:
466 method = fn.arg(2).to_string();
467 case 2:
468 target = fn.arg(1).to_string();
469 case 1:
470 url = fn.arg(0).to_string();
471 break;
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
488 // encoded.
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
498 } // namespace gnash