diff options
Diffstat (limited to 'library/ajaxchat/chat/socket/server.rb')
-rw-r--r-- | library/ajaxchat/chat/socket/server.rb | 400 |
1 files changed, 0 insertions, 400 deletions
diff --git a/library/ajaxchat/chat/socket/server.rb b/library/ajaxchat/chat/socket/server.rb deleted file mode 100644 index c2f532c84..000000000 --- a/library/ajaxchat/chat/socket/server.rb +++ /dev/null @@ -1,400 +0,0 @@ -#!/usr/bin/env ruby - -# Simple Ruby XML Socket Server -# -# This is a a simple socket server implementation in ruby -# to communicate with flash clients via Flash XML Sockets. -# -# The socket code is based on the tutorial -# "Sockets programming in Ruby" -# by M. Tim Jones (mtj@mtjones.com). -# -# Date:: Tue, 05 Mar 2008 -# Author:: Sebastian Tschan, https://blueimp.net -# License:: GNU Affero General Public License - -# Include socket library: -require 'socket' -# Include XML libraries: -require 'rexml/document' -require 'rexml/streamlistener' - -# XML Stream Handler class used to parse chat messages: -class XMLStreamHandler - attr_reader :type,:chat_id,:user_id,:reg_id,:channel_id,:channel_ids - # Called when an opening tag (including attributes) is parsed: - def tag_start name, attrs - case name - when 'root' - # root messages are broadcast messages: - @type = :message - @chat_id = attrs['chatID'] - @channel_id = attrs['channelID'] - throw :break - when 'register' - # register messages are sent by chat clients: - @type = :register - @chat_id = attrs['chatID'] - @user_id = attrs['userID'] - @reg_id = attrs['regID'] - throw :break - when 'authenticate' - # authenticate messages are sent by the chat server client: - @type = :authenticate - @chat_id = attrs['chatID'] - @user_id = attrs['userID'] - @reg_id = attrs['regID'] - @channel_ids = Array::new - when 'channel' - # authenticate messages contain channel tags: - if @channel_ids - @channel_ids.push(attrs['id']) - else - throw :break - end - when 'policy-file-request' - # policy-file-requests are sent by flash clients for cross-domain authentication: - @type = :policy_file_request - throw :break - else - throw :break - end - end - # Called when a closing tag is parsed: - def tag_end name - if name == 'authenticate' - throw :break - end - end - def text text - # Called on text between tags - end - # Called when cdata is parsed: - alias cdata text -end - -# Socket Server class: -class SocketServer - - def initialize(config_file) - # List of configuration settings: - @config = Hash::new - # Initialize default settings: - initialize_default_properties - if config_file - # Load settings from configuration file: - load_properties_from_file(config_file) - end - # Sockets list: - @sockets = Array::new - # Clients list: - @clients = Hash::new - # Chats list, used to distinguish between different chat installations (contains channels list): - @chats = Hash::new - # Initialize server socket: - initialize_server_socket - if @server_socket - # Log server start (STDOUT.flush prevents output buffering): - puts "#{Time.now}\tServer started on Port #{@config[:server_port].to_s} ..."; STDOUT.flush - begin - # Start the server: - run - rescue SignalException - # Controlled stop: - ensure - for socket in @sockets - if socket != @server_socket - # Disconnect all clients: - handle_client_disconnection(socket, false) - end - end - @sockets = nil - @clients = nil - # Log server stop: - puts "#{Time.now}\tServer stopped."; STDOUT.flush - end - end - end - - def run - # Endless loop: - while 1 - # Blocking select call. The first three parameters are arrays of IO objects or nil. - # The last parameter is to set a timeout in seconds to force select to return - # if no event has occurred on any of the given IO object arrays. - res = select(@sockets, nil, nil, nil) - if res != nil then - # Iterate through the tagged read descriptors: - for socket in res[0] - # Received a connect to the server socket: - if socket == @server_socket then - accept_new_connection - else - # Received something on a client socket: - if socket.eof? then - # Handle client disconnection: - handle_client_disconnection(socket) - else - # Handle client input data: - handle_client_input(socket, socket.gets(@config[:eol])) - end - end - end - end - end - end - - private - - def initialize_default_properties - # Server address (empty = bind to all available interfaces): - @config[:server_address] = '' - # Server port: - @config[:server_port] = 1935 - # Comma-separated list of clients allowed to broadcast (allows all if empty): - @config[:broadcast_clients] = '' - # Defines if broadcast is sent to broadcasting client: - @config[:broadcast_self] = false - # Maximum number of clients (0 allows an unlimited number of clients): - @config[:max_clients] = 0 - # Comma-separated list of domains from which downloaded Flash clients are allowed to connect (* allows all domains): - @config[:allow_access_from] = '*' - # Defines the cross-domain-policy string sent to Flash clients as response to a policy-file-request: - @config[:cross_domain_policy] = '<cross-domain-policy><allow-access-from domain="'+@config[:allow_access_from]+'" to-ports="'+@config[:server_port].to_s+'"/></cross-domain-policy>' - # EOL (End Of Line) character used by Flash XML Socket communication (a null-byte): - @config[:eol] = "\0" - # Log level (0 logs only errors and server start/stop, 1 logs client connections, 2 logs all messages but no broadcast content, 3 logs everything): - @config[:log_level] = 0 - end - - def load_properties_from_file(config_file) - # Open the config file and go through each line: - File.open(config_file, 'r') do |file| - file.read.each_line do |line| - # Remove trailing whitespace from the line: - line.strip! - # Get the position of the first "=": - i = line.index('=') - # Check if line is not a comment and a valid property: - if (!line.empty? && line[0] != ?# && i > 0) - # Add the configuration option to the config hash: - key = line[0..i - 1].strip - value = line[i + 1..-1].strip - # Parse boolean values: - if value.eql?('false') - @config[key.to_sym] = false - elsif value.eql?('true') - @config[key.to_sym] = true - # Parse integer numbers: - elsif value.to_i.to_s.eql?(value) - @config[key.to_sym] = value.to_i - # Parse floating point numbers: - elsif value.to_f.to_s.eql?(value) - @config[key.to_sym] = value.to_f - # Parse string values: - else - @config[key.to_sym] = value - end - end - end - end - if @config[:eol].empty? - # Use default EOL if configuration option is empty: - @config[:eol] = $/ - end - end - - def initialize_server_socket - begin - # The server socket, allowing connections from any interface and bound to the given port number: - @server_socket = TCPServer.new(@config[:server_address], @config[:server_port].to_i) - # Enable reuse of the server address (e.g. for rapid restarts of the server): - @server_socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, 1) - # Add the server socket to the sockets list: - @sockets.push(@server_socket) - rescue Exception => error - # Log initialization failure: - puts "#{Time.now}\tFailed to initialize Server on Port #{@config[:server_port].to_s}: #{error}."; STDOUT.flush - end - end - - def accept_new_connection - begin - # Accept the client connection (non-blocking): - socket = @server_socket.accept_nonblock - # Retrieve IP and Port: - ip = socket.peeraddr[3] - port = socket.peeraddr[1] - # Check if we have reached the maximum number of connected clients (always accept the broadcast clients): - if @config[:max_clients].to_i == 0 || @clients.size < @config[:max_clients].to_i || !@config[:broadcast_clients].empty? && @config[:broadcast_clients].include?(ip) - # Add the accepted socket connection to the socket list: - @sockets.push(socket) - # Create a new Hash to store the client data: - client = Hash::new - client[:id] = "[#{ip}]:#{port}" - # Check if the client is allowed to broadcast: - if @config[:broadcast_clients].empty? || @config[:broadcast_clients].include?(ip) - client[:allowed_to_broadcast] = true - else - client[:allowed_to_broadcast] = false - end - # Add the client to the clients list: - @clients[socket] = client - if @config[:log_level].to_i > 0 - # Log client connection and the number of connected clients: - puts "#{Time.now}\t#{client[:id]} Connects\t(#{@clients.size} connected)"; STDOUT.flush - end - else - # Close the socket connection: - socket.close - end - rescue - # Client disconnected before the address information (IP, Port) could be retrieved. - end - end - - def handle_client_disconnection(client_socket, delete_socket=true) - # Retrieve the client ID for the current socket: - client_id = @clients[client_socket][:id] - begin - # Close the socket connection: - client_socket.close - rescue - # Rescue if closing the socket fails - end - if delete_socket - # Remove the socket from the sockets list: - @sockets.delete(client_socket) - end - # Remove the client ID from the clients list: - @clients.delete(client_socket) - if @config[:log_level].to_i > 0 - # Log client disconnection and the number of connected clients: - puts "#{Time.now}\t#{client_id} Disconnects\t(#{@clients.size} connected)"; STDOUT.flush - end - end - - def handle_client_input(client_socket, str) - # Create a new XML stream handler: - handler = XMLStreamHandler.new - begin - # As soon as the parser has found the relevant information it throws a :break symbol: - catch :break do - # Parse the given input string for XML messages: - REXML::Document.parse_stream(str, handler) - end - # The handler stores a type property to define the parsed XML message: - case handler.type - when :message - handle_broadcast_message(client_socket, handler.chat_id, handler.channel_id, str) - when :register - handle_client_registration(client_socket, handler.chat_id, handler.user_id, handler.reg_id) - when :authenticate - handle_client_authentication(client_socket, handler.chat_id, handler.user_id, handler.reg_id, handler.channel_ids) - when :policy_file_request - handle_policy_file_request(client_socket) - end - rescue Exception => error - # Rescue if parsing the client input fails and log the error message: - puts "#{Time.now}\t#{@clients[client_socket][:id]} Client Input Error:#{error.to_s.dump}"; STDOUT.flush - end - end - - def handle_broadcast_message(client_socket, chat_id, channel_id, str) - # Check if the_client is allowed to broadcast: - if @clients[client_socket][:allowed_to_broadcast] - # Check if the chat and channel have been registered: - if @chats[chat_id] && (@chats[chat_id][channel_id] || @chats[chat_id]['ALL']) - # Go through the sockets list: - @sockets.each do |socket| - # Skip the server socket and skip the the client socket if broadcast is not to be sent to self: - if socket != @server_socket && (@config[:broadcast_self] || socket != client_socket) - # Only write to clients registered to the given channel or to the "ALL" channel: - if @chats[chat_id]['ALL'] - reg_id = @chats[chat_id]['ALL'][@clients[socket][:user_id]] - end - if !reg_id && @chats[chat_id][channel_id] - reg_id = @chats[chat_id][channel_id][@clients[socket][:user_id]] - end - # Check if the reg_id stored for the given channel and user_id matches the clients reg_id: - if reg_id && reg_id.eql?(@clients[socket][:reg_id]) - begin - # Write the broadcast message on the socket connection: - socket.write(str) - rescue - # Rescue if writing to the socket fails - end - end - end - end - end - if @config[:log_level].to_i > 2 - # Log the message sent by the broadcast client: - puts "#{Time.now}\t#{@clients[client_socket][:id]} Chat:#{chat_id.to_s.dump} Channel:#{channel_id.to_s.dump} Message:#{str.to_s.dump}"; STDOUT.flush - elsif @config[:log_level].to_i > 1 - # Log the message sent by the broadcast client: - puts "#{Time.now}\t#{@clients[client_socket][:id]} Chat:#{chat_id.to_s.dump} Channel:#{channel_id.to_s.dump} Message"; STDOUT.flush - end - end - end - - def handle_client_registration(client_socket, chat_id, user_id, reg_id) - # Save the chat_id, use_id and reg_id as client properties: - @clients[client_socket][:chat_id] = chat_id - @clients[client_socket][:user_id] = user_id - @clients[client_socket][:reg_id] = reg_id - if @config[:log_level].to_i > 1 - # Log the client registration: - puts "#{Time.now}\t#{@clients[client_socket][:id]} Chat:#{chat_id.to_s.dump} User:#{user_id.to_s.dump} Reg:#{reg_id.to_s.dump}"; STDOUT.flush - end - end - - def handle_client_authentication(client_socket, chat_id, user_id, reg_id, channel_ids) - # Only the broadcast clients may send authentication messages: - if @clients[client_socket][:allowed_to_broadcast] - # Create a new chat item if not found for the given chat_id: - if !@chats[chat_id] - @chats[chat_id] = Hash.new - end - # Go through the list of channels for the given chat: - @chats[chat_id].each_key do |key| - # Delete all items for the given user on all channels of the given chat: - @chats[chat_id][key].delete(user_id) - # If the chat channel is empty, delete the channel item: - if @chats[chat_id][key].size == 0 - @chats[chat_id].delete(key) - end - end - # Go through the list of authenticated channel_ids: - channel_ids.each do |channel_id| - # Create a new channel item if not found for the current channel_id (and the given chat_id): - if !@chats[chat_id][channel_id] - @chats[chat_id][channel_id] = Hash.new - end - # Add a user item of the given user_id with the given reg_id to the current channel: - @chats[chat_id][channel_id][user_id] = reg_id - end - if @config[:log_level].to_i > 1 - # Log the client authentication: - puts "#{Time.now}\t#{@clients[client_socket][:id]} Chat:#{chat_id.to_s.dump} User:#{user_id.to_s.dump} Auth:#{reg_id.to_s.dump} Channels:#{channel_ids.join(',').dump}"; STDOUT.flush - end - end - end - - def handle_policy_file_request(client_socket) - begin - # Write the cross-domain-policy to the Flash client: - client_socket.write(@config[:cross_domain_policy]+@config[:eol]) - rescue - # Rescue if writing to the socket fails - end - if @config[:log_level].to_i > 1 - # Log the policy-file-request: - puts "#{Time.now}\t#{@clients[client_socket][:id]} Policy-File-Request"; STDOUT.flush - end - end - -end - -# Start the socket server with the first command line argument as configuration file: -SocketServer.new($*[0])
\ No newline at end of file |