views:

46

answers:

1

Hey guys,

This is sort of a two part question. I'm building a flash image viewer to be a portable age-restricted module (violent video games). Basically, it will load any number of images from a CDN and require the user to enter their birthday before viewing it. Simple stuff. The problem is that, per company policy, we can't allow those images to be directly accessible by any means of sniffing (eg, watching your Resources/Net tab in Webkit/Firebug, or watching XHR if we loaded them that way). So, I'd like to get your opinions about the best way to approach this.

At first I thought to have a server-side script that loads the image (eg via PHP) by passing a var that can be decrypted but with a dynamic salt, eg, using a method that is very unlikely to be discovered (for example, having the salt be embedded throughout the string), but that defeats the purpose of the CDN as all requests would be geolocated to the server.

Then I thought that loading the images via sockets would be a good solution, however, I'm having trouble converting the binary image (after removing the response headers) to a Bitmap.

Is there a better way to go about this? I know that realistically it's impossible to completely prevent the content from being linked to (eg someone could screenshot the flash) but my goal is just to take steps on our side to prevent that from happening by native means. Any thoughts would be appreciated. Below is the socket code (irrelevant parts removed), if that helps any.

package com.std.socket {
    import flash.system.Security;
    import flash.net.Socket;
    import flash.events.*;
    import flash.errors.*;
    import flash.display.Bitmap;
    import flash.display.MovieClip;
    import flash.display.Loader;
    import flash.utils.ByteArray;

    public class SocketRequest {
        private var root:MovieClip;
        private var sock:Socket;
        private var data:ByteArray = new ByteArray();
        private var request:Object = {
            'host': null,
            'port': null,
            'uri': null,
            'callback': null,
            'response_parts': [] };

        public function SocketRequest(root:MovieClip, host:String, port:int, uri:String, callback:Function):void {
            Security.allowDomain('*');
            Security.loadPolicyFile('http://foo.bar/crossdomain.xml');

            this.request.root = root;
            this.request.host = host;
            this.request.port = port;
            this.request.uri = uri;

            this.sock = new Socket();
            this.sock.addEventListener(Event.CONNECT, this.evt_socketConnect);
            this.sock.addEventListener(Event.CLOSE, this.evt_socketClose);
            this.sock.addEventListener(ProgressEvent.SOCKET_DATA, this.evt_socketResponse);
            this.sock.connect(this.request.host, this.request.port);
        }

        private function evt_socketConnect(evt:Event):void {
            // Send the request headers
            this.sock.writeUTFBytes('GET ' + this.request.uri + " HTTP/1.1\n");
            this.sock.writeUTFBytes('Host: ' + this.request.host + ':' + this.request.port + "\n\n");
        }

        private function evt_socketClose(evt:Event):void {
            // Since this is an image, just split on header \r\n and get the last member 
            var response:Array = this.request.response_parts.join('').split("\r\n");
            var body:String = response[response.length - 1];
            var loader = new Loader();
            var ba:ByteArray = new ByteArray();
            var mc:MovieClip = new MovieClip();

            this.data.writeMultiByte(body, 'utf-8');

            loader.loadBytes(this.data);
            mc.addChild(loader);

            // Pass the resulting MovieClip back to the caller
            if(typeof(this.request.callback) == 'function')
                this.request.callback.apply(this.request.root, [mc])
        }

        private function evt_socketResponse(evt:ProgressEvent):void {
            if(!this.sock.bytesAvailable)
                return;
            this.request.response_parts.push(this.sock.readUTFBytes(this.sock.bytesAvailable));
        }
    }
}

As I'm writing I'm thinking that in SocketRequest::evt_socketResponse(), the this.sock.readUTFBytes() bit should really be:

this.request.response_parts.push(this.sock.readBytes(this.data, this.data.length, evt.bytesLoaded));

and then remove the headers from this.data, but TBH I'm not super ByteArray-savvy. Does anyone have any thoughts?

+3  A: 

A couple of things look wrong with your code.

First, you're using read/writeUTF8Bytes. This is not going to work right for binary data. You need read/writeBytes instead.

Second, the header is terminated by a \r\n\r\n sequence, not \r\n.

Here's how I would do it:

private function evt_socketClose(evt:Event):void {

    _buffer.position = 0;
    var pos:int = 0;

    while(_buffer.bytesAvailable >= 4) {

        if(_buffer.readUnsignedByte() == 0xd && _buffer.readUnsignedByte() == 0xa
            && _buffer.readUnsignedByte() == 0xd && _buffer.readUnsignedByte() == 0xa) {

            var bodyBytes:ByteArray = new ByteArray();
            bodyBytes.writeBytes(_buffer,_buffer.position);
            var loader:Loader = new Loader();
            var mc:MovieClip = new MovieClip();
            loader.loadBytes(bodyBytes);
            mc.addChild(loader); 
            if(this.request.callback is Function) {
                this.request.callback.apply(this.request.root, [mc]);
            }
            // this return isn't really neccesary...
            return;
        }
    }

}

private var _buffer:ByteArray = new ByteArray();

private function evt_socketResponse(evt:ProgressEvent):void {
    if(!this.sock.bytesAvailable) {
        return;
    }
    var bytesToRead:uint = this.sock.bytesAvailable;
    this.sock.readBytes(_buffer,_buffer.position,bytesToRead);
    _buffer.position += bytesToRead;
}

Basically, when you receive data, you store it in your buffer. When data is complete, you start reading this buffer until you find the end of the header (\r\n\r\n or 0xd,0xa,0xd,0xa). At that point your buffer position is the first byte of the body / payload. So you read the buffer from that point to the end and store it in a ByteArray. Then you pass this ByteArray to Loader::loadBytes and if what you've loaded is a valid image or swf, it should be loaded.

Juan Pablo Califano
Much, MUCH clearer now. These fixes worked perfectly. I appreciate the help!
mway
@mway. No problem. I'm glad this was of help.
Juan Pablo Califano