/*
 * File    : WebPlugin.java
 * Created : 23-Jan-2004
 * By      : parg
 * 
 * Azureus - a Java Bittorrent client
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details ( see the LICENSE file ).
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package org.gudy.azureus2.ui.webplugin;

/**
 * @author parg
 *
 */

import java.io.*;
import java.util.*;
import java.net.*;

import org.gudy.azureus2.core3.util.*;
import org.gudy.azureus2.plugins.*;
import org.gudy.azureus2.plugins.logging.*;
import org.gudy.azureus2.plugins.ipfilter.*;
import org.gudy.azureus2.plugins.tracker.*;
import org.gudy.azureus2.plugins.tracker.web.*;
import org.gudy.azureus2.plugins.ui.*;
import org.gudy.azureus2.plugins.ui.config.*;
import org.gudy.azureus2.plugins.ui.model.*;

import com.aelitis.azureus.core.pairing.PairedService;
import com.aelitis.azureus.core.pairing.PairingConnectionData;
import com.aelitis.azureus.core.pairing.PairingManager;
import com.aelitis.azureus.core.pairing.PairingManagerFactory;
import com.aelitis.azureus.core.pairing.PairingManagerListener;
import com.aelitis.azureus.core.pairing.PairingTest;
import com.aelitis.azureus.core.pairing.PairingTestListener;
import com.aelitis.azureus.plugins.upnp.UPnPMapping;
import com.aelitis.azureus.plugins.upnp.UPnPPlugin;

