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