1 // LoadableObject.cpp: abstraction of network-loadable AS object functions.
2 //
3 //   Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012
4 //   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.
15 //
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
19 //
20 
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"
37 
38 #include <sstream>
39 #include <map>
40 #include <boost/tokenizer.hpp>
41 
42 namespace gnash {
43 
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);
52 }
53 
54 void
attachLoadableInterface(as_object & o,int flags)55 attachLoadableInterface(as_object& o, int flags)
56 {
57     Global_as& gl = getGlobal(o);
58 
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);
65 }
66 
67 void
registerLoadableNative(as_object & o)68 registerLoadableNative(as_object& o)
69 {
70     VM& vm = getVM(o);
71 
72     vm.registerNative(loadableobject_load, 301, 0);
73     vm.registerNative(loadableobject_send, 301, 1);
74     vm.registerNative(loadableobject_sendAndLoad, 301, 2);
75 
76     /// This is only automatically used in LoadVars.
77     vm.registerNative(loadableobject_decode, 301, 3);
78 }
79 
80 /// Functors for use with foreachArray
81 namespace {
82 
83 class WriteHeaders
84 {
85 public:
86 
WriteHeaders(NetworkAdapter::RequestHeaders & headers)87     WriteHeaders(NetworkAdapter::RequestHeaders& headers)
88         :
89         _headers(headers),
90         _i(0)
91     {}
92 
operator ()(const as_value & val)93     void operator()(const as_value& val)
94     {
95         // Store even elements and continue
96         if (!(_i++ % 2)) {
97             _key = val;
98             return;
99         }
100 
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();
105     }
106 
107 private:
108     as_value _key;
109     NetworkAdapter::RequestHeaders _headers;
110     size_t _i;
111 };
112 
113 class GetHeaders
114 {
115 public:
116 
GetHeaders(as_object & target)117     GetHeaders(as_object& target)
118         :
119         _target(target),
120         _i(0)
121     {}
122 
operator ()(const as_value & val)123     void operator()(const as_value& val)
124     {
125         // Store even elements and continue
126         if (!(_i++ % 2)) {
127             _key = val;
128             return;
129         }
130 
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);
135     }
136 
137 private:
138     as_value _key;
139     as_object& _target;
140     size_t _i;
141 };
142 
143 as_value
loadableobject_getBytesLoaded(const fn_call & fn)144 loadableobject_getBytesLoaded(const fn_call& fn)
145 {
146     as_object* ptr = ensure<ValidThis>(fn);
147     as_value bytesLoaded;
148     ptr->get_member(NSV::PROP_uBYTES_LOADED, &bytesLoaded);
149     return bytesLoaded;
150 }
151 
152 as_value
loadableobject_getBytesTotal(const fn_call & fn)153 loadableobject_getBytesTotal(const fn_call& fn)
154 {
155     as_object* ptr = ensure<ValidThis>(fn);
156     as_value bytesTotal;
157     ptr->get_member(NSV::PROP_uBYTES_TOTAL, &bytesTotal);
158     return bytesTotal;
159 }
160 
161 /// Can take either a two strings as arguments or an array of strings,
162 /// alternately header and value.
163 as_value
loadableobject_addRequestHeader(const fn_call & fn)164 loadableobject_addRequestHeader(const fn_call& fn)
165 {
166 
167     as_value customHeaders;
168     as_object* array;
169 
170     if (fn.this_ptr->get_member(NSV::PROP_uCUSTOM_HEADERS, &customHeaders))
171     {
172         array = toObject(customHeaders, getVM(fn));
173         if (!array)
174         {
175             IF_VERBOSE_ASCODING_ERRORS(
176                 log_aserror(_("XML.addRequestHeader: XML._customHeaders "
177                               "is not an object"));
178             );
179             return as_value();
180         }
181     }
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);
187     }
188 
189     if (fn.nargs == 0)
190     {
191         // Return after having initialized the _customHeaders array.
192         IF_VERBOSE_ASCODING_ERRORS(
193             log_aserror(_("XML.addRequestHeader requires at least "
194                           "one argument"));
195         );
196         return as_value();
197     }
198 
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));
203 
204         if (!headerArray) {
205             IF_VERBOSE_ASCODING_ERRORS(
206                 log_aserror(_("XML.addRequestHeader: single argument "
207                                 "is not an array"));
208             );
209             return as_value();
210         }
211 
212         GetHeaders gh(*array);
213         foreachArray(*headerArray, gh);
214         return as_value();
215     }
216 
217     if (fn.nargs > 2)
218     {
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());
224         );
225     }
226 
227     // Push both to the _customHeaders array.
228     const as_value& name = fn.arg(0);
229     const as_value& val = fn.arg(1);
230 
231     // Both arguments must be strings.
232     if (!name.is_string() || !val.is_string())
233     {
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());
239         );
240         return as_value();
241     }
242 
243     callMethod(array, NSV::PROP_PUSH, name, val);
244 
245     return as_value();
246 }
247 /// Decode method (ASnative 301, 3) can be applied to any as_object.
248 as_value
loadableobject_decode(const fn_call & fn)249 loadableobject_decode(const fn_call& fn)
250 {
251     as_object* ptr = ensure<ValidThis>(fn);
252 
253     if (!fn.nargs) return as_value(false);
254 
255     typedef std::map<std::string, std::string> ValuesMap;
256     ValuesMap vals;
257 
258     const int version = getSWFVersion(fn);
259     const std::string qs = fn.arg(0).to_string(version);
260 
261     if (qs.empty()) return as_value();
262 
263     typedef boost::char_separator<char> Sep;
264     typedef boost::tokenizer<Sep> Tok;
265     Tok t1(qs, Sep("&"));
266 
267     VM& vm = getVM(fn);
268 
269     for (Tok::iterator tit=t1.begin(); tit!=t1.end(); ++tit) {
270 
271         const std::string& nameval = *tit;
272 
273         std::string name;
274         std::string value;
275 
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);
281         }
282 
283         URL::decode(name);
284         URL::decode(value);
285 
286         if (!name.empty()) ptr->set_member(getURI(vm, name), value);
287     }
288 
289     return as_value();
290 }
291 
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
loadableobject_sendAndLoad(const fn_call & fn)298 loadableobject_sendAndLoad(const fn_call& fn)
299 {
300     as_object* obj = ensure<ValidThis>(fn);
301 
302     if ( fn.nargs < 2 ) {
303         IF_VERBOSE_ASCODING_ERRORS(
304         log_aserror(_("sendAndLoad() requires at least two arguments"));
305         );
306         return as_value(false);
307     }
308 
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"));
313         );
314         return as_value(false);
315     }
316 
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)"));
321         );
322         return as_value(false);
323     }
324 
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));
328 
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;
334 
335     if (fn.nargs > 2) {
336         const std::string& method = fn.arg(2).to_string();
337         StringNoCaseEqual nc;
338         post = nc(method, "post");
339     }
340 
341     const RunResources& ri = getRunResources(*obj);
342 
343     URL url(urlstr, ri.streamProvider().baseURL());
344 
345     std::unique_ptr<IOChannel> str;
346 
347     if (post) {
348         as_value customHeaders;
349 
350         NetworkAdapter::RequestHeaders headers;
351 
352         if (obj->get_member(NSV::PROP_uCUSTOM_HEADERS, &customHeaders)) {
353 
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);
360             }
361         }
362 
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()));
369         }
370 
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();
375 
376         /// It doesn't matter if there are no request headers.
377         str = ri.streamProvider().getStream(url, strval, headers);
378     }
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();
383 
384         // Any data must be added to the existing querystring.
385         if (!dataString.empty()) {
386 
387             std::string existingQS = url.querystring();
388             if (!existingQS.empty()) existingQS += "&";
389 
390             url.set_querystring(existingQS + dataString);
391         }
392 
393         log_debug("Using GET method for sendAndLoad: %s", url.str());
394         str = ri.streamProvider().getStream(url.str());
395     }
396 
397     log_security(_("Loading from URL: '%s'"), url.str());
398 
399     movie_root& mr = getRoot(*obj);
400 
401     /// All objects get a loaded member, set to false.
402     target->set_member(NSV::PROP_LOADED, false);
403 
404     mr.addLoadableObject(target, std::move(str));
405     return as_value(true);
406 }
407 
408 
409 as_value
loadableobject_load(const fn_call & fn)410 loadableobject_load(const fn_call& fn)
411 {
412     as_object* obj = ensure<ValidThis>(fn);
413 
414     if ( fn.nargs < 1 )
415     {
416         IF_VERBOSE_ASCODING_ERRORS(
417         log_aserror(_("load() requires at least one argument"));
418         );
419         return as_value(false);
420     }
421 
422     const std::string& urlstr = fn.arg(0).to_string();
423     if ( urlstr.empty() )
424     {
425         IF_VERBOSE_ASCODING_ERRORS(
426         log_aserror(_("load(): invalid empty URL"));
427         );
428         return as_value(false);
429     }
430 
431     // Set loaded property to false; will be updated (hopefully)
432     // when loading is complete.
433     obj->set_member(NSV::PROP_LOADED, false);
434 
435     const RunResources& ri = getRunResources(*obj);
436 
437     URL url(urlstr, ri.streamProvider().baseURL());
438 
439     // Checks whether access is allowed.
440     std::unique_ptr<IOChannel> str(ri.streamProvider().getStream(url));
441 
442     movie_root& mr = getRoot(fn);
443     mr.addLoadableObject(obj, std::move(str));
444 
445     obj->set_member(NSV::PROP_uBYTES_LOADED, 0.0);
446     obj->set_member(NSV::PROP_uBYTES_TOTAL, as_value());
447 
448     return as_value(true);
449 
450 }
451 
452 
453 as_value
loadableobject_send(const fn_call & fn)454 loadableobject_send(const fn_call& fn)
455 {
456     as_object* obj = ensure<ValidThis>(fn);
457 
458     std::string target;
459     std::string url;
460     std::string method;
461 
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;
472     }
473 
474     StringNoCaseEqual noCaseCompare;
475 
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;
480 
481     // Encode the data in the default way for the type.
482     std::ostringstream data;
483 
484     movie_root& m = getRoot(fn);
485 
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();
491 
492     m.getURL(url, target, str, meth);
493 
494     return as_value(true);
495 }
496 
497 } // anonymous namespace
498 } // namespace gnash
499