/**
 * $RCSfile: $
 * $Revision: $
 * $Date: $
 *
 * Copyright (C) 2005-2008 Jive Software. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.jivesoftware.openfire;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;

import org.jivesoftware.openfire.container.BasicModule;
import org.jivesoftware.util.JiveGlobals;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class FlashCrossDomainHandler extends BasicModule {
	
	private static final Logger Log = LoggerFactory.getLogger(FlashCrossDomainHandler.class);

    private ServerSocket serverSocket;

    public static String CROSS_DOMAIN_TEXT = "<?xml version=\"1.0\"?>" +
            "<!DOCTYPE cross-domain-policy SYSTEM \"http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd\">" +
            "<cross-domain-policy>" +
            "<allow-access-from domain=\"*\" to-ports=\"";

    public static String CROSS_DOMAIN_END_TEXT = "\" /></cross-domain-policy>";

    public FlashCrossDomainHandler() {
        super("Flash CrossDomain Handler");
    }

    @Override
	public void start() {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    startServer();
                }
                catch (Exception e) {
                    Log.error(e.getMessage(), e);
                }
            }
        }, "Flash Cross Domain");

        thread.start();
    }

    @Override
	public void stop() {
        try {
            if (serverSocket != null) {
                serverSocket.close();
            }
        }
        catch (IOException e) {
            Log.error(e.getMessage(), e);
        }
    }

    public int getPort() {
        return serverSocket != null ? serverSocket.getLocalPort() : 0;
    }

    private void startServer() throws Exception {
        if(!JiveGlobals.getBooleanProperty("flash.crossdomain.enabled",true)){
            Log.debug("Flash cross domain listener is disabled");
            return;
        }
        
        int port = JiveGlobals.getIntProperty("flash.crossdomain.port",5229);
        try {
            // Listen on a specific network interface if it has been set.
            String interfaceName = JiveGlobals.getXMLProperty("network.interface");
            InetAddress bindInterface = null;
            if (interfaceName != null) {
                if (interfaceName.trim().length() > 0) {
                    bindInterface = InetAddress.getByName(interfaceName);
                }
            }
            serverSocket = new ServerSocket(port, -1, bindInterface);
            Log.debug("Flash cross domain is listening on " + interfaceName + " on port " + port);
        }
        catch (IOException e) {
            Log.error("Could not listen on port: " + port, e);
            return;
        }

        while (true) {
            Socket clientSocket = null;
            PrintWriter out = null;
            BufferedReader in = null;
            try {
                clientSocket = serverSocket.accept();
                clientSocket.setSoTimeout(10000); // 10 second timeout

                out = new PrintWriter(clientSocket.getOutputStream(), true);
                in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
                
                String request = "";
            	request = read(in);
                
                if (request.contains("<policy-file-request/>") || request.contains("GET /crossdomain.xml")) {
	                out.write(CROSS_DOMAIN_TEXT +
	                        XMPPServer.getInstance().getConnectionManager().getClientListenerPort() +
	                        CROSS_DOMAIN_END_TEXT+"\u0000");
                }
            }
            catch (IOException e) {
                if (XMPPServer.getInstance().isShuttingDown()) {
                    break;
                }
                Log.error(e.getMessage(), e);
            }
            finally {
            	if (out != null) {
            		out.flush();
            		out.close();
            	}
            	if (in != null) {
            		in.close();
            	}
            	if (clientSocket != null) {
            		clientSocket.close();
            	}
            }
        }
    }
    
    /**
     * Safely read a string from the reader until a zero character or a newline
     * is received, more then 100 invalid code points where read or the 200
     * character is reached.
     * 
     * @return the string read from the reader.
     */
    protected String read(BufferedReader in) {
        StringBuilder buffer = new StringBuilder();
        int codePoint;
        boolean stopReading = false;
        int invalidCodePoints = 0;

        try {
            do {
                codePoint = in.read();

                if (codePoint == 0 || codePoint == '\n' || codePoint == -1) {
                    stopReading = true;
                }
                else if (Character.isValidCodePoint(codePoint)) {
                    buffer.appendCodePoint(codePoint);
                } else {
                    invalidCodePoints++;
                }
            } while (!stopReading && buffer.length() < 200
                    && invalidCodePoints < 100);
        }
        catch (Exception e) {
            Log.debug("Exception (read): " + e.getMessage());
        }
        
        return buffer.toString();
    }

}