public class 
WebPlugin
	implements Plugin, TrackerWebPageGenerator
{
	public static final String	PR_ENABLE					= "Enable";						// Boolean
	public static final String	PR_DISABLABLE				= "Disablable";					// Boolean
	public static final String	PR_PORT						= "Port";						// Integer
	public static final String	PR_BIND_IP					= "Bind IP";					// String
	public static final String	PR_ROOT_RESOURCE			= "Root Resource";				// String
	public static final String	PR_ROOT_DIR					= "Root Dir";					// String
	public static final String	PR_ACCESS					= "Access";						// String
	public static final String	PR_LOG						= "DefaultLoggerChannel";		// LoggerChannel
	public static final String	PR_CONFIG_MODEL_PARAMS		= "DefaultConfigModelParams";	// String[] params to use when creating config model
	public static final String	PR_CONFIG_MODEL				= "DefaultConfigModel";			// BasicPluginConfigModel
	public static final String	PR_VIEW_MODEL				= "DefaultViewModel";			// BasicPluginViewModel
	public static final String	PR_HIDE_RESOURCE_CONFIG		= "DefaultHideResourceConfig";	// Boolean
	public static final String	PR_ENABLE_KEEP_ALIVE		= "DefaultEnableKeepAlive";		// Boolean
	public static final String	PR_PAIRING_SID				= "PairingSID";					// String
	
	public static final String	PROPERTIES_MIGRATED		= "Properties Migrated";
	public static final String	CONFIG_MIGRATED			= "Config Migrated";
	public static final String	PAIRING_MIGRATED		= "Pairing Migrated";
	public static final String	PAIRING_SESSION_KEY		= "Pairing Session Key";

	public static final String	CONFIG_PASSWORD_ENABLE			= "Password Enable";
	public static final boolean	CONFIG_PASSWORD_ENABLE_DEFAULT	= false;
	
	public static final String	CONFIG_PAIRING_ENABLE			= "Pairing Enable";
	public static final boolean	CONFIG_PAIRING_ENABLE_DEFAULT	= true;

	public static final String	CONFIG_PORT_OVERRIDE			= "Port Override";
	
	public static final String	CONFIG_PAIRING_AUTO_AUTH			= "Pairing Auto Auth";
	public static final boolean	CONFIG_PAIRING_AUTO_AUTH_DEFAULT	= true;

	
	public static final String	CONFIG_ENABLE					= PR_ENABLE;
	public  			boolean	CONFIG_ENABLE_DEFAULT			= true;
	
	public static final String	CONFIG_USER						= "User";
	public static final String	CONFIG_USER_DEFAULT				= "";
	
	public static final String	CONFIG_PASSWORD					= "Password";
	public static final byte[]	CONFIG_PASSWORD_DEFAULT			= {};
	
	public static final String 	CONFIG_PORT						= PR_PORT;
	public int			 		CONFIG_PORT_DEFAULT				= 8089;
	
	public static final String 	CONFIG_BIND_IP					= PR_BIND_IP;
	public String		 		CONFIG_BIND_IP_DEFAULT			= "";

	public static final String 	CONFIG_PROTOCOL					= "Protocol";
	public static final String 	CONFIG_PROTOCOL_DEFAULT			= "HTTP";

	public static final String	CONFIG_UPNP_ENABLE				= "UPnP Enable";
	public static final boolean	CONFIG_UPNP_ENABLE_DEFAULT		= true;

	public static final String 	CONFIG_HOME_PAGE				= "Home Page";
	public static final String 	CONFIG_HOME_PAGE_DEFAULT		= "index.html";
	
	public static final String 	CONFIG_ROOT_DIR					= PR_ROOT_DIR;
	public        		String 	CONFIG_ROOT_DIR_DEFAULT			= "";
	
	public static final String 	CONFIG_ROOT_RESOURCE			= PR_ROOT_RESOURCE;
	public              String 	CONFIG_ROOT_RESOURCE_DEFAULT	= "";
	
	public static final String 	CONFIG_MODE						= "Mode";
	public static final String 	CONFIG_MODE_FULL				= "full";
	public static final String 	CONFIG_MODE_DEFAULT				= CONFIG_MODE_FULL;
	
	public static final String 	CONFIG_ACCESS					= PR_ACCESS;
	public        		String 	CONFIG_ACCESS_DEFAULT			= "all";
	
	protected static final String	NL			= "\r\n";
	
	protected static final String[]		welcome_pages = {"index.html", "index.htm", "index.php", "index.tmpl" };
	protected static File[]				welcome_files;
	
	protected PluginInterface			plugin_interface;	// unfortunately this is accessed by webui - fix sometime
	
	private LoggerChannel			log;
	private PluginConfig			plugin_config;
	private BasicPluginViewModel 	view_model;
	private BasicPluginConfigModel	config_model;
	
	private StringParameter			param_home;
	private StringParameter			param_rootdir;
	private StringParameter			param_rootres;

	private IntParameter			param_port;
	private StringListParameter		param_protocol;
	private StringParameter			param_bind;
	
	private StringParameter			param_access;
	
	private BooleanParameter		p_upnp_enable;
	
	private BooleanParameter		pw_enable;
	private StringParameter			p_user_name;
	private PasswordParameter		p_password;
	
	private BooleanParameter		param_auto_auth;
	private IntParameter			param_port_or;
	private boolean					setting_auto_auth;
	private String					pairing_access_code;
	private String					pairing_session_code;
	
	private boolean				plugin_enabled;
	
	private String				home_page;
	private String				file_root;
	private String				resource_root;
	
	private String				root_dir;
	
	private boolean				ip_range_all	= false;
	private List<IPRange>		ip_ranges;
	
	private TrackerWebContext			tracker_context;
	private UPnPMapping					upnp_mapping;
	private PairingManagerListener		pairing_listener;
	
	private Properties	properties;
	
	private static ThreadLocal<String>		tls	= 
		new ThreadLocal<String>()
		{
			public String
			initialValue()
			{
				return( null );
			}
		};
	
	private static final int	LOGOUT_GRACE_MILLIS	= 5*1000;
	private static final String	GRACE_PERIOD_MARKER	= "<grace_period>";
	
	private Map<String,Long>	logout_timer 		= new HashMap<String, Long>();
	
	public 
	WebPlugin()
	{
		properties	= new Properties();
	}
	
	public 
	WebPlugin(
		Properties		defaults )
	{	
		properties	= defaults;
	}
	
	public void 
	initialize(
		PluginInterface _plugin_interface )
	
		throws PluginException
	{	
		plugin_interface	= _plugin_interface;
		
		plugin_config = plugin_interface.getPluginconfig();

		Properties plugin_properties = plugin_interface.getPluginProperties();
		
		if ( plugin_properties != null ){
			
			Object o = plugin_properties.get( "plugin." + PR_ROOT_DIR.replaceAll( " ", "_" ));
			
			if ( o instanceof String ){
				
				properties.put( PR_ROOT_DIR, o );
			}
		}
		
		Boolean	pr_enable = (Boolean)properties.get(PR_ENABLE);
		
		if ( pr_enable != null ){
		
			CONFIG_ENABLE_DEFAULT	= pr_enable.booleanValue();
		}
		
		Integer	pr_port = (Integer)properties.get(PR_PORT);
		
		if ( pr_port != null ){
		
			CONFIG_PORT_DEFAULT	= pr_port.intValue();
		}
		
		String	pr_bind_ip = (String)properties.get(PR_BIND_IP);
		
		if ( pr_bind_ip != null ){
		
			CONFIG_BIND_IP_DEFAULT	= pr_bind_ip.trim();
		}
		
		String	pr_root_resource = (String)properties.get( PR_ROOT_RESOURCE );
		
		if( pr_root_resource != null ){
			
			CONFIG_ROOT_RESOURCE_DEFAULT	= pr_root_resource;
		}
		
		String	pr_root_dir = (String)properties.get( PR_ROOT_DIR );
		
		if( pr_root_dir != null ){
			
			CONFIG_ROOT_DIR_DEFAULT	= pr_root_dir;
		}
		
		String	pr_access = (String)properties.get( PR_ACCESS );
		
		if( pr_access != null ){
			
			CONFIG_ACCESS_DEFAULT	= pr_access;
		}
		
		Boolean	pr_hide_resource_config = (Boolean)properties.get( PR_HIDE_RESOURCE_CONFIG );
		
		log = (LoggerChannel)properties.get( PR_LOG );
		
		if ( log == null ){
			
			log = plugin_interface.getLogger().getChannel("WebPlugin");
		}
		
		UIManager	ui_manager = plugin_interface.getUIManager();
		
		view_model = (BasicPluginViewModel)properties.get( PR_VIEW_MODEL );

		if ( view_model == null ){
			
			view_model = ui_manager.createBasicPluginViewModel( plugin_interface.getPluginName());
		}
		
		String sConfigSectionID = "plugins." + plugin_interface.getPluginID();
		
		view_model.setConfigSectionID(sConfigSectionID);
		view_model.getStatus().setText( "Running" );
		view_model.getActivity().setVisible( false );
		view_model.getProgress().setVisible( false );
		
		log.addListener(
			new LoggerChannelListener()
			{
				public void
				messageLogged(
					int		type,
					String	message )
				{
					view_model.getLogArea().appendText( message+"\n");
				}
				
				public void
				messageLogged(
					String		str,
					Throwable	error )
				{
					view_model.getLogArea().appendText( str + "\n" );
					view_model.getLogArea().appendText( error.toString() + "\n" );
				}
			});
		
		
		config_model = (BasicPluginConfigModel)properties.get( PR_CONFIG_MODEL );
		
		if ( config_model == null ){
			
			String[] cm_params = (String[])properties.get( PR_CONFIG_MODEL_PARAMS );
	
			if ( cm_params == null || cm_params.length == 0 ){
				
				config_model = ui_manager.createBasicPluginConfigModel(ConfigSection.SECTION_PLUGINS, sConfigSectionID);
				
			}else if ( cm_params.length == 1 ){
				
				config_model = ui_manager.createBasicPluginConfigModel( cm_params[0] );
	
			}else{
				
				config_model = ui_manager.createBasicPluginConfigModel( cm_params[0], cm_params[1] );
			}
		}
		
		boolean	save_needed = false;
		
		if ( !plugin_config.getPluginBooleanParameter( CONFIG_MIGRATED, false )){
			
			plugin_config.setPluginParameter( CONFIG_MIGRATED, true );
			
			save_needed	= true;
			
			plugin_config.setPluginParameter(
					CONFIG_PASSWORD_ENABLE,
					plugin_config.getBooleanParameter(
							"Tracker Password Enable Web", CONFIG_PASSWORD_ENABLE_DEFAULT ));
			
			plugin_config.setPluginParameter(
					CONFIG_USER,
					plugin_config.getStringParameter(
							"Tracker Username", CONFIG_USER_DEFAULT ));
			
			plugin_config.setPluginParameter(
					CONFIG_PASSWORD,
					plugin_config.getByteParameter(
							"Tracker Password", CONFIG_PASSWORD_DEFAULT ));
					
		}
		
		if ( !plugin_config.getPluginBooleanParameter( PROPERTIES_MIGRATED, false )){
			
			plugin_config.setPluginParameter( PROPERTIES_MIGRATED, true );
						
			Properties	props = plugin_interface.getPluginProperties();
			
				// make sure we've got an old properties file too
			
			if ( props.getProperty( "port", "" ).length() > 0 ){
				
				save_needed = true;
				
				String	prop_port		= props.getProperty( "port",			""+CONFIG_PORT_DEFAULT );
				String	prop_protocol	= props.getProperty( "protocol", 		CONFIG_PROTOCOL_DEFAULT );
				String	prop_home		= props.getProperty( "homepage", 		CONFIG_HOME_PAGE_DEFAULT );
				String	prop_rootdir	= props.getProperty( "rootdir", 		CONFIG_ROOT_DIR_DEFAULT );
				String	prop_rootres	= props.getProperty( "rootresource", 	CONFIG_ROOT_RESOURCE_DEFAULT );
				String	prop_mode		= props.getProperty( "mode", 			CONFIG_MODE_DEFAULT );
				String	prop_access		= props.getProperty( "access", 			CONFIG_ACCESS_DEFAULT );
	
				int	prop_port_int = CONFIG_PORT_DEFAULT;
				
				try{
					prop_port_int	= Integer.parseInt( prop_port );
					
				}catch( Throwable e ){
				}
		
				plugin_config.setPluginParameter(CONFIG_PORT, prop_port_int );
				plugin_config.setPluginParameter(CONFIG_PROTOCOL, prop_protocol );
				plugin_config.setPluginParameter(CONFIG_HOME_PAGE, prop_home );
				plugin_config.setPluginParameter(CONFIG_ROOT_DIR, prop_rootdir );
				plugin_config.setPluginParameter(CONFIG_ROOT_RESOURCE, prop_rootres );
				plugin_config.setPluginParameter(CONFIG_MODE, prop_mode );
				plugin_config.setPluginParameter(CONFIG_ACCESS, prop_access );
								
				File	props_file = new File( plugin_interface.getPluginDirectoryName(), "plugin.properties" );
				
				PrintWriter pw = null;
				
				try{
					File	backup = new File( plugin_interface.getPluginDirectoryName(), "plugin.properties.bak" );
					
					props_file.renameTo( backup );
					
					pw = new PrintWriter( new FileWriter( props_file ));
	
					pw.println( "plugin.class=" + props.getProperty( "plugin.class" ));
					pw.println( "plugin.name=" + props.getProperty( "plugin.name" ));
					pw.println( "plugin.version=" + props.getProperty( "plugin.version" ));
					pw.println( "plugin.id=" + props.getProperty( "plugin.id" ));
					pw.println( "" );
					pw.println( "# configuration has been migrated to plugin config - see view->config->plugins" );
					pw.println( "# in the SWT user interface" );
					
					log.logAlert( 	LoggerChannel.LT_INFORMATION, 
							plugin_interface.getPluginName() + " - plugin.properties settings migrated to plugin configuration." );
			
				}catch( Throwable  e ){
					
					Debug.printStackTrace( e );
					
					log.logAlert( 	LoggerChannel.LT_ERROR, 
									plugin_interface.getPluginName() + " - plugin.properties settings migration failed." );
					
				}finally{
					
					if ( pw != null ){
						
						pw.close();
					}
				}	
			}
		}
		
		if ( save_needed ){
			
			plugin_config.save();
		}
		
		Boolean	disablable = (Boolean)properties.get( PR_DISABLABLE );
				
		final BooleanParameter	param_enable;
		
		if ( disablable != null && disablable ){
			
			param_enable = 
				config_model.addBooleanParameter2( CONFIG_ENABLE, "webui.enable", CONFIG_ENABLE_DEFAULT );
			
			plugin_enabled = param_enable.getValue();
			
		}else{
			param_enable 	= null;
			
			plugin_enabled 	= true;
		}
		
			// connection group
		
		param_port = config_model.addIntParameter2(		CONFIG_PORT, "webui.port", CONFIG_PORT_DEFAULT );
		
		param_bind = config_model.addStringParameter2(	CONFIG_BIND_IP, "webui.bindip", CONFIG_BIND_IP_DEFAULT );
		
		param_protocol = 
			config_model.addStringListParameter2(
					CONFIG_PROTOCOL, "webui.protocol", new String[]{ "http", "https" }, CONFIG_PROTOCOL_DEFAULT );
		
		ParameterListener update_server_listener = 
			new ParameterListener()
			{
				public void 
				parameterChanged(
					Parameter param ) 
				{
					setupServer();
				}
			};
		
		param_port.addListener( update_server_listener );
		param_bind.addListener( update_server_listener );
		param_protocol.addListener( update_server_listener );		
		
		p_upnp_enable = 
			config_model.addBooleanParameter2( 
							CONFIG_UPNP_ENABLE, 
							"webui.upnpenable",
							CONFIG_UPNP_ENABLE_DEFAULT );

		p_upnp_enable.addListener(
			new ParameterListener()
			{
				public void 
				parameterChanged(
					Parameter param ) 
				{
					setupUPnP();
				}
			});	
		
		plugin_interface.addListener(
				new PluginListener()
				{
					public void
					initializationComplete()
					{
						setupUPnP();
					}
					
					public void
					closedownInitiated()
					{
					}
					
					public void
					closedownComplete()
					{	
					}
				});
		
		
		final String p_sid = (String)properties.get( PR_PAIRING_SID );
		
		final LabelParameter		pairing_info;
		final BooleanParameter		pairing_enable;
		final HyperlinkParameter	pairing_test;
		final HyperlinkParameter	connection_test;
		
		if ( p_sid != null ){
			
			final PairingManager pm = PairingManagerFactory.getSingleton();

			pairing_info = config_model.addLabelParameter2( "webui.pairing.info." + (pm.isEnabled()?"y":"n"));
				
			pairing_enable = config_model.addBooleanParameter2( CONFIG_PAIRING_ENABLE, "webui.pairingenable", CONFIG_PAIRING_ENABLE_DEFAULT );
			
			if ( !plugin_config.getPluginBooleanParameter( PAIRING_MIGRATED, false )){
			
					// if they already have a password, don't override it by setting auto-auth
				
				boolean	has_pw_enabled = plugin_config.getPluginBooleanParameter( CONFIG_PASSWORD_ENABLE, CONFIG_PASSWORD_ENABLE_DEFAULT );
				
				if ( has_pw_enabled ){
					
					plugin_config.setPluginParameter( CONFIG_PAIRING_AUTO_AUTH, false );
				}
				
				plugin_config.setPluginParameter( PAIRING_MIGRATED, true );
			}
			
			param_port_or	=  config_model.addIntParameter2( CONFIG_PORT_OVERRIDE, "webui.port.override", 0 );

			param_auto_auth = config_model.addBooleanParameter2( CONFIG_PAIRING_AUTO_AUTH, "webui.pairing.autoauth", CONFIG_PAIRING_AUTO_AUTH_DEFAULT );
			
			param_auto_auth.addListener(
				new ParameterListener()
				{
					public void 
					parameterChanged(
						Parameter param ) 
					{
						if ( pairing_enable.getValue() && pm.isEnabled()){
					
							setupAutoAuth();
						}
					}
				});
						
			connection_test = config_model.addHyperlinkParameter2( "webui.connectiontest", getConnectionTestURL( p_sid ));

			pairing_test = config_model.addHyperlinkParameter2( "webui.pairingtest", "http://remote.vuze.com/?sid=" + p_sid );

				// listeners setup later as they depend on userame params etc
			
		}else{
			pairing_info	= null;
			pairing_enable 	= null;
			param_auto_auth	= null;
			param_port_or	= null;
			pairing_test	= null;
			connection_test	= null;
		}
			
		config_model.createGroup(
				"ConfigView.section.Pairing",
				new Parameter[]{
					pairing_info, pairing_enable, param_port_or, param_auto_auth, connection_test, pairing_test,
				});
		
		config_model.createGroup(
			"ConfigView.section.server",
			new Parameter[]{
				param_port, param_bind, param_protocol, p_upnp_enable,
			});
		
		param_home 		= config_model.addStringParameter2(	CONFIG_HOME_PAGE, "webui.homepage", CONFIG_HOME_PAGE_DEFAULT );
		param_rootdir 	= config_model.addStringParameter2(	CONFIG_ROOT_DIR, "webui.rootdir", CONFIG_ROOT_DIR_DEFAULT );
		param_rootres	= config_model.addStringParameter2(	CONFIG_ROOT_RESOURCE, "webui.rootres", CONFIG_ROOT_RESOURCE_DEFAULT );
		
		if ( pr_hide_resource_config != null && pr_hide_resource_config.booleanValue()){
			
			param_home.setVisible( false );
			param_rootdir.setVisible( false );
			param_rootres.setVisible( false );
			
		}else{
			
			ParameterListener update_resources_listener = 
				new ParameterListener()
				{
					public void 
					parameterChanged(
						Parameter param ) 
					{
						setupResources();
					}
				};
				
			param_home.addListener( update_resources_listener );
			param_rootdir.addListener( update_resources_listener );
			param_rootres.addListener( update_resources_listener );
		}
		
			// access group
		
		LabelParameter a_label1 = config_model.addLabelParameter2( "webui.mode.info" ); 
		StringListParameter param_mode = 
			config_model.addStringListParameter2(	
					CONFIG_MODE, "webui.mode", new String[]{ "full", "view" }, CONFIG_MODE_DEFAULT );
		
		
		LabelParameter a_label2 = config_model.addLabelParameter2( "webui.access.info" );
		
		param_access	= config_model.addStringParameter2(	CONFIG_ACCESS, "webui.access", CONFIG_ACCESS_DEFAULT );
		
		param_access.addListener(
			new ParameterListener()
			{
				public void 
				parameterChanged(
					Parameter param ) 
				{
					setupAccess();
				}		
			});
		
		pw_enable = 
			config_model.addBooleanParameter2( 
							CONFIG_PASSWORD_ENABLE, 
							"webui.passwordenable",
							CONFIG_PASSWORD_ENABLE_DEFAULT );
		
		p_user_name = 
			config_model.addStringParameter2( 
							CONFIG_USER, 
							"webui.user",
							CONFIG_USER_DEFAULT );
		
		p_password = 
			config_model.addPasswordParameter2( 
							CONFIG_PASSWORD, 
							"webui.password",
							PasswordParameter.ET_SHA1,
							CONFIG_PASSWORD_DEFAULT );
		
		pw_enable.addEnabledOnSelection( p_user_name );
		pw_enable.addEnabledOnSelection( p_password );
		
		ParameterListener auth_change_listener = 
			new ParameterListener()
			{
				public void 
				parameterChanged(
					Parameter param ) 
				{
					if ( param_auto_auth != null ){
						
						if ( !setting_auto_auth ){
							
							log( "Disabling pairing auto-authentication as overridden by user" );
							
							param_auto_auth.setValue( false );
						}
					}
				}
			};

		p_user_name.addListener( auth_change_listener );
		p_password.addListener( auth_change_listener );
		pw_enable.addListener( auth_change_listener );
			
		config_model.createGroup(
			"webui.group.access",
			new Parameter[]{
				a_label1, param_mode, a_label2, param_access,
				pw_enable, p_user_name, p_password,
			});
			    
		if ( p_sid != null ){
			
			final PairingManager pm = PairingManagerFactory.getSingleton();

			pairing_enable.addListener(
					new ParameterListener()
					{
						public void 
						parameterChanged(
							Parameter param ) 
						{
							boolean enabled = pairing_enable.getValue();
							
							param_auto_auth.setEnabled( pm.isEnabled() && enabled );
							param_port_or.setEnabled( pm.isEnabled() && enabled );
							
							boolean test_ok = pm.isEnabled() && pairing_enable.getValue() && pm.peekAccessCode() != null && !pm.hasActionOutstanding();

							pairing_test.setEnabled( test_ok );
							connection_test.setEnabled( test_ok );

							setupPairing( p_sid, enabled );
						}
					});
							
			pairing_listener = 
				new PairingManagerListener()
				{
					public void 
					somethingChanged(
						PairingManager pm ) 
					{
						pairing_info.setLabelKey( "webui.pairing.info." + (pm.isEnabled()?"y":"n"));
	
						if ( plugin_enabled ){
							
							pairing_enable.setEnabled( pm.isEnabled());
								
							param_auto_auth.setEnabled( pm.isEnabled() && pairing_enable.getValue() );
							param_port_or.setEnabled( pm.isEnabled() && pairing_enable.getValue() );
							
							boolean test_ok = pm.isEnabled() && pairing_enable.getValue() && pm.peekAccessCode() != null && !pm.hasActionOutstanding();
							
							pairing_test.setEnabled( test_ok );
							connection_test.setEnabled( test_ok );
						}
						
						connection_test.setHyperlink( getConnectionTestURL( p_sid ));
								  				
						setupPairing( p_sid, pairing_enable.getValue());
					}
				};
				
			pairing_listener.somethingChanged( pm );
			
			pm.addListener( pairing_listener );

			setupPairing( p_sid, pairing_enable.getValue());
			
			ParameterListener update_pairing_listener = 
				new ParameterListener()
				{
					public void 
					parameterChanged(
						Parameter param ) 
					{
						updatePairing( p_sid );
						
						setupUPnP();
					}
				};
				
			param_port.addListener( update_pairing_listener );
			
			param_port_or.addListener( update_pairing_listener );
			
			param_protocol.addListener( update_pairing_listener );
			
			/*
			config_model.addActionParameter2( "test", "test" ).addListener(
				new ParameterListener()
				{
					public void 
					parameterChanged(
						Parameter param )
					{
						try{
							pm.testService( 
								p_sid,
								new PairingTestListener()
								{
									public void 
									testStarted(
										PairingTest test )
									{
										System.out.println( "Test starts" );
									}
									
									public void 
									testComplete(
										PairingTest test) 
									{
										System.out.println( "Test complete: " + test.getOutcome() + "/" + test.getErrorMessage());
									}
								});
						}catch( Throwable e ){
							
							Debug.out( e );
						}
					}
				});
			*/
		}
		
		if ( param_enable != null ){
						
			final List<Parameter> changed_params = new ArrayList<Parameter>();
			
			if ( !plugin_enabled){
				
				Parameter[] params = config_model.getParameters();

				for ( Parameter param: params ){
					
					if ( param == param_enable ){
						
						continue;
					}
					
					if ( param.isEnabled()){
						
						changed_params.add( param );
					
						param.setEnabled( false );
					}
				}
			}
			
			param_enable.addListener(
				new ParameterListener()
				{
					public void 
					parameterChanged(
						Parameter e_p )
					{
							// this doesn't quite work as tne enabler/disabler parameter logic is implemented
							// badly and only toggles the UI component, not the enabled state of the
							// underlying parameter. grr. better than nothing though
						
						plugin_enabled = ((BooleanParameter)e_p).getValue();
						
						if ( plugin_enabled ){
						
							for ( Parameter p: changed_params ){
								
								p.setEnabled( true );
							}
						}else{
							
							changed_params.clear();
							
							Parameter[] params = config_model.getParameters();
							
							for ( Parameter param: params ){
								
								if ( param == e_p ){
									
									continue;
								}
								
								if ( param.isEnabled()){
									
									changed_params.add( param );
								
									param.setEnabled( false );
								}
							}
						}
						
						setupServer();
						
						setupUPnP();
						
						if ( p_sid != null ){
						
							setupPairing( p_sid, pairing_enable.getValue());
						}
					}
				});
		}
		
			// end config
				
		setupResources();
			
		setupAccess();
												
		setupServer();
	}
	
	private String
	getConnectionTestURL(
		String		sid )
	{
		String res = "http://pair.vuze.com/pairing/web/test?sid=" + sid;
		
		PairingManager pm = PairingManagerFactory.getSingleton();
		
		if ( pm.isEnabled()){
			
			String ac = pm.peekAccessCode();
			
			if ( ac != null ){
				
				res += "&ac=" + ac;
			}
		}
		
		return( res );
	}
	
	protected void
	unloadPlugin()
	{
		if ( view_model != null ){
		
			view_model.destroy();
		
			view_model = null;
		}
		
		if ( config_model != null ){
			
			config_model.destroy();
			
			config_model = null;
		}
	
		if ( tracker_context != null ){
			
			tracker_context.destroy();
			
			tracker_context = null;
		}
		
		if ( upnp_mapping != null ){
			
			upnp_mapping.destroy();
			
			upnp_mapping = null;
		}
		
		if ( pairing_listener != null ){
		
			PairingManager pm = PairingManagerFactory.getSingleton();
			
			pm.removeListener( pairing_listener );
			
			pairing_listener = null;
		}
	}
	
	private void
	setupResources()
	{
		home_page = param_home.getValue().trim();
		
		if ( home_page.length() == 0 ){
				
			home_page = null;
				
		}else if (!home_page.startsWith("/" )){
			
			home_page = "/" + home_page;
		}
		
		resource_root = param_rootres.getValue().trim();
				
		if ( resource_root.length() == 0 ){
				
			resource_root = null;
				
		}else if ( resource_root.startsWith("/" )){
			
			resource_root = resource_root.substring(1);
		}
		
		root_dir	= param_rootdir.getValue().trim();
		
		if ( root_dir.length() == 0 ){
			
			file_root = plugin_interface.getPluginDirectoryName();
			
			if ( file_root == null ){
				
				file_root = SystemProperties.getUserPath() + "web";
			}
		}else{
			
				// absolute or relative
			
			if ( root_dir.startsWith(File.separator) || root_dir.indexOf(":") != -1 ){
				
				file_root = root_dir;
				
			}else{
				
				if ( File.separatorChar != '/' && root_dir.contains( "/" )){
					
					root_dir = root_dir.replace( '/', File.separatorChar );
				}
				
					// try relative to plugin dir
				
				file_root = plugin_interface.getPluginDirectoryName();

				if ( file_root != null ){
					
					file_root = file_root + File.separator + root_dir;
					
					if ( !new File(file_root).exists()){
						
						file_root = null;
					}
				}
				
				if ( file_root == null ){
					
					file_root = SystemProperties.getUserPath() + "web" + File.separator + root_dir;
				}
			}
		}

		File	f_root = new File( file_root );
		
		if ( !f_root.exists()){
	
			String	error = "WebPlugin: root dir '" + file_root + "' doesn't exist";
			
			log.log( LoggerChannel.LT_ERROR, error );
			
		}else if ( !f_root.isDirectory()){
			
			String	error = "WebPlugin: root dir '" + file_root + "' isn't a directory";
			
			log.log( LoggerChannel.LT_ERROR, error );
		}
		
		welcome_files = new File[welcome_pages.length];
		
		for (int i=0;i<welcome_pages.length;i++){
			
			welcome_files[i] = new File( file_root + File.separator + welcome_pages[i] );
		}
	}
	
	private void
	setupAccess()
	{
		String	access_str = param_access.getValue().trim();
		
		String ip_ranges_str = "";
		
		ip_ranges 		= null;
		ip_range_all	= false;
		
		if ( access_str.length() > 7 && Character.isDigit(access_str.charAt(0))){
			
			String[] ranges = access_str.replace( ';', ',' ).split( "," );
			
			ip_ranges = new ArrayList<IPRange>();
			
			for ( String range: ranges ){
				
				range = range.trim();
				
				if ( range.length() > 7 ){
					
					IPRange ip_range	= plugin_interface.getIPFilter().createRange(true);
			
					int	sep = range.indexOf("-");
				
					if ( sep == -1 ){
				
						ip_range.setStartIP( range );
				
						ip_range.setEndIP( range );
				
					}else{				
				
						ip_range.setStartIP( range.substring(0,sep).trim());
				
						ip_range.setEndIP( range.substring( sep+1 ).trim());
					}
			
					ip_range.checkValid();
			
					if (!ip_range.isValid()){
			
						log.log( LoggerChannel.LT_ERROR, "Access parameter '" + range + "' is invalid" );
						
					}else{
						
						ip_ranges.add( ip_range );
						
						ip_ranges_str += (ip_ranges_str.length()==0?"":", ") + ip_range.getStartIP() + " - " + ip_range.getEndIP();
					}
				}
			}
			
			if ( ip_ranges.size() == 0 ){
				
				ip_ranges = null;
			}
		}else{
			
			if ( access_str.equalsIgnoreCase( "all" ) || access_str.length() == 0 ){
								
				ip_range_all	= true;				
			}
		}
		
		log.log( 	LoggerChannel.LT_INFORMATION, 
				"Acceptable IP range = " +
					( ip_ranges==null?
						(ip_range_all?"all":"local"):
						(ip_ranges_str)));
	}
	
	protected void
	setupServer()
	{
		try{
			if ( !plugin_enabled ){
				
				if ( tracker_context != null ){
					
					tracker_context.destroy();
					
					tracker_context = null;
				}
				
				return;
			}
			
			final int port	= param_port.getValue();

			String	protocol_str = param_protocol.getValue().trim();
			
			String bind_ip_str = param_bind.getValue().trim();
			
			InetAddress	bind_ip = null;
			
			if ( bind_ip_str.length() > 0 ){
				
				try{
					bind_ip = InetAddress.getByName( bind_ip_str );
					
				}catch( Throwable  e ){
					
					log.log( LoggerChannel.LT_ERROR, "Bind IP parameter '" + bind_ip_str + "' is invalid" );
		
				}
			}
			
			if ( tracker_context != null ){
				
				URL	url = tracker_context.getURLs()[0];
				
				String		existing_protocol 	= url.getProtocol();
				int			existing_port		= url.getPort()==-1?url.getDefaultPort():url.getPort();
				InetAddress existing_bind_ip 	= tracker_context.getBindIP();
				
				if ( 	existing_port == port &&
						existing_protocol.equalsIgnoreCase( protocol_str ) &&
						sameAddress( bind_ip, existing_bind_ip )){
					
					return;
				}
				
				tracker_context.destroy();
				
				tracker_context = null;
			}

			

			int	protocol = protocol_str.equalsIgnoreCase( "HTTP")?Tracker.PR_HTTP:Tracker.PR_HTTPS;
		
			log.log( 	LoggerChannel.LT_INFORMATION, 
						"Server initialisation: port = " + port +
						(bind_ip == null?"":(", bind = " + bind_ip_str + ")")) +
						", protocol = " + protocol_str + (root_dir.length()==0?"":(", root = " + root_dir )));

			tracker_context = 
				plugin_interface.getTracker().createWebContext(
						Constants.APP_NAME + " - " + plugin_interface.getPluginName(), 
						port, protocol, bind_ip );
		
			Boolean	pr_enable_keep_alive = (Boolean)properties.get( PR_ENABLE_KEEP_ALIVE );

			if ( pr_enable_keep_alive != null && pr_enable_keep_alive ){
			
				tracker_context.setEnableKeepAlive( true );
			}
			
			tracker_context.addPageGenerator( this );
	
			tracker_context.addAuthenticationListener(
				new TrackerAuthenticationAdapter()
				{
					private String	last_pw		= "";
					private byte[]	last_hash	= {};
					
					private final int DELAY = 10*1000;
					
					private Map<String,Object[]>	fail_map = new HashMap<String, Object[]>();
					
					public boolean
					authenticate(
						String		headers,
						URL			resource,
						String		user,
						String		pw )
					{
						long	now = SystemTime.getMonotonousTime();

						String	client_address = getHeaderField( headers, "X-Real-IP" );
						
						if ( client_address == null ){
							
							client_address = "<unknown>";
						}

						synchronized( logout_timer ){
							
							Long logout_time = logout_timer.get( client_address );
							
							if ( logout_time != null && now - logout_time <= LOGOUT_GRACE_MILLIS ){
																
								tls.set( GRACE_PERIOD_MARKER );
								
								return( true );
							}
						}
						
						boolean	result = authenticateSupport( headers, resource, user, pw );
																				
						if ( !result ){
							
							AESemaphore waiter = null;
							
							synchronized( fail_map ){

								
								Object[] x = fail_map.get( client_address );
							
								if ( x == null ){
									
									x = new Object[]{ new AESemaphore( "af:waiter" ), new Long(-1), new Long(-1), now };
									
									fail_map.put( client_address, x );
									
								}else{
									
									x[1] = x[2];
									x[2] = x[3];
									x[3] = now;
									
									long t = (Long)x[1];
									
									if ( now - t < 10*1000 ){
										
										log( "Too many recent authentication failures from '" + client_address + "' - rate limiting" );

										x[2] = now+DELAY;
										
										waiter = (AESemaphore)x[0];
									}
								}
							}
							
							if ( waiter != null ){
								
								waiter.reserve( DELAY );
							}
						}
						
						return( result );
					}
					
					private boolean
					authenticateSupport(
						String		headers,
						URL			resource,
						String		user,
						String		pw )
					{
						boolean	result;
						
						boolean	auto_auth =  param_auto_auth != null && param_auto_auth.getValue();

						if ( !pw_enable.getValue()){
														
							result = true;
							
						}else{
													
							if ( auto_auth ){
								
								user = user.trim().toLowerCase();
							}
							
							if ( !user.equals( p_user_name.getValue())){
								
								result = false;
								
							}else{
							
								byte[]	hash = last_hash;
								
								if (  !last_pw.equals( pw )){
																
									hash = plugin_interface.getUtilities().getSecurityManager().calculateSHA1( 
											auto_auth?pw.toUpperCase().getBytes():pw.getBytes());
									
									last_pw		= pw;
									last_hash	= hash;
								}
								
								result = Arrays.equals( hash, p_password.getValue());
							}
						}
						
						if ( result ){
						
								// user name and password match, see if we've come from the pairing process 
							
							checkCookieSet( headers, resource );
							
						}else if ( auto_auth  ){
							
								// either the ac is in the url, referer or we have a cookie set
														
							int x = checkCookieSet( headers, resource );
							
							if ( x == 1 ){
								
								result = true;
								
							}else if ( x == 0 ){
														
								result = hasOurCookie( getHeaderField( headers, "Cookie" ));
							}
						}
						
						return( result );
					}
					
						/**
						 * 
						 * @param headers
						 * @param resource
						 * @return 0 = unknown, 1 = ok, 2 = bad
						 */
					
					private int
					checkCookieSet(
						String		headers,
						URL			resource )
					{
						if ( pairing_access_code == null ){
						
							return( 2 );
						}
						
						String[]	locations = { resource.getQuery(), getHeaderField( headers, "Referer" )};
						
						for ( String location: locations ){
						
							if ( location != null ){
								
								int p1 = location.indexOf( "vuze_pairing_ac=" );
								
								if ( p1 != -1 ){
									
									int p2 = location.indexOf( '&', p1 );
									
									String ac = location.substring( p1+16, p2==-1?location.length():p2 ).trim();
									
									p2 = ac.indexOf( '#' );
									
									if ( p2 != -1 ){
										
										ac = ac.substring( 0, p2 );
									}
																
									if ( ac.equalsIgnoreCase( pairing_access_code )){
								
										tls.set( pairing_session_code );
										
										return( 1 );
										
									}else{
										
										return( 2 );
									}
								}
							}
						}
						
						return( 0 );
					}
					
					private String
					getHeaderField(
						String	headers,
						String	field )
					{
						String lc_headers = headers.toLowerCase();
						
						int p1 = lc_headers.indexOf( field.toLowerCase() + ":" );
						
						if ( p1 != -1 ){
						
							int	p2 = lc_headers.indexOf( '\n', p1 );
							
							if ( p2 != -1 ){
								
								return( headers.substring( p1+field.length()+1, p2 ).trim());
							}
						}
						
						return( null );
					}
				});
			
		}catch( TrackerException e ){
			
			log.log( "Server initialisation failed", e );
		}
	}
	
	private boolean
	hasOurCookie(
		String		cookies )
	{
		if ( cookies == null ){
			
			return( false );
		}
		
		String[] cookie_list = cookies.split( ";" );
		
		for ( String cookie: cookie_list ){
			
			String[] bits = cookie.split( "=" );
			
			if ( bits.length == 2 ){
				
				if ( bits[0].trim().equals( "vuze_pairing_sc" )){
					
					if ( bits[1].trim().equals( pairing_session_code )){
						
						return( true );
					}
				}
			}
		}
		
		return( false );
	}
	
	private boolean
	sameAddress(
		InetAddress	a1,
		InetAddress a2 )
	{
		if ( a1 == null && a2 == null ){
			
			return( true );
			
		}else if ( a1 == null || a2 == null ){
			
			return( false );
			
		}else{
			
			return( a1.equals( a2 ));
		}
	}
	
	protected void
	setupUPnP()
	{
		if ( !plugin_enabled  || !p_upnp_enable.getValue()){
			
			if ( upnp_mapping != null ){
				
				log( "Removing UPnP mapping" );
				
				upnp_mapping.destroy();
				
				upnp_mapping = null;
			}
			
			return;
		}
		
		PluginInterface pi_upnp = plugin_interface.getPluginManager().getPluginInterfaceByClass( UPnPPlugin.class );
		
		if ( pi_upnp == null ){
			
			log.log( "No UPnP plugin available, not attempting port mapping");
			
		}else{
			
			int port = param_port.getValue();
			
			if ( upnp_mapping != null ){
				
				if ( upnp_mapping.getPort() == port ){
					
					return;
				}
				
				log( "Updating UPnP mapping" );
				
				upnp_mapping.destroy();
				
			}else{
				
				log( "Creating UPnP mapping" );
			}
						
			upnp_mapping = ((UPnPPlugin)pi_upnp.getPlugin()).addMapping( plugin_interface.getPluginName(), true, port, true );
		}
	}
	
	protected void
	setupPairing(
		String		sid,
		boolean		pairing_enabled )
	{
		PairingManager pm = PairingManagerFactory.getSingleton();
		
		PairedService service = pm.getService( sid );
		
		if ( plugin_enabled && pairing_enabled && pm.isEnabled()){
			
			setupAutoAuth();
			
			if ( service == null ){
				
				log( "Adding pairing service" );
				
				service =  pm.addService( sid ); 
				
				PairingConnectionData cd = service.getConnectionData();

				try{					
					updatePairing( cd );
				
				}finally{
				
					cd.sync();
				}
			}
		}else{
			
			pairing_access_code 	= null;
			pairing_session_code	= null;
			
			if ( service != null ){
				
				log( "Removing pairing service" );
				
				service.remove();
			}
		}
	}
		
	protected void
	setupAutoAuth()
	{
		PairingManager pm = PairingManagerFactory.getSingleton();

		String ac = pm.peekAccessCode();
		
		if ( ac != null && ( pairing_access_code == null || !ac.equals( pairing_access_code ))){
			
			synchronized( this ){
			
				String existing_key = plugin_config.getPluginStringParameter( PAIRING_SESSION_KEY, "" );
			
				String[]	bits = existing_key.split( "=" );
				
				if ( bits.length == 2 && bits[0].equals( ac )){
					
					pairing_session_code = bits[1];
					
				}else{
			
					pairing_session_code = Base32.encode( RandomUtils.nextSecureHash());
					
					plugin_config.setPluginParameter( PAIRING_SESSION_KEY, ac + "=" + pairing_session_code );
				}
			}
		}
		
		pairing_access_code = ac;
		
			// good time to check the default pairing auth settings
		
		if ( pairing_access_code != null ){
			
			if ( param_auto_auth.getValue()){
				
				try{
					setting_auto_auth = true;
					
					if ( !p_user_name.getValue().equals( "vuze" )){
						
						p_user_name.setValue( "vuze" );
					}
					
			        SHA1Hasher hasher = new SHA1Hasher();
			        
			        byte[] encoded = hasher.calculateHash( pairing_access_code.getBytes());
			        
					if ( !Arrays.equals( p_password.getValue(), encoded )){
						
						p_password.setValue( pairing_access_code );
					}
					
					if ( !pw_enable.getValue()){
						
						pw_enable.setValue( true );
					}
				}finally{
					
					setting_auto_auth = false;
				}
			}
		}
	}
	protected void
	updatePairing(
		String		sid )
	{
		PairingManager pm = PairingManagerFactory.getSingleton();
		
		PairedService service = pm.getService( sid );
		
		if ( service != null ){
			
			PairingConnectionData cd = service.getConnectionData();
			
			log( "Updating pairing information" );
			
			try{
				updatePairing( cd );
				
			}finally{
				
				cd.sync();
			}
		}
	}

	protected void
	updatePairing(
		PairingConnectionData		cd )
	{
		cd.setAttribute( PairingConnectionData.ATTR_PORT, 		String.valueOf( param_port.getValue()));
		
		int	override = param_port_or==null?0:param_port_or.getValue();
		
		if ( override > 0 ){
			
			cd.setAttribute( PairingConnectionData.ATTR_PORT_OVERRIDE, 	String.valueOf( override ));
			
		}else{
			
			cd.setAttribute( PairingConnectionData.ATTR_PORT_OVERRIDE, null );
		}
		
		cd.setAttribute( PairingConnectionData.ATTR_PROTOCOL, 	param_protocol.getValue());
	}

	protected int
	getPort()
	{
		return( param_port.getValue());
	}
	
	protected String
	getProtocol()
	{
		return( param_protocol.getValue());
	}
	
	public void
	setUserAndPassword(
		String		user,
		String		password )
	{
		p_user_name.setValue( user );
		p_password.setValue( password );
		pw_enable.setValue( true );
	}
	
	public void
	unsetUserAndPassword()
	{
		pw_enable.setValue( false );
	}
			
	public boolean
	generateSupport(
		TrackerWebPageRequest		request,
		TrackerWebPageResponse		response )
	
		throws IOException
	{
		return( false );
	}
	
	public boolean
	generate(
		TrackerWebPageRequest		request,
		TrackerWebPageResponse		response )
	
		throws IOException
	{
		// System.out.println( request.getURL());
		
		String	client = request.getClientAddress();

		if ( !ip_range_all ){
					
			// System.out.println( "client = " + client );
			
			try{
				boolean valid_ip = true;
				InetAddress ia = InetAddress.getByName( client );
				
				if ( ip_ranges == null ){
					
					if ( !ia.isLoopbackAddress()){
				
						log.log( LoggerChannel.LT_ERROR, "Client '" + client + "' is not local, rejecting" );
						valid_ip = false;
					}
				}else{
					
					boolean ok = false;
					
					for ( IPRange range: ip_ranges ){
						
						if ( range.isInRange( ia.getHostAddress())){
						
							ok = true;
						}
					}
					
					if ( !ok ){
						
						log.log( LoggerChannel.LT_ERROR, "Client '" + client + "' (" + ia.getHostAddress() + ") is not in range, rejecting" );
						valid_ip = false;
					}
				}
				
				if ( !valid_ip ){
					
					response.setReplyStatus( 403 );
					
					return( returnTextPlain( response, "Cannot access resource from this IP address." ));
				}
				
			}catch( Throwable e ){
				
				Debug.printStackTrace( e );
				
				return( false );
			}
		}
		
		String url = request.getURL();
		
	
		if ( url.toString().endsWith(".class")){
			
			System.out.println( "WebPlugin::generate:" + url );
		}
	
		String	cookie_to_set = tls.get();
		
		if ( cookie_to_set == GRACE_PERIOD_MARKER ){
			
			return( returnTextPlain( response, "Logout in progress, please try again later." ));
		}
		
		if ( cookie_to_set != null ){
			
				// set session cookie
		
			response.setHeader( "Set-Cookie", "vuze_pairing_sc=" + cookie_to_set + "; HttpOnly" );
			
			tls.set( null );
		}
		
		URL full_url = request.getAbsoluteURL();
		
		String	full_url_path = full_url.getPath();
		
		if ( full_url_path.equals( "/isPairedServiceAvailable" )){
			
			String redirect = getArgumentFromURL( full_url, "redirect_to" );
			
			if ( redirect != null ){
				
				try{
					URL target = new URL( redirect );
					
					String	host = target.getHost();
					
					if ( !Constants.isAzureusDomain( host )){
					
						if ( !InetAddress.getByName(host).isLoopbackAddress()){
							
							log( "Invalid redirect host: " + host );
							
							redirect = null;
						}
					}
				}catch( Throwable e ){
					
					Debug.out( e );
					
					redirect = null;
				}
			}
			
			if ( redirect != null ){
								
				response.setReplyStatus( 302 );
				
				response.setHeader( "Location", redirect );
				
				return( true );
			}
			
			String callback = getArgumentFromURL( full_url, "jsoncallback" );
				
			if ( callback != null ){
					
				return( returnTextPlain( response,  callback + "( {'pairedserviceavailable':true} )"));
			}
		}else if ( full_url_path.equals( "/isServicePaired" )){
			
			boolean paired = cookie_to_set != null || hasOurCookie((String)request.getHeaders().get( "cookie" ));
			
			return( returnJSON( response, "{ 'servicepaired': " + ( paired?"true":"false" ) + " }" ));
			
		}else if ( full_url_path.equals( "/pairedServiceLogout")){
			
			synchronized( logout_timer ){
				
				logout_timer.put( client, SystemTime.getMonotonousTime());
			}
			
			response.setHeader( "Set-Cookie", "vuze_pairing_sc=<deleted>, expires=" + TimeFormatter.getCookieDate(0));
			
			String redirect = getArgumentFromURL( full_url, "redirect_to" );
			
			if ( redirect != null ){
				
				try{
					URL target = new URL( redirect );
					
					String	host = target.getHost();
					
					if ( !Constants.isAzureusDomain( host )){
					
						if ( !InetAddress.getByName(host).isLoopbackAddress()){
							
							log( "Invalid redirect host: " + host );
							
							redirect = null;
						}
					}
				}catch( Throwable e ){
					
					Debug.out( e );
					
					redirect = null;
				}
			}
			if ( redirect == null ){
					
				return( returnTextPlain( response, "" ));
				
			}else{
				
				response.setReplyStatus( 302 );
				
				response.setHeader( "Location", redirect );
				
				return( true );
			}
		}
	
		if ( generateSupport( request, response )){
			
			return(true);
		}
				
		if ( url.equals("/") || url.startsWith( "/?" )){
			
			url = "/";
			
			if ( home_page != null ){
				
				url = home_page;
				
			}else{
			
				for (int i=0;i<welcome_files.length;i++){
					
					if ( welcome_files[i].exists()){
						
						url = "/" + welcome_pages[i];
						
						break;
					}
				}	
			}
		}
	
			// first try file system for data
		
		if ( response.useFile( file_root, url )){
			
			return( true );
		}
		
				// now try jars		
			
		String	resource_name = url;
		
		if (resource_name.startsWith("/")){
			
			resource_name = resource_name.substring(1);
		}
					
		int	pos = resource_name.lastIndexOf(".");
		
		if ( pos != -1 ){
			
			String	type = resource_name.substring( pos+1 );
		
			ClassLoader	cl = plugin_interface.getPluginClassLoader();
			
			InputStream is = cl.getResourceAsStream( resource_name );
		
			if ( is == null ){
				
				// failed absolute load, try relative
				
				if ( resource_root != null ){ 
					
					resource_name = resource_root + "/" + resource_name;
					
					is = cl.getResourceAsStream( resource_name );	
				}
			}
			
			// System.out.println( resource_name + "->" + is + ", url = " + url );
		
			if (is != null ){
			
				try{
					response.useStream( type, is );
				
				}finally{
				
					is.close();
				}
			
				return( true );
			}
		}
		
		return( false );
	}
	
	private String
	getArgumentFromURL(
		URL			url,
		String		argument )
	{
		String query = url.getQuery();
		
		if ( query != null ){
						
			String[] args = query.split( "&" );
			
			for ( String arg: args ){
				
				String [] x = arg.split( "=" );
				
				if ( x.length == 2 ){
					
					if ( x[0].equals( argument )){
						
						return( UrlUtils.decode( x[1] ));
					}
				}
			}
		}
		
		return( null );
	}
	
	private boolean
	returnTextPlain(
		TrackerWebPageResponse		response,
		String						str )
	{
		return( returnStuff( response, "text/plain", str ));
	}
	
	private boolean
	returnJSON(
		TrackerWebPageResponse		response,
		String						str )
	{
		return( returnStuff( response, "text/plain", str ));
	}
	
	private boolean
	returnStuff(
		TrackerWebPageResponse		response,
		String						content_type,
		String						str )
	{
		response.setContentType( content_type );
		
		PrintWriter pw = new PrintWriter( response.getOutputStream());
		
		pw.println( str );
		
		pw.flush();
		
		pw.close();
	
		return( true );
	}
	
	protected BasicPluginConfigModel
	getConfigModel()
	{
		return( config_model );
	}
	
	protected BasicPluginViewModel getViewModel() {
		return this.view_model;
	}
	
	protected void
	log(
		String	str )
	{
		log.log( str );
	}
	
	protected void
	log(
		String		str,
		Throwable 	e )
	{
		log.log( str, e );
	}
}
