1////////////////////////////////////////////////////////////////////////////////
2//
3//  ADOBE SYSTEMS INCORPORATED
4//  Copyright 2007 Adobe Systems Incorporated
5//  All Rights Reserved.
6//
7//  NOTICE: Adobe permits you to use, modify, and distribute this file
8//  in accordance with the terms of the license agreement accompanying it.
9//
10////////////////////////////////////////////////////////////////////////////////
11
12package mx.core
13{
14
15import flash.display.Loader;
16import flash.events.Event;
17import flash.events.ErrorEvent;
18import flash.events.ProgressEvent;
19import flash.events.IOErrorEvent;
20import flash.events.SecurityErrorEvent;
21import flash.net.URLRequest;
22import flash.net.URLLoader;
23import flash.net.URLLoaderDataFormat;
24import flash.system.LoaderContext;
25import flash.system.ApplicationDomain;
26import flash.system.LoaderContext;
27import flash.system.Security;
28import flash.system.SecurityDomain;
29import flash.utils.ByteArray;
30//import flash.utils.getTimer;        // PERFORMANCE_INFO
31
32import mx.events.RSLEvent;
33import mx.utils.SHA256;
34import mx.utils.LoaderUtil;
35
36[ExcludeClass]
37
38/**
39 *  @private
40 *  Cross-domain RSL Item Class.
41 *
42 *  The rsls are typically located on a different host than the loader.
43 *  There are signed and unsigned Rsls, both have a digest to confirm the
44 *  correct rsl is loaded.
45 *  Signed Rsls are loaded by setting the digest of the URLRequest.
46 *  Unsigned Rsls are check using actionScript to calculate a sha-256 hash of
47 *  the loaded bytes and compare them to the expected digest.
48 *
49 */
50public class CrossDomainRSLItem extends RSLItem
51{
52    include "../core/Version.as";
53
54    //--------------------------------------------------------------------------
55    //
56    //  Variables
57    //
58    //--------------------------------------------------------------------------
59
60    private var rslUrls:Array;  // first url is the primary url in the url parameter, others are failovers
61    private var policyFileUrls:Array; // optional policy files, parallel array to rslUrls
62    private var digests:Array;      // option rsl digest, parallel array to rslUrls
63    private var isSigned:Array;     // each entry is a boolean value. "true" if the rsl in the parallel array is signed
64    private var hashTypes:Array;     //  type of hash used to create the digest
65    private var urlIndex:int = 0;   // index into url being loaded in rslsUrls and other parallel arrays
66
67    // this reference to the loader keeps the loader from being garbage
68    // collected before the complete event can be sent.
69    private var loadBytesLoader:Loader;
70
71//    private var startTime:int;      // PERFORMANCE_INFO
72
73    //--------------------------------------------------------------------------
74    //
75    //  Constructor
76    //
77    //--------------------------------------------------------------------------
78
79    /**
80    *  Create a cross-domain RSL item to load.
81    *
82    *  @param rslUrls Array of Strings, may not be null. Each String is the url of an RSL to load.
83    *  @param policyFileUrls Array of Strings, may not be null. Each String contains the url of an
84    *                       policy file which may be required to allow the RSL to be read from another
85    *                       domain. An empty string means there is no policy file specified.
86    *  @param digests Array of Strings, may not be null. A String contains the value of the digest
87    *                computed by the hash in the corresponding entry in the hashTypes Array. An empty
88    *                string may be provided for unsigned RSLs to loaded them without verifying the digest.
89    *                This is provided as a development cycle convenience and should not be used in a
90    *                production application.
91    *  @param hashTypes Array of Strings, may not be null. Each String identifies the type of hash
92    *                  used to compute the digest. Currently the only valid value is SHA256.TYPE_ID.
93    *  @param isSigned Array of boolean, may not be null. Each boolean value specifies if the RSL to be
94    *                  loaded is a signed or unsigned RSL. If the value is true the RSL is signed.
95    *                  If the value is false the RSL is unsigned.
96    *  @param rootURL provides the url used to locate relative RSL urls.
97    */
98    public function CrossDomainRSLItem(rslUrls:Array,
99                             policyFileUrls:Array,
100                             digests:Array,
101                             hashTypes:Array,
102                             isSigned:Array,
103                             rootURL:String = null)
104    {
105        super(rslUrls[0], rootURL);
106
107        this.rslUrls = rslUrls;
108        this.policyFileUrls = policyFileUrls;
109        this.digests = digests;
110        this.hashTypes = hashTypes;
111        this.isSigned = isSigned;
112
113        // startTime = getTimer(); // PERFORMANCE_INFO
114    }
115
116
117    //--------------------------------------------------------------------------
118    //
119    //  Overridden Methods
120    //
121    //--------------------------------------------------------------------------
122
123
124    /**
125     *
126     * Load an RSL.
127    *
128    * @param progressHandler       receives ProgressEvent.PROGRESS events, may be null.
129    * @param completeHandler       receives Event.COMPLETE events, may be null.
130    * @param ioErrorHandler        receives IOErrorEvent.IO_ERROR events, may be null.
131    * @param securityErrorHandler  receives SecurityErrorEvent.SECURITY_ERROR events, may be null.
132    * @param rslErrorHandler       receives RSLEvent.RSL_ERROR events, may be null.
133    *
134    */
135    override public function load(progressHandler:Function,
136                                  completeHandler:Function,
137                                  ioErrorHandler:Function,
138                                  securityErrorHandler:Function,
139                                  rslErrorHandler:Function):void
140    {
141        chainedProgressHandler = progressHandler;
142        chainedCompleteHandler = completeHandler;
143        chainedIOErrorHandler = ioErrorHandler;
144        chainedSecurityErrorHandler = securityErrorHandler;
145        chainedRSLErrorHandler = rslErrorHandler;
146
147
148/*
149        // Debug loading of swf files
150
151        trace("begin load of " + url);
152                if (Security.sandboxType == Security.REMOTE)
153        {
154            trace(" in REMOTE sandbox");
155        }
156        else if (Security.sandboxType == Security.LOCAL_TRUSTED)
157        {
158            trace(" in LOCAL_TRUSTED sandbox");
159        }
160        else if (Security.sandboxType == Security.LOCAL_WITH_FILE)
161        {
162            trace(" in LOCAL_WITH_FILE sandbox");
163        }
164        else if (Security.sandboxType == Security.LOCAL_WITH_NETWORK)
165        {
166            trace(" in LOCAL_WITH_NETWORK sandbox");
167        }
168*/
169
170        urlRequest = new URLRequest(LoaderUtil.createAbsoluteURL(rootURL, rslUrls[urlIndex]));
171        var loader:URLLoader = new URLLoader();
172        loader.dataFormat = URLLoaderDataFormat.BINARY;
173
174        // We needs to listen to certain events.
175
176        loader.addEventListener(
177            ProgressEvent.PROGRESS, itemProgressHandler);
178
179        loader.addEventListener(
180            Event.COMPLETE, itemCompleteHandler);
181
182        loader.addEventListener(
183            IOErrorEvent.IO_ERROR, itemErrorHandler);
184
185        loader.addEventListener(
186            SecurityErrorEvent.SECURITY_ERROR, itemErrorHandler);
187
188        if (policyFileUrls.length > urlIndex &&
189            policyFileUrls[urlIndex] != "")
190        {
191            Security.loadPolicyFile(policyFileUrls[urlIndex]);
192        }
193
194        if (isSigned[urlIndex])
195        {
196            if (urlRequest.hasOwnProperty("digest"))
197            {
198                // load a signed rsl by specifying the digest
199                urlRequest.digest = digests[urlIndex];
200            }
201            else if (hasFailover())
202            {
203                loadFailover();
204                return;
205            }
206            else
207            {
208                // B Feature: externalize error message
209                var rslError:ErrorEvent = new ErrorEvent(RSLEvent.RSL_ERROR);
210                rslError.text = "Flex Error #1002: Flash Player 9.0.115 and above is required to support signed RSLs. Problem occurred when trying to load the RSL " +
211                                urlRequest.url +
212                                ".  Upgrade your Flash Player and try again.";
213                super.itemErrorHandler(rslError);
214                return;
215            }
216        }
217
218//        trace("start load of " + urlRequest.url + " at " + (getTimer() - startTime)); // PERFORMANCE_INFO
219
220        loader.load(urlRequest);
221    }
222
223
224
225    //--------------------------------------------------------------------------
226    //
227    //  Methods
228    //
229    //--------------------------------------------------------------------------
230
231    /**
232     *  @private
233     *  Complete the load of the cross-domain rsl by loading it into the current
234     *  application domain. The load was started by loadCdRSL.
235     *
236     *  @param - urlLoader from the complete event.
237     *
238     *  @return - true if the load was completed successfully or unsuccessfully,
239     *            false if the load of a failover rsl was started
240     */
241    private function completeCdRslLoad(urlLoader:URLLoader):Boolean
242    {
243        // handle player bug #204244, complete event without data after an error
244        if (urlLoader == null || urlLoader.data == null || ByteArray(urlLoader.data).bytesAvailable == 0)
245        {
246            return true;
247        }
248
249        // load the bytes into the current application domain.
250        loadBytesLoader = new Loader();
251        var context:LoaderContext = new LoaderContext();
252        context.applicationDomain = ApplicationDomain.currentDomain;
253        context.securityDomain = null;
254
255        // If the AIR flag is available then set it to true so we can
256        // load the RSL without a security error.
257        if ("allowLoadBytesCodeExecution" in context)
258        {
259            context["allowLoadBytesCodeExecution"] = true;
260        }
261
262        // verify the digest, if any, is correct
263        if (digests[urlIndex] != null && String(digests[urlIndex]).length > 0)
264        {
265            var verifiedDigest:Boolean = false;
266            if (!isSigned[urlIndex])
267            {
268                // verify an unsigned rsl
269                if (hashTypes[urlIndex] == SHA256.TYPE_ID)
270                {
271                    // get the bytes from the rsl and calculate the hash
272                    var rslDigest:String = null;
273                    if (urlLoader.data != null)
274                    {
275                        rslDigest = SHA256.computeDigest(urlLoader.data);
276                    }
277
278                    if (rslDigest == digests[urlIndex])
279                    {
280                        verifiedDigest = true;
281                    }
282                }
283            }
284            else
285            {
286                // signed rsls are verified by the player
287                verifiedDigest = true;
288            }
289
290            if (!verifiedDigest)
291            {
292                // failover to the next rsl, if one exists
293                // no failover to load, all the rsls have failed to load
294                // report an error.
295                 // B Feature: externalize error message
296                var hasFailover:Boolean = hasFailover();
297                var rslError:ErrorEvent = new ErrorEvent(RSLEvent.RSL_ERROR);
298                rslError.text = "Flex Error #1001: Digest mismatch with RSL " +
299                                urlRequest.url +
300                                ". Redeploy the matching RSL or relink your application with the matching library.";
301                itemErrorHandler(rslError);
302
303                return !hasFailover;
304            }
305        }
306
307        // load the rsl into memory
308        loadBytesLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, loadBytesCompleteHandler);
309        loadBytesLoader.loadBytes(urlLoader.data, context);
310        return true;
311    }
312
313
314    /**
315    *  Does the current url being processed have a failover?
316    *
317    * @return true if a failover url exists, false otherwise.
318    */
319    public function hasFailover():Boolean
320    {
321        return (rslUrls.length > (urlIndex + 1));
322    }
323
324
325    /**
326    *  Load the next url from the list of failover urls.
327    */
328    public function loadFailover():void
329    {
330        // try to load the failover from the same node again
331        if (urlIndex < rslUrls.length)
332        {
333            trace("Failed to load RSL " + rslUrls[urlIndex]);
334            trace("Failing over to RSL " + rslUrls[urlIndex+1]);
335            urlIndex++;        // move to failover url
336            url = rslUrls[urlIndex];
337            load(chainedProgressHandler,
338                 chainedCompleteHandler,
339                 chainedIOErrorHandler,
340                 chainedSecurityErrorHandler,
341                 chainedRSLErrorHandler);
342        }
343    }
344
345
346
347    //--------------------------------------------------------------------------
348    //
349    //  Overridden Event Handlers
350    //
351    //--------------------------------------------------------------------------
352
353    /**
354     *  @private
355     */
356    override public function itemCompleteHandler(event:Event):void
357    {
358//        trace("complete load of " + url + " at " + (getTimer() - startTime)); // PERFORMANCE_INFO
359
360        // complete loading the cross-domain rsl by calling loadBytes.
361        completeCdRslLoad(event.target as URLLoader);
362    }
363
364    /**
365     *  @private
366     */
367    override public function itemErrorHandler(event:ErrorEvent):void
368    {
369//        trace("error loading " + url + " at " + (getTimer() - startTime)); // PERFORMANCE_INFO
370
371        // if a failover exists, try to load it. Otherwise call super()
372        // for default error handling.
373        if (hasFailover())
374        {
375            trace(decodeURI(event.text));
376            loadFailover();
377        }
378        else
379        {
380            super.itemErrorHandler(event);
381        }
382    }
383
384
385    /**
386     * loader.loadBytes() has a complete event.
387     * Done loading this rsl into memory. Call the completeHandler
388     * to start loading the next rsl.
389     *
390     *  @private
391     */
392    private function loadBytesCompleteHandler(event:Event):void
393    {
394        loadBytesLoader.contentLoaderInfo.removeEventListener(Event.COMPLETE, loadBytesCompleteHandler);
395        loadBytesLoader = null;
396        super.itemCompleteHandler(event);
397    }
398
399}
400}