// ------------------------------------------------------------------ // Houses the jabber transport code // // 1. jabber_connection - high level jabber component // 2. jabber_message - message class // 3. jabber_socket - socket handling code (shouldn't have to // use this class directly) // // Requires oils_utils.js // ------------------------------------------------------------------ // ------------------------------------------------------------------ // JABBER_CONNECTION // High level transport code // ------------------------------------------------------------------ // Constructor // ------------------------------------------------------------------ jabber_connection.prototype = new transport_connection(); jabber_connection.prototype.constructor = jabber_connection; jabber_connection.baseClass = transport_connection.prototype.constructor; /** Initializes a jabber_connection object */ function jabber_connection( username, password, resource ) { this.username = username; this.password = password; this.resource = resource; this.socket = new jabber_socket(); this.host = ""; } /** Connects to the Jabber server. 'timeout' is the connect timeout * in milliseconds */ jabber_connection.prototype.connect = function( host, port, timeout ) { this.host = host; return this.socket.connect( this.username, this.password, this.resource, host, port, timeout ); } /** Sends a message to 'recipient' with the provided message * thread and body */ jabber_connection.prototype.send = function( recipient, thread, body ) { var jid = this.username+"@"+this.host+"/"+this.resource; var msg = new jabber_message( jid, recipient, thread, body ); return this.socket.tcp_send( msg.to_string() ); } /** This method will wait at most 'timeout' milliseconds * for a Jabber message to arrive. If one arrives * it is returned to the caller, other it returns null */ jabber_connection.prototype.recv = function( timeout ) { return this.socket.recv( timeout ); } /** Disconnects from the jabber server */ jabber_connection.prototype.disconnect = function() { return this.socket.disconnect(); } /** Returns true if we are currently connected to the * Jabber server */ jabber_connection.prototype.connected = function() { return this.socket.connected(); } // ------------------------------------------------------------------ // JABBER_MESSAGE // High level message handling code jabber_message.prototype = new transport_message(); jabber_message.prototype.constructor = jabber_message; jabber_message.prototype.baseClass = transport_message.prototype.constructor; /** Builds a jabber_message object */ function jabber_message( sender, recipient, thread, body ) { if( sender == null || recipient == null || recipient.length < 1 ) { return; } this.doc = new DOMParser().parseFromString("", "text/xml"); this.root = this.doc.documentElement; this.root.setAttribute( "from", sender ); this.root.setAttribute( "to", recipient ); var body_node = this.doc.createElement("body"); body_node.appendChild( this.doc.createTextNode( body ) ); var thread_node = this.doc.createElement("thread"); thread_node.appendChild( this.doc.createTextNode( thread ) ); this.root.appendChild( body_node ); this.root.appendChild( thread_node ); } /** Builds a new message from raw xml. * If the message is a Jabber error message, then msg.is_error_msg * is set to true; */ jabber_message.prototype.from_xml = function( xml ) { var msg = new jabber_message(); msg.doc = new DOMParser().parseFromString( xml, "text/xml" ); msg.root = msg.doc.documentElement; if( msg.root.getAttribute( "type" ) == "error" ) { msg.is_error_msg = true; } else { this.is_error_msg = false; } return msg; } /** Returns the 'from' field of the message */ jabber_message.prototype.get_sender = function() { return this.root.getAttribute( "from" ); } /** Returns the jabber thread */ jabber_message.prototype.get_thread = function() { var nodes = this.root.getElementsByTagName( "thread" ); var thread_node = nodes.item(0); return thread_node.firstChild.nodeValue; } /** Returns the message body */ jabber_message.prototype.get_body = function() { var nodes = this.root.getElementsByTagName( "body" ); var body_node = nodes.item(0); new Logger().transport( "Get Body returning:\n" + body_node.textContent, Logger.DEBUG ); return body_node.textContent; } /** Returns the message as a whole as an XML string */ jabber_message.prototype.to_string = function() { return new XMLSerializer().serializeToString(this.root); } // ------------------------------------------------------------------ // TRANSPORT_SOCKET /** Initializes a new jabber_socket object */ function jabber_socket() { this.is_connected = false; this.outstream = ""; this.instream = ""; this.buffer = ""; this.socket = ""; } /** Connects to the jabber server */ jabber_socket.prototype.connect = function( username, password, resource, host, port, timeout ) { var starttime = new Date().getTime(); // there has to be at least some kind of timeout if( ! timeout || timeout < 100 ) { timeout = 1000; } try { this.xpcom_init( host, port ); this.tcp_send( "" ); if( !this.tcp_recv( timeout ) ) { throw 1; } } catch( E ) { throw new oils_ex_transport( "Could not open a socket to the transport server\n" + "Server: " + host + " Port: " + port ); } // Send the auth packet this.tcp_send( "" + username + "" + password + "" + resource + "" ); var cur = new Date().getTime(); var remaining = timeout - ( cur - starttime ); this.tcp_recv( remaining ); if( ! this.connected() ) { throw new oils_ex_transport( "Connection to transport server timed out" ); } return true; } /** Sets up all of the xpcom components */ jabber_socket.prototype.xpcom_init = function( host, port ) { var transportService = Components.classes["@mozilla.org/network/socket-transport-service;1"] .getService(Components.interfaces.nsISocketTransportService); this.transport = transportService.createTransport( null, 0, host, port, null); // ------------------------------------------------------------------ // Build the stream objects // ------------------------------------------------------------------ this.outstream = this.transport.openOutputStream(0,0,0); var stream = this.transport.openInputStream(0,0,0); this.instream = Components.classes["@mozilla.org/scriptableinputstream;1"] .createInstance(Components.interfaces.nsIScriptableInputStream); this.instream.init(stream); } /** Send data to the TCP pipe */ jabber_socket.prototype.tcp_send = function( data ) { new Logger().transport( "Sending Data: \n" + data, Logger.INFO ); this.outstream.write(data,data.length); } /** Accepts data coming directly from the socket. If we're not * connected, we pass it off to procecc_connect(). Otherwise, * this method adds the data to the local buffer. */ jabber_socket.prototype.process_data = function( data ) { new Logger().transport( "Received TCP data: " + data, Logger.DEBUG ); if( ! this.connected() ) { this.process_connect( data ); return; } this.buffer += data; } /** Processes connect data to verify we are logged in correctly */ jabber_socket.prototype.process_connect = function( data ) { var reg = /type=["\']result["\']/; var err = /error/; if( reg.exec( data ) ) { this.is_connected = true; } else { if( err.exec( data ) ) { //throw new oils_ex_transport( "Server returned: \n" + data ); throw new oils_ex_jabber_auth( "Server returned: \n" + data ); // Throw exception, return something... } } } /** Waits up to at most 'timeout' milliseconds for data to arrive * in the TCP buffer. If there is at least one byte of data * in the buffer, then all of the data that is in the buffer is sent * to the process_data method for processing and the method returns. */ jabber_socket.prototype.tcp_recv = function( timeout ) { var count = this.instream.available(); var did_receive = false; // ------------------------------------------------------------------ // If there is any data in the tcp buffer, process it and return // ------------------------------------------------------------------ if( count > 0 ) { did_receive = true; while( count > 0 ) { new Logger().transport( "before process data", Logger.DEBUG ); this.process_data( this.instream.read( count ) ); new Logger().transport( "after process data", Logger.DEBUG ); count = this.instream.available(); new Logger().transport( "received " + count + " bytes" , Logger.DEBUG ); } } else { // ------------------------------------------------------------------ // Do the timeout dance // ------------------------------------------------------------------ // ------------------------------------------------------------------ // If there is no data in the buffer, wait up to timeout seconds // for some data to arrive. Once it arrives, suck it all out // and send it on for processing // ------------------------------------------------------------------ var now, then; now = new Date().getTime(); then = now; // ------------------------------------------------------------------ // Loop and poll for data every 50 ms. // ------------------------------------------------------------------ while( ((now-then) <= timeout) && count <= 0 ) { sleep(50); count = this.instream.available(); now = new Date().getTime(); } // ------------------------------------------------------------------ // if we finally get some data, process it. // ------------------------------------------------------------------ if( count > 0 ) { did_receive = true; while( count > 0 ) { // pull in all of the data there is this.process_data( this.instream.read( count ) ); count = this.instream.available(); } } } return did_receive; } /** If a message is already sitting in the queue, it is returned. * If not, this method waits till at most 'timeout' milliseconds * for a full jabber message to arrive and then returns that. * If none ever arrives, returns null. */ jabber_socket.prototype.recv = function( timeout ) { var now, then; now = new Date().getTime(); then = now; var first_pass = true; while( ((now-then) <= timeout) ) { if( this.buffer.length == 0 || !first_pass ) { if( ! this.tcp_recv( timeout ) ) { return null; } } first_pass = false; //new Logger().transport( "\n\nTCP Buffer Before: \n" + this.buffer, Logger.DEBUG ); var buf = this.buffer; this.buffer = ""; new Logger().transport( "CURRENT BUFFER\n" + buf, Logger.DEBUG ); buf = buf.replace( /\n/g, '' ); // remove pesky newlines var reg = /.*?<\/message>/; var iqr = /.*?<\/iq>/; var out = reg.exec(buf); if( out ) { var msg_xml = out[0]; this.buffer = buf.substring( msg_xml.length, buf.length ); new Logger().transport( "Building Jabber message\n\n" + msg_xml, Logger.DEBUG ); var jab_msg = new jabber_message().from_xml( msg_xml ); if( jab_msg.is_error_msg ) { new Logger().transport( "Received Jabber error message \n\n" + msg_xml, Logger.ERROR ); } return jab_msg; } else { out = iqr.exec(buf); if( out ) { var msg_xml = out[0]; this.buffer = buf.substring( msg_xml.length, buf.length ); process_iq_data( msg_xml ); return; } else { this.buffer = buf; } } now = new Date().getTime(); } return null; } jabber_socket.prototype.process_iq_data = function( data ) { new Logger().transport( "IQ Packet received... Not Implemented\n" + data, Logger.ERROR ); } /** Disconnects from the jabber server and closes down shop */ jabber_socket.prototype.disconnect = function() { this.tcp_send( "" ); this.instream.close(); this.outstream.close(); } /** True if connected */ jabber_socket.prototype.connected = function() { return this.is_connected; }