May 29, 2012

HTMLHost class used to hack the Facebook login window

When sharing content on social networks like Facebook or Twitter from a desktop app, authentication systems require us to open new windows beyond our control and display many options, even if these are not suitable for the application you are designing.

One suitable solution could be the HTMLHost class, that controls the HTML code that is loaded into the external window and allows us to enable or disable any HTML element like links or buttons.

Facebook case

If we logging on Facebook from an AIR desktop app ( using the AS3 Facebook API ) we´ll see the login windows with several buttons that open new windows that the programmer can´t avoid. In order to disable this buttons we can follow the steps below:

  • 1. Modify the library Facebook Graph to gain access to the HTMLLoader object, who is responsible to load the login window. Yeap, we´re hacking something.
  • 2. Create the HTMLHost object and define which methods to overwrite.
  • 3. Create conditions and modify the HTML according to our app needs.

HTMLLoader and Facebook Graph

We don´t need the whole Facebook Graph source code, we could import GraphAPI_Desktop_1_8_1.swc and use only three classes related with desktop apps : FacebookDesktop, AbstractWindow and LoginWindow. It is convenient in order to avoid changes in code, for example, the old JSON functions. Inside the class AbstractWindow.as we can add public visibility to HTMLLoader object.

protected var html:HTMLLoader;

change to:

public var html:HTMLLoader;

and then in FacebookDesktop class:

import flash.html.HTMLLoader;

public static function get htmlLoader():HTMLLoader  
{
	return getInstance().loginWindow.html;
}

HTMLHost Class

The HTMLHost class allow us to modify the HTMLLoader object behavior. If we extend it, we can subscribe to the changes happening inside the “out-of’-control” login windown. There is a direct match between the method name and the property name we want to observe, for example: if we overwrite updateLocation method we´ll receive notifications related with the window.location property and to use the values that comes in the event object to take the right choice.

A piece a code could be like this:

public class FacebookHTMLHost extends HTMLHost
{						
    public function FacebookHTMLHost(  defaultBehaviors:Boolean=true )
    {
      super( defaultBehaviors );
    }
		
    override public function windowClose():void { }
		
    override public function createWindow( windowCreateOptions:HTMLWindowCreateOptions ): HTMLLoader  { return null; }
		
    override public function updateLocation( locationURL:String):void  { }   
		
    override public function set windowRect(value:Rectangle):void { }
		
    override public function updateStatus(status:String):void { }
			
    override public function updateTitle(title:String):void { }
		
    override public function windowBlur():void { }
		
    override public function windowFocus():void { }
}

To remove an HTMLHost object we should assign the htmlHost property to null.

Hacking Facebook

Now , the FacebookHTMLHost class is defined and is easy to detect the URLs of pages we could need and modify the html code when the HTMLLoader loads this pages. For example, we define:

private static var SECURE_PERMISION_URL : String = "https://www.facebook.com/dialog/permissions.request";
private static var PERMISION_URL : String = "http://www.facebook.com/dialog/permissions.request";
private static var REPORT_APP : String = "http://www.facebook.com/dialog/report.application";

and the method updateLocation could be like this:

override public function updateLocation( locationURL:String):void
{
			
    if( locationURL.indexOf( FacebookHTMLHost.PERMISION_URL 	) == 0 ||
       locationURL.indexOf( FacebookHTMLHost.SECURE_PERMISION_URL ) == 0 
      )
    {
        this.htmlLoader.addEventListener( Event.COMPLETE, disableDontAllowBtn );	
    }					
  
    if( locationURL.indexOf( "about:blank" ) == 0 )
    {

    }
}

If the URL received in updateLocation method is in our list, we can add a COMPLETE event to detect if the page has been loaded. Once all the html code is complete down, we modify any element or tag as a web pag using the window object in HTMLLoader class.

private function disableDontAllowBtn(event:Event):void
{
    var _html: HTMLLoader = event.currentTarget as HTMLLoader;

    _html.removeEventListener( Event.COMPLETE, disableDontAllowBtn );	
			
    for (var i:int = 0; i < htmlLoader.window.document.getElementsByTagName("input").length; i++) 
    {
	if( htmlLoader.window.document.getElementsByTagName("input")[i].type == "submit" )
	{
	    var button : Object = htmlLoader.window.document.getElementsByTagName("input")[i];
					
	    if( button.value == "Don't Allow" || 
		button.id    == "uh5cmv_2"    ||
		button.name  == "cancel_clicked" )
	    {
		button.disabled = true;
	    }
	}
    }
			
    for (var j:int = 0; j <  htmlLoader.window.document.getElementsByTagName("a").length; j++) 
    {
	var anchor  : Object  = htmlLoader.window.document.getElementsByTagName("a")[j];
	anchor.href = "#";
    }
			
}

Search the tags we want to modify is a task of “test and mistake”, you should parse the html content and to add rules. Besides you can´t control the login window completey because Facebook is the real owner of the content, but it is a good solution that can solve your problems for several scenarios.

Facebook could change the html content of the login window and your app will stop working, but it is a risk involved whenever working with external APIs, so the changes force developers to curse and rewrite the app again.

Bonus Track: Avoid popup windows

Avoid opening a new window is even easier. If we redefine the createWindow method corresponding to window.open and do not define any behavior, the window will not open:

override public function createWindow( windowCreateOptions:HTMLWindowCreateOptions ): HTMLLoader
{	
	return null;
}

The fact of having a class like HTMLHost gives almost as much freedom as a javascript programmer “playing” with the html.

Post a Reply

Your email address will not be published. Required fields are marked *