--- /dev/null
+package MARC::File::SAX;
+
+## no POD here since you don't really want to use this module
+## directly. Look at MARC::File::XML instead.
+##
+## MARC::File::SAX is a SAX handler for parsing XML encoded using the
+## MARC21slim XML schema from the Library of Congress. It builds a MARC::Record
+## object up from SAX events.
+##
+## For more details see: http://www.loc.gov/standards/marcxml/
+
+use strict;
+use XML::SAX;
+use base qw( XML::SAX::Base );
+use Data::Dumper;
+use MARC::Charset;
+use Encode ();
+
+my $charset = MARC::Charset->new();
+my $_is_unicode;
+
+sub start_element {
+ my ( $self, $element ) = @_;
+ my $name = $element->{ Name };
+ if ( $name eq 'leader' ) {
+ $self->{ tag } = 'LDR';
+ } elsif ( $name eq 'controlfield' ) {
+ $self->{ tag } = $element->{ Attributes }{ '{}tag' }{ Value };
+ } elsif ( $name eq 'datafield' ) {
+ $self->{ tag } = $element->{ Attributes }{ '{}tag' }{ Value };
+ $self->{ i1 } = $element->{ Attributes }{ '{}ind1' }{ Value };
+ $self->{ i2 } = $element->{ Attributes }{ '{}ind2' }{ Value };
+ } elsif ( $name eq 'subfield' ) {
+ $self->{ subcode } = $element->{ Attributes }{ '{}code' }{ Value };
+ }
+}
+
+sub end_element {
+ my ( $self, $element ) = @_;
+ my $name = $element->{ Name };
+ if ( $name eq 'subfield' ) {
+ push( @{ $self->{ subfields } }, $self->{ subcode }, save_space_in_utf8($self->{ chars }) );
+ $self->{ chars } = '';
+ $self->{ subcode } = '';
+ } elsif ( $name eq 'controlfield' ) {
+ $self->{ record }->append_fields(
+ MARC::Field->new(
+ $self->{ tag },
+ save_space_in_utf8($self->{ chars })
+ )
+ );
+ $self->{ chars } = '';
+ $self->{ tag } = '';
+ } elsif ( $name eq 'datafield' ) {
+ $self->{ record }->append_fields(
+ MARC::Field->new(
+ $self->{ tag },
+ $self->{ i1 },
+ $self->{ i2 },
+ @{ $self->{ subfields } }
+ )
+ );
+ $self->{ tag } = '';
+ $self->{ i1 } = '';
+ $self->{ i2 } = '';
+ $self->{ subfields } = [];
+ $self->{ chars } = '';
+ } elsif ( $name eq 'leader' ) {
+ $_is_unicode = 0;
+ my $ldr = $self->{ chars };
+ $_is_unicode++ if (substr($ldr,9,1) eq 'a');
+ substr($ldr,9,1,' ');
+ $self->{ record }->leader( save_space_in_utf8($ldr) );
+ $self->{ chars } = '';
+ $self->{ tag } = '';
+ }
+
+}
+
+sub save_space_in_utf8 {
+ my $string = shift;
+ my $output = '';
+ while ($string =~ /(\s*)(\S*)(\s*)/gcsmo) {
+ $output .= $1 . Encode::encode('latin1',$charset->to_marc8($2)) . $3;# if ($_is_unicode);
+ #$output .= $1 . $2 . $3 unless ($_is_unicode);
+ }
+ return $output;
+}
+
+sub characters {
+ my ( $self, $chars ) = @_;
+ if ( $self->{ subcode } or ( $self->{ tag } and
+ ( $self->{ tag } eq 'LDR' or $self->{ tag } < 10 ) ) ) {
+ $self->{ chars } .= $chars->{ Data };
+ }
+}
+
+1;
--- /dev/null
+package MARC::File::XML;
+
+use warnings;
+use strict;
+use base qw( MARC::File );
+use MARC::Record;
+use MARC::Field;
+use MARC::File::SAX;
+use MARC::Charset;
+use IO::File;
+use Carp qw( croak );
+use Encode ();
+
+our $VERSION = '0.66';
+
+my $handler = MARC::File::SAX->new();
+my $parser = XML::SAX::ParserFactory->parser( Handler => $handler );
+my $charset = MARC::Charset->new();
+
+
+=head1 NAME
+
+MARC::File::XML - Work with MARC data encoded as XML
+
+=head1 SYNOPSIS
+
+ ## reading with MARC::Batch
+ my $batch = MARC::Batch->new( 'XML', $filename );
+ my $record = $batch->next();
+
+ ## or reading with MARC::File::XML explicitly
+ my $file = MARC::File::XML->in( $filename );
+ my $record = $file->next();
+
+ ## serialize a single MARC::Record object as XML
+ print $record->as_xml();
+
+ ## write a bunch of records to a file
+ my $file = MARC::File::XML->out( 'myfile.xml' );
+ $file->write( $record1 );
+ $file->write( $record2 );
+ $file->write( $record3 );
+ $file->close();
+
+ ## instead of writing to disk, get the xml directly
+ my $xml = join( "\n",
+ MARC::File::XML::header(),
+ MARC::File::XML::record( $record1 ),
+ MARC::File::XML::record( $record2 ),
+ MARC::File::XML::footer()
+ );
+
+=head1 DESCRIPTION
+
+The MARC-XML distribution is an extension to the MARC-Record distribution for
+working with MARC21 data that is encoded as XML. The XML encoding used is the
+MARC21slim schema supplied by the Library of Congress. More information may
+be obtained here: http://www.loc.gov/standards/marcxml/
+
+You must have MARC::Record installed to use MARC::File::XML. In fact
+once you install the MARC-XML distribution you will most likely not use it
+directly, but will have an additional file format available to you when you
+use MARC::Batch.
+
+This version of MARC-XML supersedes an the versions ending with 0.25 which
+were used with the MARC.pm framework. MARC-XML now uses MARC::Record
+exclusively.
+
+If you have any questions or would like to contribute to this module please
+sign on to the perl4lib list. More information about perl4lib is available
+at L<http://perl4lib.perl.org>.
+
+=head1 METHODS
+
+When you use MARC::File::XML your MARC::Record objects will have two new
+additional methods available to them:
+
+=head2 as_xml()
+
+Returns a MARC::Record object serialized in XML.
+
+ print $record->as_xml();
+
+=cut
+
+sub MARC::Record::as_xml {
+ my $record = shift;
+ return( MARC::File::XML::encode( $record ) );
+}
+
+=head2 new_from_xml()
+
+If you have a chunk of XML and you want a record object for it you can use
+this method to generate a MARC::Record object.
+
+ my $record = MARC::Record->new_from_xml( $xml );
+
+Note: only works for single record XML chunks.
+
+=cut
+
+sub MARC::Record::new_from_xml {
+ my $xml = shift;
+ ## to allow calling as MARC::Record::new_from_xml()
+ ## or MARC::Record->new_from_xml()
+ $xml = shift if ( ref($xml) || ($xml eq "MARC::Record") );
+ return( MARC::File::XML::decode( $xml ) );
+}
+
+=pod
+
+If you want to write records as XML to a file you can use out() with write()
+to serialize more than one record as XML.
+
+=head2 out()
+
+A constructor for creating a MARC::File::XML object that can write XML to a
+file. You must pass in the name of a file to write XML to.
+
+ my $file = MARC::XML::File->out( $filename );
+
+=cut
+
+sub out {
+ my ( $class, $filename ) = @_;
+ my $fh = IO::File->new( ">$filename" ) or croak( $! );
+ my %self = (
+ filename => $filename,
+ fh => $fh,
+ header => 0
+ );
+ return( bless \%self, ref( $class ) || $class );
+}
+
+=head2 write()
+
+Used in tandem with out() to write records to a file.
+
+ my $file = MARC::File::XML->out( $filename );
+ $file->write( $record1 );
+ $file->write( $record2 );
+
+=cut
+
+sub write {
+ my ( $self, $record ) = @_;
+ if ( ! $self->{ fh } ) {
+ croak( "MARC::File::XML object not open for writing" );
+ }
+ if ( ! $record ) {
+ croak( "must pass write() a MARC::Record object" );
+ }
+ ## print the XML header if we haven't already
+ if ( ! $self->{ header } ) {
+ $self->{ fh }->print( header() );
+ $self->{ header } = 1;
+ }
+ ## print out the record
+ $self->{ fh }->print( record( $record ) ) || croak( $! );
+ return( 1 );
+}
+
+=head2 close()
+
+When writing records to disk the filehandle is automatically closed when you
+the MARC::File::XML object goes out of scope. If you want to close it explicitly
+use the close() method.
+
+=cut
+
+sub close {
+ return( 1 );
+ my $self = shift;
+ if ( $self->{ fh } ) {
+ $self->{ fh }->print( footer() ) if $self->{ header };
+ $self->{ fh } = undef;
+ $self->{ filename } = undef;
+ $self->{ header } = undef;
+ }
+ return( 1 );
+}
+
+## makes sure that the XML file is closed off
+
+sub DESTROY {
+ shift->close();
+}
+
+=pod
+
+If you want to generate batches of records as XML, but don't want to write to
+disk you'll have to use header(), record() and footer() to generate the
+different portions.
+
+ $xml = join( "\n",
+ MARC::File::XML::header(),
+ MARC::File::XML::record( $record1 ),
+ MARC::File::XML::record( $record2 ),
+ MARC::File::XML::record( $record3 ),
+ MARC::File::XML::footer()
+ );
+
+=head2 header()
+
+Returns a string of XML to use as the header to your XML file.
+
+This method takes an optional $encoding parameter to set the output encoding
+to something other than 'UTF-8'. This is meant mainly to support slightly
+broken records that are in ISO-8859-1 (ANSI) format with 8-bit characters.
+
+=cut
+
+sub header {
+ my $encoding = shift || 'UTF-8';
+ return( <<MARC_XML_HEADER );
+<?xml version="1.0" encoding="$encoding"?>
+<collection xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.loc.gov/MARC21/slim http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd" xmlns="http://www.loc.gov/MARC21/slim">
+MARC_XML_HEADER
+}
+
+=head2 footer()
+
+Returns a string of XML to use at the end of your XML file.
+
+=cut
+
+sub footer {
+ return( "</collection>" );
+}
+
+=head2 record()
+
+Returns a chunk of XML suitable for placement between the header and the footer.
+
+=cut
+
+sub _perhaps_encode {
+ my $data = shift;
+ my $done = shift;
+ $data = Encode::encode('utf8',$charset->to_utf8($data)) unless ($done);
+ return $data;
+}
+
+sub record {
+ my $record = shift;
+ my $_is_unicode = shift;
+ my @xml = ();
+ push( @xml, "<record>" );
+ push( @xml, " <leader>" . escape( _perhaps_encode($record->leader(), $_is_unicode)) . "</leader>" );
+ foreach my $field ( $record->fields() ) {
+ my $tag = $field->tag();
+ if ( $field->is_control_field() ) {
+ my $data = $field->data;
+ push( @xml, qq( <controlfield tag="$tag">) .
+ escape( _perhaps_encode($data, $_is_unicode) ). qq(</controlfield>) );
+ } else {
+ my $i1 = $field->indicator( 1 );
+ my $i2 = $field->indicator( 2 );
+ push( @xml, qq( <datafield tag="$tag" ind1="$i1" ind2="$i2">) );
+ foreach my $subfield ( $field->subfields() ) {
+ my ( $code, $data ) = @$subfield;
+ push( @xml, qq( <subfield code="$code">).
+ escape( _perhaps_encode($data, $_is_unicode) ).qq(</subfield>) );
+ }
+ push( @xml, " </datafield>" );
+ }
+ }
+ push( @xml, "</record>\n" );
+ return( join( "\n", @xml ) );
+}
+
+my %ESCAPES = (
+ '&' => '&',
+ '<' => '<',
+ '>' => '>',
+);
+my $ESCAPE_REGEX =
+ eval 'qr/' .
+ join( '|', map { $_ = "\Q$_\E" } keys %ESCAPES ) .
+ '/;'
+ ;
+
+sub escape {
+ my $string = shift;
+ $string =~ s/($ESCAPE_REGEX)/$ESCAPES{$1}/oge;
+ return( $string );
+}
+
+sub _next {
+ my $self = shift;
+ my $fh = $self->{ fh };
+
+ ## return undef at the end of the file
+ return if eof($fh);
+
+ ## get a chunk of xml for a record
+ local $/ = '</record>';
+ my $xml = <$fh>;
+
+ ## trim stuff before the start record element
+ $xml =~ s/.*<record.*?>/<record>/s;
+
+ ## return undef if there isn't a good chunk of xml
+ return if ( $xml !~ m|<record>.*</record>|s );
+
+ ## return the chunk of xml
+ return( $xml );
+}
+
+=head2 decode()
+
+You probably don't ever want to call this method directly. If you do
+you should pass in a chunk of XML as the argument.
+
+It is normally invoked by a call to next(), see L<MARC::Batch> or L<MARC::File>.
+
+=cut
+
+sub decode {
+
+ my $text;
+ my $location = '';
+ my $self = shift;
+
+ ## see MARC::File::USMARC::decode for explanation of what's going on
+ ## here
+ if ( ref($self) =~ /^MARC::File/ ) {
+ $location = 'in record '.$self->{recnum};
+ $text = shift;
+ } else {
+ $location = 'in record 1';
+ $text = $self=~/MARC::File/ ? shift : $self;
+ }
+
+ $parser->{ tagStack } = [];
+ $parser->{ subfields } = [];
+ $parser->{ Handler }{ record } = MARC::Record->new();
+ $parser->parse_string( $text );
+
+ return( $parser->{ Handler }{ record } );
+
+}
+
+=head2 encode([$encoding])
+
+You probably want to use the as_marc() method on your MARC::Record object
+instead of calling this directly. But if you want to you just need to
+pass in the MARC::Record object you wish to encode as XML, and you will be
+returned the XML as a scalar.
+
+This method takes an optional $encoding parameter to set the output encoding
+to something other than 'UTF-8'. This is meant mainly to support slightly
+broken records that are in ISO-8859-1 (ANSI) format with 8-bit characters.
+
+=cut
+
+sub encode {
+ my $record = shift;
+ my $encoding = shift;
+
+ my $_is_unicode = 0;
+ my $ldr = $record->leader;
+ my $needed_charset;
+
+ if (defined $encoding) {
+ # Are we forcing an alternate encoding? Then leave it alone.
+
+ } elsif (substr($ldr,9,1) eq 'a') {
+ # Does the record think it is already Unicode?
+ $_is_unicode++;
+ if ( my ($unneeded_charset) = $record->field('066') ) {
+ $record->delete_field( $unneeded_charset );
+ }
+
+ } else {
+ # Not forcing an encoding, and it's NOT Unicode. We set the leader to say
+ # Unicode for the conversion, remove any '066' field, and put it back later.
+ #
+ # XXX Need to generat a '066' field here, but I don't understand how yet.
+ substr($ldr,9,1,'a');
+ $record->leader( $ldr );
+ if ( ($needed_charset) = $record->field('066') ) {
+ $record->delete_field( $needed_charset );
+ }
+
+ }
+
+ my @xml = ();
+ push( @xml, header($encoding) );
+ push( @xml, record( $record, $_is_unicode ) );
+ push( @xml, footer() );
+
+ if (defined $needed_charset) {
+ $record->insert_fields_ordered($needed_charset);
+ substr($ldr,8,1,' ');
+ $record->leader( $ldr );
+ }
+
+ return( join( "\n", @xml ) );
+}
+
+=head1 TODO
+
+=over 4
+
+=item * Support for character translation using MARC::Charset.
+
+=item * Support for callback filters in decode().
+
+=item * Command line utilities marc2xml, etc.
+
+=back
+
+=head1 SEE ALSO
+
+=over 4
+
+=item L<http://www.loc.gov/standards/marcxml/>
+
+=item L<MARC::File::USMARC>
+
+=item L<MARC::Batch>
+
+=item L<MARC::Record>
+
+=back
+
+=head1 AUTHORS
+
+=over 4
+
+=item * Ed Summers <ehs@pobox.com>
+
+=back
+
+=cut
+
+1;
--- /dev/null
+#!/usr/bin/perl -w
+
+# Pulls the jabber users from the oils/jabber config files
+# and populates the mysql db for the jabber server with the users
+
+use DBI;
+use strict;
+use OpenILS::Utils::Config qw( /pines/conf/oils.conf );
+my $config = OpenILS::Utils::Config->current;
+
+if( @ARGV < 2 ) {
+ print "usage: perl jcreate.pl dbhost dbuser dbpass\n";
+ exit;
+}
+
+
+my $host = $ARGV[0];
+my $user = $ARGV[1];
+my $pass = $ARGV[2];
+
+my $connection = DBI->connect( "DBI:mysql:jabberd2:$host", $user, $pass )
+ or die "Cannot connect to db: $! \n";
+
+my $jpass = $config->transport->auth->password;
+my $realm = $config->transport->server->primary;
+
+# Delete all users
+my $clean = "delete from authreg;";
+my $sth = $connection->prepare( $clean );
+$sth->execute();
+
+my @users = keys %{$config->transport->users};
+
+# Grab each user from the config and push them into mysql
+for my $user (@users) {
+ if( ! $user or $user eq "__id" or $user eq "__sub") { next; }
+ print "Inserting $user: ";
+
+ my $sql = "insert into authreg (username, realm, password) values " .
+ "('$user', '$realm', '$jpass');";
+
+ print "[$sql]\n";
+
+ $sth = $connection->prepare( $sql );
+ $sth->execute();
+
+}
+
+$sth->finish();
+
--- /dev/null
+#!/bin/bash
+
+#
+# Simple rc script for controlling the system
+# Only works on linux because of 'ps' syntax
+#
+
+
+case $1 in
+ "start")
+ perl -MOpenILS::System -e 'OpenILS::System->bootstrap()' &
+ sleep 2;
+ $0 status;
+ echo;
+ ;;
+ "stop")
+ PID=$(ps ax | grep "[0-9] System$" | awk '{print $1}');
+ if [ -z $PID ]; then
+ echo "OpenILS System is not running";
+ exit;
+ fi
+ echo "Killing System...$PID";
+ kill -s INT $PID;
+ echo "Done";
+ ;;
+ "status")
+ PID=$(ps ax | grep "[0-9] System$" | awk '{print $1}');
+ if [ -z $PID ]; then
+ echo "OpenILS System is not running";
+ exit 0;
+ fi
+ echo "OpenILS System is running";
+ exit 1;
+ ;;
+ "restart")
+ $0 stop;
+ $0 start;
+ ;;
+ *)
+ echo "usage: system.sh [start|stop|restart|status]";
+ ;;
+esac
+
+
--- /dev/null
+<html>
+
+ <head>
+
+ <title> OILS Messaging </title>
+
+ </head>
+
+ <body>
+
+
+ <h1> Abstract </h1>
+
+ <p>
+
+ The OILS messaging system works on two different primary layers: the transport layer and the
+ application layer. The transport layer manages virtual connections between client and server,
+ while the application layer manages user/application level messages.
+
+ All messages must declare which protocol version they are requesting. The current protocol level
+ is 1.
+
+ <h1> Transport Layer </h1>
+
+ <p>
+ There are currently three types of messages in the transport layer: <b>CONNECT, STATUS, </b> and
+ <b>DISCONNECT</b>.
+
+ <p>
+ <b>STATUS</b> message provide general information to the transport layer are used in different
+ ways throughout the system. They are sent primarily by the server in response to client requests.
+ Each message comes with
+ a status and statusCode. The actual status part of the STATUS message is just a helpful message (mostly for debugging). The
+ statusCode is an integer representing the exact status this message represents. The status codes
+ are modeled after HTTP status codes. Currently used codes consist of the following:
+
+ <b> <pre style="border: solid thin blue; margin: 2% 10% 2% 10%; padding-left: 50px">
+ 100 STATUS_CONTINUE
+ 200 STATUS_OK
+ 205 STATUS_COMPLETE
+ 307 STATUS_REDIRECTED
+ 400 STATUS_BADREQUEST
+ 403 STATUS_FORBIDDEN
+ 404 STATUS_NOTFOUND
+ 408 STATUS_TIMEOUT
+ 417 STATUS_EXPFAILED
+ </pre> </b>
+
+ <p>
+ This list is likely to change at least a little.
+
+
+ <p>
+ The <b>CONNECT</b> message initiates the virtual connection for a client and expects a <b>STATUS</b>
+ in return. If the connection is successful, the statusCode for the <b>STATUS</b> message shall be
+ <b>STATUS_OK</b>. If the authentication fails or if there is not actual authentication information
+ within the message, the statusCode for the returned message shall be <b>STATUS_FORBIDDEN</b>.
+
+ <p>
+ If at any point the client sends a non-connect message to the server when the client is not connected or the
+ connection has timed out, the <b>STATUS</b> that is returned shall have statusCode <b>STATUS_EXPFAILED</b>.
+
+ <p>
+ The <b>DISCONNECT</b> message is sent by the client to the server to end the virtual session. The server
+ shall not respond to any disconnect messages.
+
+
+ <h1> Message Layer </h1>
+
+ <p>
+ There are currently two types of message layer messages: <b>REQUEST</b> and <b>RESULT</b>. <b>REQUEST</b>
+ messages represent application layer requests made by a client and <b>RESULT</b> messages are the servers
+ response to such <b>REQUEST</b>'s.
+
+ <p>
+ By design, all <b>CONNECT</b> and <b>REQUEST</b> messages sent by a client will be acknowledged by one or
+ more responses from the server. This is much like the SYN-ACK philosophy of TCP, however different in many
+ ways. The only guarantees made by the server are 1. you will know that we received your request and 2. you
+ will know the final outcome of your request. It is the responsibility of the actual application to send
+ the requested application data (e.g. RESULT messages, intermediate STATUS messages).
+
+
+ <p>
+ The server responses are matched to client requests by a <b>threadTrace</b>. A threadTrace is simply a
+ number and all application layer messages and STATUS messages are required to have one. (Note that the
+ threadTrace contained within a STATUS message sent in response to a CONNECT will be ignored). Currently,
+ there is no restriction on the number other than it shall be unique within a given virtual connection.
+ When the server receives a <b>REQUEST</b> message, it extracts the <b>threadTrace</b> from the message
+ and all responses to that request will contain the same <b>threadTrace</b>.
+
+ <p>
+ As mentioned above, every <b>CONNECT</b> message will be acknowledged by a single
+ <b>STATUS</b> message. <b>REQUEST</b>'s are a little more complex, however. A <b>REQUEST</b>
+ will receive one or more <b>RESULT</b>'s if the <b>REQUEST</b> warrants such a response. A <b>REQUEST</b>
+ may even receive one or more intermediate <b>STATUS</b>'s (e.g. <b>STATUS_CONTINUE</b>). (Consult the
+ documentation on the application request the client is requesting for more information on the number and
+ type of responses to that request). All <b>REQUEST</b>'s, however, regardless of other response types,
+ shall receieve as the last response a <b>STATUS</b> message with statusCode <b>STATUS_COMPLETE</b>. This
+ allows the client to wait for REQUEST "completeness" as opposed to waiting on or calculating individual
+ responses.
+
+
+ <h1> Client Pseudocode </h1>
+
+ <pre style="border: solid thin blue; margin: 0% 10% 0% 10%; padding-left: 50px">
+
+send CONNECT
+
+msg = recv()
+
+if ( msg.statusCode == STATUS_OK )
+
+ OK. continue
+
+if ( msg.statusCode == STATUS_FORBIDDEN )
+
+ handle authentication failure and attempt another connect if requested
+
+while ( more requests ) {
+
+ /* you may send multiple requests before processing any responses. For the sake
+ of this example, we will only walk through a single client request */
+
+ send REQUEST with threadTrace X
+
+ while ( response = recv ) {
+
+ if ( response.threadTrace != X )
+
+ continue/ignore
+
+ if ( response.type == STATUS )
+
+ if ( response.statusCode == STATUS_TIMEOUT or
+ response.statusCode == STATUS_REDIRECTED or
+ response.statusCode == STATUS_EXPFAILED)
+
+ resend the the request with threadTrace X because it was not honored.
+
+ if ( response.statusCode == STATUS_COMPLETE )
+
+ the request is now complete, nothing more to be done with this request
+ break out of loop
+
+ if ( response.typ == RESULT )
+
+ pass result to the application layer for processing
+
+ } // receiving
+
+} // sending
+
+
+ </pre>
+
+ <br>
+ <h1> Server Pseudocode </h1>
+
+ <pre style="border: solid thin blue; margin: 0% 10% 0% 10%; padding-left: 50px">
+
+while( message = recv() ) {
+
+ if( message.type != CONNECT )
+
+ return a STATUS with statusCode STATUS_EXPFAILED
+ start loop over
+
+ if ( message.type == CONNECT and server is unable to authenticate the client )
+
+ return a STATUS with statusCode STATUS_FORBIDDEN
+ start loop over
+
+ if ( message.type == CONNECT and server is able to authenticate user )
+
+ return STATUS with statusCode STATUS_OK and continue
+
+ while ( msg = recv() and virtual session is active ) {
+
+
+ if ( msg.type == REQUEST )
+
+ Record the threadTrace. Pass the REQUEST to the application layer for processing.
+ When the application layer has completed processing, respond to the client
+ with a STATUS message with statusCode STATUS_COMPLETE and threadTrace matching
+ the threadTrace of the REQUEST. Once the final STATUS_COMPLETE message is sent,
+ the session is over. Return to outer server loop.
+
+ /* Note: during REQUEST processing by the application layer, the application may
+ opt to send RESULT and/or STATUS messages to the client. The server side
+ transport mechanism is not concerned with these messages. The server only
+ needs to be notified when the REQUEST has been sucessfully completed. */
+
+ if( message.type == DISCONNECT )
+
+ Virtual session has ended. Return to outer loop.
+
+
+ } // Sessin loop
+
+} // Main server loop
+
+
+
+ </pre>
+
+
+ <br>
+ <h1> XML Examples</h1>
+ <br>
+
+
+ <h2> Protocol Element </h2>
+
+ <pre style="border: solid thin blue; margin: 0% 10% 0% 10%; padding-left: 50px">
+
+<oils:domainObjectAttr value="1" name="protocol"/>
+
+ </pre>
+
+ <h2> threadTrace Element </h2>
+
+ <pre style="border: solid thin blue; margin: 0% 10% 0% 10%; padding-left: 50px">
+
+<oils:domainObjectAttr value="1" name="threadTrace"/>
+
+ </pre>
+
+ <h2> Type element </h2>
+
+ <pre style="border: solid thin blue; margin: 0% 10% 0% 10%; padding-left: 50px">
+
+<oils:userAuth hashseed="237" secret="89dd8c65300d4af126cf467779ff1820" username="bill"/>
+
+ </pre>
+
+ <h2> CONNECT Message </h2>
+
+ <pre style="border: solid thin blue; margin: 0% 10% 0% 10%; padding-left: 50px">
+
+<oils:domainObjectAttr value="CONNECT" name="type"/>
+ <oils:userAuth hashseed="237" secret="89dd8c65300d4af126cf467779ff1820" username="bill"/>
+ <oils:domainObjectAttr value="1" name="threadTrace"/>
+ <oils:domainObjectAttr value="1" name="protocol"/>
+</oils:domainObject>
+
+ </pre>
+
+
+ <h2> DISCONNECT Message </h2>
+
+ <pre style="border: solid thin blue; margin: 0% 10% 0% 10%; padding-left: 50px">
+
+<oils:domainObject name="oilsMessage">
+ <oils:domainObjectAttr value="DISCONNECT" name="type"/>
+ <oils:domainObjectAttr value="0" name="threadTrace"/>
+ <oils:domainObjectAttr value="1" name="protocol"/>
+</oils:domainObject>
+
+ </pre>
+
+ <h2> STATUS Message </h2>
+
+ <pre style="border: solid thin blue; margin: 0% 10% 0% 10%; padding-left: 50px">
+
+<oils:domainObject name="oilsMessage">
+ <oils:domainObjectAttr value="STATUS" name="type"/>
+ <oils:domainObjectAttr value="0" name="threadTrace"/>
+ <oils:domainObjectAttr value="1" name="protocol"/>
+ <oils:domainObject name="oilsConnectStatus">
+ <oils:domainObjectAttr value="Connection Successful" name="status"/>
+ <oils:domainObjectAttr value="200" name="statusCode"/>
+ </oils:domainObject>
+</oils:domainObject>
+
+ </pre>
+
+ <h2> REQUEST Message </h2>
+
+ <pre style="border: solid thin blue; margin: 0% 10% 0% 10%; padding-left: 50px">
+
+<oils:domainObject name="oilsMessage">
+ <oils:domainObjectAttr value="REQUEST" name="type"/>
+ <oils:domainObjectAttr value="4" name="threadTrace"/>
+ <oils:domainObjectAttr value="1" name="protocol"/>
+ <oils:domainObject name="oilsMethod">
+ <oils:domainObjectAttr value="mult" name="method"/>
+ <oils:params>
+ <oils:param>1</oils:param>
+ <oils:param>2</oils:param>
+ </oils:params>
+ </oils:domainObject>
+</oils:domainObject>
+
+ </pre>
+
+ <h2> RESULT Message </h2>
+
+ <pre style="border: solid thin blue; margin: 0% 10% 0% 10%; padding-left: 50px">
+
+<oils:domainObject name="oilsMessage">
+ <oils:domainObjectAttr value="RESULT" name="type"/>
+ <oils:domainObjectAttr value="4" name="threadTrace"/>
+ <oils:domainObjectAttr value="1" name="protocol"/>
+ <oils:domainObject name="oilsResult">
+ <oils:domainObjectAttr value="OK" name="status"/>
+ <oils:domainObjectAttr value="200" name="statusCode"/>
+ <oils:domainObject name="oilsScalar">2</oils:domainObject>
+ </oils:domainObject>
+</oils:domainObject>
+
+ </pre>
+
+
+ </body>
+
+</html>
+
+
--- /dev/null
+#!/usr/bin/perl -w
+use strict;use warnings;
+use OpenILS::System;
+use OpenILS::DOM::Element::userAuth;
+use OpenILS::Utils::Config;
+use OpenILS::DomainObject::oilsMethod;
+use OpenILS::DomainObject::oilsPrimitive;
+use Time::HiRes qw/time/;
+use OpenILS::EX qw/:try/;
+
+$| = 1;
+
+# ----------------------------------------------------------------------------------------
+# This is a quick and dirty script to perform benchmarking against the math server.
+# Note: 1 request performs a batch of 4 queries, one for each supported method: add, sub,
+# mult, div.
+# Usage: $ perl math_bench.pl <num_requests>
+# ----------------------------------------------------------------------------------------
+
+
+my $count = $ARGV[0];
+
+unless( $count ) {
+ print "usage: ./math_bench.pl <num_requests>\n";
+ exit;
+}
+
+warn "PID: $$\n";
+
+my $config = OpenILS::Utils::Config->current;
+OpenILS::System->bootstrap_client();
+
+my $session = OpenILS::AppSession->create(
+ "math", username => 'math_bench', secret => '12345' );
+
+try {
+ if( ! ($session->connect()) ) { die "Connect timed out\n"; }
+
+} catch OpenILS::EX with {
+ my $e = shift;
+ warn "Connection Failed *\n";
+ die $e;
+}
+
+my @times;
+my %vals = ( add => 3, sub => -1, mult => 2, div => 0.5 );
+
+for my $x (1..100) {
+ if( $x % 10 ) { print ".";}
+ else{ print $x/10; };
+}
+print "\n";
+
+my $c = 0;
+
+for my $scale ( 1..$count ) {
+ for my $mname ( keys %vals ) {
+
+ my $method = OpenILS::DomainObject::oilsMethod->new( method => $mname );
+ $method->params( 1,2 );
+
+ my $req;
+ my $resp;
+ my $starttime;
+ try {
+
+ $starttime = time();
+ $req = $session->request( $method );
+ $resp = $req->recv( timeout => 10 );
+ push @times, time() - $starttime;
+
+ } catch OpenILS::EX with {
+ my $e = shift;
+ die "ERROR\n $e";
+
+ } catch Error with {
+ my $e = shift;
+ die "Caught unknown error: $e";
+ };
+
+
+ if( ! $req->complete ) { warn "\nIncomplete\n"; }
+
+
+ if ( $resp ) {
+
+ my $ret = $resp->content();
+ if( "$ret" eq $vals{$mname} ) { print "+"; }
+
+ else { print "*BAD*\n" . $resp->toString(1) . "\n"; }
+
+ } else { print "*NADA*"; }
+
+ $req->finish();
+ $c++;
+
+ }
+ print "\n[$c] \n" unless $scale % 25;
+}
+
+$session->kill_me();
+
+my $total = 0;
+
+$total += $_ for (@times);
+
+$total /= scalar(@times);
+
+print "\n\n\tAverage Round Trip Time: $total Seconds\n";
+
--- /dev/null
+#!/usr/bin/perl -w
+use strict;use warnings;
+use OpenILS::System;
+use OpenILS::Utils::Config;
+use OpenILS::DomainObject::oilsMethod;
+use OpenILS::DomainObject::oilsPrimitive;
+use OpenILS::EX qw/:try/;
+$| = 1;
+
+# ----------------------------------------------------------------------------------------
+# Simple math shell where you can test the transport system.
+# Enter simple, binary equations ony using +, -, *, and /
+# Example: # 1+1
+# Usage: % perl math_shell.pl
+# ----------------------------------------------------------------------------------------
+
+# load the config
+my $config = OpenILS::Utils::Config->current;
+
+# connect to the transport (jabber) server
+OpenILS::System->bootstrap_client();
+
+# build the AppSession object.
+my $session = OpenILS::AppSession->create(
+ "math", username => 'math_bench', secret => '12345' );
+
+# launch the shell
+print "type 'exit' or 'quit' to leave the shell\n";
+print "# ";
+while( my $request = <> ) {
+
+ chomp $request ;
+
+ # exit loop if user enters 'exit' or 'quit'
+ if( $request =~ /exit/i or $request =~ /quit/i ) { last; }
+
+ # figure out what the user entered
+ my( $a, $mname, $b ) = parse_request( $request );
+
+ if( $a =~ /error/ ) {
+ print "Parse Error. Try again. \nExample # 1+1\n";
+ next;
+ }
+
+
+ try {
+
+ # Connect to the MATH server
+ if( ! ($session->connect()) ) { die "Connect timed out\n"; }
+
+ } catch OpenILS::EX with {
+ my $e = shift;
+ die "* * Connection Failed with:\n$e";
+ };
+
+ my $method = OpenILS::DomainObject::oilsMethod->new( method => $mname );
+ $method->params( $a, $b );
+
+ my $req;
+ my $resp;
+
+ try {
+ $req = $session->request( $method );
+
+ # we know that this request only has a single reply
+ # if your expecting a 'stream' of results, you can
+ # do: while( $resp = $req->recv( timeout => 10 ) ) {}
+ $resp = $req->recv( timeout => 10 );
+
+ } catch OpenILS::EX with {
+
+ # Any transport layer or server problems will launch an exception
+ my $e = shift;
+ die "ERROR Receiving\n $e";
+
+ } catch Error with {
+
+ # something just died somethere
+ my $e = shift;
+ die "Caught unknown error: $e";
+ };
+
+ if ( $resp ) {
+ # ----------------------------------------------------------------------------------------
+ # $resp is an OpenILS::DomainObject::oilsResponse object. $resp->content() returns whatever
+ # data the object has. If the server returns an exception that we're meant to see, then
+ # the data will be an exception object. In this case, barring any exception, we know that
+ # the data is an OpenILS::DomainObject::oilsScalar object which has a value() method
+ # that returns a perl scalar. For us, that scalar is just a number.
+ # ----------------------------------------------------------------------------------------
+
+ if( UNIVERSAL::isa( $resp, "OpenILS::EX" ) ) {
+ throw $resp;
+ }
+
+ my $ret = $resp->content();
+ print $ret->value();
+ }
+
+ $req->finish();
+
+ print "\n# ";
+
+}
+
+# disconnect from the MATH server
+$session->kill_me();
+exit;
+
+# ------------------------------------------------------------------------------------
+# parse the user input string
+# returns a list of the form (first param, operation, second param)
+# These operations are what the MATH server recognizes as method names
+# ------------------------------------------------------------------------------------
+sub parse_request {
+ my $string = shift;
+ my $op;
+ my @ops;
+
+ while( 1 ) {
+
+ @ops = split( /\+/, $string );
+ if( @ops > 1 ) { $op = "add"; last; }
+
+ @ops = split( /\-/, $string );
+ if( @ops > 1 ) { $op = "sub"; last; }
+
+ @ops = split( /\*/, $string );
+ if( @ops > 1 ) { $op = "mult", last; }
+
+ @ops = split( /\//, $string );
+ if( @ops > 1 ) { $op = "div"; last; }
+
+ return ("error");
+ }
+
+ return ($ops[0], $op, $ops[1]);
+}
+
--- /dev/null
+#!/usr/bin/perl -w
+use strict;use warnings;
+use OpenILS::System;
+use OpenILS::Utils::Config;
+use OpenILS::DomainObject::oilsMethod;
+use OpenILS::DomainObject::oilsPrimitive;
+use OpenILS::EX qw/:try/;
+$| = 1;
+
+
+# ----------------------------------------------------------------------------------------
+# This script makes a single query, 1 + 2, to the the MATH test app and prints the result
+# Usage: % perl math_simple.pl
+# ----------------------------------------------------------------------------------------
+
+
+# connect to the transport (jabber) server
+OpenILS::System->bootstrap_client();
+
+# build the AppSession object.
+my $session = OpenILS::AppSession->create(
+ "math", username => 'math_bench', secret => '12345' );
+
+try {
+
+ # Connect to the MATH server
+ if( ! ($session->connect()) ) { die "Connect timed out\n"; }
+
+} catch OpenILS::EX with {
+ my $e = shift;
+ die "* * Connection Failed with:\n$e";
+};
+
+my $method = OpenILS::DomainObject::oilsMethod->new( method => "add" );
+$method->params( 1, 2 );
+
+my $req;
+my $resp;
+
+try {
+ $req = $session->request( $method );
+
+ # we know that this request only has a single reply
+ # if your expecting a 'stream' of results, you can
+ # do: while( $resp = $req->recv( timeout => 10 ) ) {}
+ $resp = $req->recv( timeout => 10 );
+
+} catch OpenILS::EX with {
+
+ # Any transport layer or server problems will launch an exception
+ my $e = shift;
+ die "ERROR Receiving\n $e";
+
+} catch Error with {
+
+ # something just died somethere
+ my $e = shift;
+ die "Caught unknown error: $e";
+};
+
+if ( $resp ) {
+ # ----------------------------------------------------------------------------------------
+ # $resp is an OpenILS::DomainObject::oilsResponse object. $resp->content() returns whatever
+ # data the object has. If the server returns an exception that we're meant to see, then
+ # the data will be an exception object. In this case, barring any exception, we know that
+ # the data is an OpenILS::DomainObject::oilsScalar object which has a value() method
+ # that returns a perl scalar. For us, that scalar is just a number.
+ # ----------------------------------------------------------------------------------------
+
+ if( UNIVERSAL::isa( $resp, "OpenILS::EX" ) ) {
+ throw $resp;
+ }
+
+ my $ret = $resp->content();
+ print "Should print 3 => " . $ret->value() . "\n";
+
+} else {
+ die "No Response from Server!\n";
+}
+
+$req->finish();
+
+# disconnect from the MATH server
+$session->kill_me();
+exit;
+
--- /dev/null
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <time.h>
+
+/* libxml stuff for the config reader */
+#include <libxml/xmlmemory.h>
+#include <libxml/parser.h>
+#include <libxml/xpath.h>
+#include <libxml/xpathInternals.h>
+#include <libxml/tree.h>
+
+#ifdef DMALLOC
+#include "dmalloc.h"
+#endif
+
+
+#ifndef GENERIC_UTILS_H
+#define GENERIC_UTILS_H
+
+
+/** Malloc's, checks for NULL, clears all memory bits and
+ * returns the pointer
+ *
+ * @param size How many bytes of memory to allocate
+ */
+inline void* safe_malloc( int size );
+
+/* 10M limit on buffers for overflow protection */
+#define BUFFER_MAX_SIZE 10485760
+
+// ---------------------------------------------------------------------------------
+// Generic growing buffer. Add data all you want
+// ---------------------------------------------------------------------------------
+struct growing_buffer_struct {
+ char *buf;
+ int n_used;
+ int size;
+};
+typedef struct growing_buffer_struct growing_buffer;
+
+growing_buffer* buffer_init( int initial_num_bytes);
+int buffer_addchar(growing_buffer* gb, char c);
+int buffer_add(growing_buffer* gb, char* c);
+int buffer_reset( growing_buffer* gb);
+char* buffer_data( growing_buffer* gb);
+int buffer_free( growing_buffer* gb );
+
+
+void log_free();
+
+// Utility method
+void get_timestamp( char buf_25chars[]);
+
+// ---------------------------------------------------------------------------------
+// Error handling interface.
+// ---------------------------------------------------------------------------------
+
+void fatal_handler( char* message, ...);
+void warning_handler( char* message, ... );
+void info_handler( char* message, ... );
+
+// ---------------------------------------------------------------------------------
+// Config file module
+// ---------------------------------------------------------------------------------
+struct config_reader_struct {
+ xmlDocPtr config_doc;
+ xmlXPathContextPtr xpathCx;
+};
+typedef struct config_reader_struct config_reader;
+config_reader* conf_reader;
+
+void config_reader_init( char* config_file );
+
+void config_reader_free();
+
+// allocastes a char*. FREE me.
+char* config_value( const char* xpath_query, ... );
+
+#endif
--- /dev/null
+#include "transport_session.h"
+#include <time.h>
+
+#ifdef DMALLOC
+#include "dmalloc.h"
+#endif
+
+#ifndef TRANSPORT_CLIENT_H
+#define TRANSPORT_CLIENT_H
+
+#define MESSAGE_LIST_HEAD 1
+#define MESSAGE_LIST_ITEM 2
+
+
+// ---------------------------------------------------------------------------
+// Represents a node in a linked list. The node holds a pointer to the next
+// node (which is null unless set), a pointer to a transport_message, and
+// and a type variable (which is not really curently necessary).
+// ---------------------------------------------------------------------------
+struct message_list_struct {
+ struct message_list_struct* next;
+ transport_message* message;
+ int type;
+};
+
+typedef struct message_list_struct transport_message_list;
+typedef struct message_list_struct transport_message_node;
+
+// ---------------------------------------------------------------------------
+// Our client struct. We manage a list of messages and a controlling session
+// ---------------------------------------------------------------------------
+struct transport_client_struct {
+ transport_message_list* m_list;
+ transport_session* session;
+};
+typedef struct transport_client_struct transport_client;
+
+// ---------------------------------------------------------------------------
+// Allocates and initializes and transport_client. This does no connecting
+// The user must call client_free(client) when finished with the allocated
+// object.
+// ---------------------------------------------------------------------------
+transport_client* client_init( char* server, int port );
+
+// ---------------------------------------------------------------------------
+// Connects to the Jabber server with the provided information. Returns 1 on
+// success, 0 otherwise.
+// ---------------------------------------------------------------------------
+int client_connect( transport_client* client,
+ char* username, char* password, char* resource, int connect_timeout );
+
+int client_disconnect( transport_client* client );
+
+// ---------------------------------------------------------------------------
+// De-allocates memory associated with a transport_client object. Users
+// must use this method when finished with a client object.
+// ---------------------------------------------------------------------------
+int client_free( transport_client* client );
+
+// ---------------------------------------------------------------------------
+// Sends the given message. The message must at least have the recipient
+// field set.
+// ---------------------------------------------------------------------------
+int client_send_message( transport_client* client, transport_message* msg );
+
+// ---------------------------------------------------------------------------
+// Returns 1 if this client is currently connected to the server, 0 otherwise
+// ---------------------------------------------------------------------------
+int client_connected( transport_client* client );
+
+// ---------------------------------------------------------------------------
+// This is the message handler required by transport_session. This handler
+// takes all incoming messages and puts them into the back of a linked list
+// of messages.
+// ---------------------------------------------------------------------------
+void client_message_handler( void* client, transport_message* msg );
+
+// ---------------------------------------------------------------------------
+// If there are any message in the message list, the 'oldest' message is
+// returned. If not, this function will wait at most 'timeout' seconds
+// for a message to arrive. Specifying -1 means that this function will not
+// return unless a message arrives.
+// ---------------------------------------------------------------------------
+transport_message* client_recv( transport_client* client, int timeout );
+
+
+#endif
--- /dev/null
+#include "libxml.h"
+
+#include "generic_utils.h"
+
+#include <string.h>
+#include <libxml/globals.h>
+#include <libxml/xmlerror.h>
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+#include <libxml/debugXML.h>
+#include <libxml/xmlmemory.h>
+
+#ifdef DMALLOC
+#include "dmalloc.h"
+#endif
+
+#ifndef TRANSPORT_MESSAGE_H
+#define TRANSPORT_MESSAGE_H
+
+
+
+// ---------------------------------------------------------------------------------
+// Jabber message object.
+// ---------------------------------------------------------------------------------
+struct transport_message_struct {
+ char* body;
+ char* subject;
+ char* thread;
+ char* recipient;
+ char* sender;
+ char* router_from;
+ char* router_to;
+ char* router_class;
+ char* router_command;
+ int is_error;
+ char* error_type;
+ int error_code;
+ int broadcast;
+ char* msg_xml; /* the entire message as XML complete with entity encoding */
+};
+typedef struct transport_message_struct transport_message;
+
+// ---------------------------------------------------------------------------------
+// Allocates and returns a transport_message. All chars are safely re-allocated
+// within this method.
+// Returns NULL on error
+// ---------------------------------------------------------------------------------
+transport_message* message_init( char* body, char* subject,
+ char* thread, char* recipient, char* sender );
+
+
+void message_set_router_info( transport_message* msg, char* router_from,
+ char* router_to, char* router_class, char* router_command, int broadcast_enabled );
+
+// ---------------------------------------------------------------------------------
+// Formats the Jabber message as XML for encoding.
+// Returns NULL on error
+// ---------------------------------------------------------------------------------
+char* message_to_xml( const transport_message* msg );
+
+
+// ---------------------------------------------------------------------------------
+// Call this to create the encoded XML for sending on the wire.
+// This is a seperate function so that encoding will not necessarily have
+// to happen on all messages (i.e. typically only occurs outbound messages).
+// ---------------------------------------------------------------------------------
+int message_prepare_xml( transport_message* msg );
+
+// ---------------------------------------------------------------------------------
+// Deallocates the memory used by the transport_message
+// Returns 0 on error
+// ---------------------------------------------------------------------------------
+int message_free( transport_message* msg );
+
+// ---------------------------------------------------------------------------------
+// Prepares the shared XML document
+// ---------------------------------------------------------------------------------
+//int message_init_xml();
+
+// ---------------------------------------------------------------------------------
+// Determines the username of a Jabber ID. This expects a pre-allocated char
+// array for the return value.
+// ---------------------------------------------------------------------------------
+void jid_get_username( const char* jid, char buf[] );
+
+// ---------------------------------------------------------------------------------
+// Determines the resource of a Jabber ID. This expects a pre-allocated char
+// array for the return value.
+// ---------------------------------------------------------------------------------
+void jid_get_resource( const char* jid, char buf[] );
+
+void set_msg_error( transport_message*, char* error_type, int error_code);
+
+#endif
--- /dev/null
+// ---------------------------------------------------------------------------------
+// Manages the Jabber session. Data is taken from the TCP object and pushed into
+// a SAX push parser as it arrives. When key Jabber documetn elements are met,
+// logic ensues.
+// ---------------------------------------------------------------------------------
+#include "libxml.h"
+#include "transport_socket.h"
+#include "transport_message.h"
+#include "generic_utils.h"
+
+#include <string.h>
+#include <libxml/globals.h>
+#include <libxml/xmlerror.h>
+#include <libxml/parser.h>
+#include <libxml/parserInternals.h> /* only for xmlNewInputFromFile() */
+#include <libxml/tree.h>
+#include <libxml/debugXML.h>
+#include <libxml/xmlmemory.h>
+
+#ifndef TRANSPORT_SESSION_H
+#define TRANSPORT_SESSION_H
+
+#define CONNECTING_1 1 /* just starting the connection to Jabber */
+#define CONNECTING_2 2 /* First <stream> packet sent and <stream> packet received from server */
+
+/* Note. these are growing buffers, so all that's necessary is a sane starting point */
+#define JABBER_BODY_BUFSIZE 4096
+#define JABBER_SUBJECT_BUFSIZE 64
+#define JABBER_THREAD_BUFSIZE 64
+#define JABBER_JID_BUFSIZE 64
+#define JABBER_STATUS_BUFSIZE 16
+
+#ifdef DMALLOC
+#include "dmalloc.h"
+#endif
+
+// ---------------------------------------------------------------------------------
+// Takes data from the socket handler and pushes it directly into the push parser
+// ---------------------------------------------------------------------------------
+void grab_incoming( void * session, char* data );
+
+// ---------------------------------------------------------------------------------
+// Callback for handling the startElement event. Much of the jabber logic occurs
+// in this and the characterHandler callbacks.
+// Here we check for the various top level jabber elements: body, iq, etc.
+// ---------------------------------------------------------------------------------
+void startElementHandler(
+ void *session, const xmlChar *name, const xmlChar **atts);
+
+// ---------------------------------------------------------------------------------
+// Callback for handling the endElement event. Updates the Jabber state machine
+// to let us know the element is over.
+// ---------------------------------------------------------------------------------
+void endElementHandler( void *session, const xmlChar *name);
+
+// ---------------------------------------------------------------------------------
+// This is where we extract XML text content. In particular, this is useful for
+// extracting Jabber message bodies.
+// ---------------------------------------------------------------------------------
+void characterHandler(
+ void *session, const xmlChar *ch, int len);
+
+void parseWarningHandler( void *session, const char* msg, ... );
+void parseErrorHandler( void *session, const char* msg, ... );
+
+// ---------------------------------------------------------------------------------
+// Tells the SAX parser which functions will be used as event callbacks
+// ---------------------------------------------------------------------------------
+static xmlSAXHandler SAXHandlerStruct = {
+ NULL, /* internalSubset */
+ NULL, /* isStandalone */
+ NULL, /* hasInternalSubset */
+ NULL, /* hasExternalSubset */
+ NULL, /* resolveEntity */
+ NULL, /* getEntity */
+ NULL, /* entityDecl */
+ NULL, /* notationDecl */
+ NULL, /* attributeDecl */
+ NULL, /* elementDecl */
+ NULL, /* unparsedEntityDecl */
+ NULL, /* setDocumentLocator */
+ NULL, /* startDocument */
+ NULL, /* endDocument */
+ startElementHandler, /* startElement */
+ endElementHandler, /* endElement */
+ NULL, /* reference */
+ characterHandler, /* characters */
+ NULL, /* ignorableWhitespace */
+ NULL, /* processingInstruction */
+ NULL, /* comment */
+ parseWarningHandler, /* xmlParserWarning */
+ parseErrorHandler, /* xmlParserError */
+ NULL, /* xmlParserFatalError : unused */
+ NULL, /* getParameterEntity */
+ NULL, /* cdataBlock; */
+ NULL, /* externalSubset; */
+ 1,
+ NULL,
+ NULL, /* startElementNs */
+ NULL, /* endElementNs */
+ NULL /* xmlStructuredErrorFunc */
+};
+
+// ---------------------------------------------------------------------------------
+// Our SAX handler pointer.
+// ---------------------------------------------------------------------------------
+static const xmlSAXHandlerPtr SAXHandler = &SAXHandlerStruct;
+
+// ---------------------------------------------------------------------------------
+// Jabber state machine. This is how we know where we are in the Jabber
+// conversation.
+// ---------------------------------------------------------------------------------
+struct jabber_state_machine_struct {
+ int connected;
+ int connecting;
+ int in_message;
+ int in_message_body;
+ int in_thread;
+ int in_subject;
+ int in_error;
+ int in_message_error;
+ int in_iq;
+ int in_presence;
+ int in_status;
+};
+typedef struct jabber_state_machine_struct jabber_machine;
+
+// ---------------------------------------------------------------------------------
+// Transport session. This maintains all the various parts of a session
+// ---------------------------------------------------------------------------------
+struct transport_session_struct {
+
+ /* our socket connection */
+ transport_socket* sock_obj;
+ /* our Jabber state machine */
+ jabber_machine* state_machine;
+ /* our SAX push parser context */
+ xmlParserCtxtPtr parser_ctxt;
+
+ /* our text buffers for holding text data */
+ growing_buffer* body_buffer;
+ growing_buffer* subject_buffer;
+ growing_buffer* thread_buffer;
+ growing_buffer* from_buffer;
+ growing_buffer* recipient_buffer;
+ growing_buffer* status_buffer;
+ growing_buffer* message_error_type;
+ int message_error_code;
+
+ /* for OILS extenstions */
+ growing_buffer* router_to_buffer;
+ growing_buffer* router_from_buffer;
+ growing_buffer* router_class_buffer;
+ growing_buffer* router_command_buffer;
+ int router_broadcast;
+
+ /* this can be anything. It will show up in the
+ callbacks for your convenience. We will *not*
+ deallocate whatever this is when we're done. That's
+ your job.
+ */
+ void* user_data;
+
+ /* the Jabber message callback */
+ void (*message_callback) ( void* user_data, transport_message* msg );
+ //void (iq_callback) ( void* user_data, transport_iq_message* iq );
+};
+typedef struct transport_session_struct transport_session;
+
+
+// ------------------------------------------------------------------
+// Allocates and initializes the necessary transport session
+// data structures.
+// ------------------------------------------------------------------
+transport_session* init_transport( char* server, int port, void* user_data );
+
+// ------------------------------------------------------------------
+// Returns the value of the given XML attribute
+// The xmlChar** construct is commonly returned from SAX event
+// handlers. Pass that in with the name of the attribute you want
+// to retrieve.
+// ------------------------------------------------------------------
+char* get_xml_attr( const xmlChar** atts, char* attr_name );
+
+// ------------------------------------------------------------------
+// Waits at most 'timeout' seconds for data to arrive from the
+// TCP handler. A timeout of -1 means to wait indefinitely.
+// ------------------------------------------------------------------
+int session_wait( transport_session* session, int timeout );
+
+// ---------------------------------------------------------------------------------
+// Sends the given Jabber message
+// ---------------------------------------------------------------------------------
+int session_send_msg( transport_session* session, transport_message* msg );
+
+// ---------------------------------------------------------------------------------
+// Returns 1 if this session is connected to the jabber server. 0 otherwise
+// ---------------------------------------------------------------------------------
+int session_connected( transport_session* );
+
+// ------------------------------------------------------------------
+// Deallocates session memory
+// ------------------------------------------------------------------
+int session_free( transport_session* session );
+
+// ------------------------------------------------------------------
+// Connects to the Jabber server. Waits at most connect_timeout
+// seconds before failing
+// ------------------------------------------------------------------
+int session_connect( transport_session* session,
+ const char* username, const char* password, const char* resource, int connect_timeout );
+
+int session_disconnect( transport_session* session );
+
+int reset_session_buffers( transport_session* session );
+
+#endif
--- /dev/null
+#include "generic_utils.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <errno.h>
+
+//---------------------------------------------------------------
+// WIN32
+//---------------------------------------------------------------
+#ifdef WIN32
+#include <Windows.h>
+#include <Winsock.h>
+#else
+
+//---------------------------------------------------------------
+// Unix headers
+//---------------------------------------------------------------
+#include <unistd.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#endif
+
+#ifdef DMALLOC
+#include "dmalloc.h"
+#endif
+
+#ifndef TRANSPORT_SOCKET_H
+#define TRANSPORT_SOCKET_H
+
+/* how many characters we read from the socket at a time */
+#ifdef _ROUTER
+#define BUFSIZE 412
+#else
+#define BUFSIZE 4096
+#endif
+
+/* we maintain the socket information */
+struct transport_socket_struct {
+ int sock_fd;
+ int connected;
+ char* server;
+ int port;
+ void * user_data;
+ /* user_data may be anything. it's whatever you wish
+ to see showing up in the callback in addition to
+ the acutal character data*/
+ void (*data_received_callback) (void * user_data, char*);
+};
+typedef struct transport_socket_struct transport_socket;
+
+int tcp_connect( transport_socket* obj );
+int tcp_send( transport_socket* obj, const char* data );
+int tcp_disconnect( transport_socket* obj );
+int tcp_wait( transport_socket* obj, int timeout );
+int tcp_connected( transport_socket* obj );
+
+/* utility methods */
+int set_fl( int fd, int flags );
+int clr_fl( int fd, int flags );
+
+
+#endif
--- /dev/null
+// in case we run on an implimentation that doesn't have "undefined";
+var undefined;
+
+function Cast (obj, class_constructor) {
+ try {
+ if (eval(class_constructor + '.prototype["class_name"]')) {
+ var template = eval("new " + class_constructor + "()");
+ // copy the methods over to 'obj'
+ for (var m in obj) {
+ if (typeof(obj[m]) != 'undefined') {
+ template[m] = obj[m];
+ }
+ }
+ obj = template;
+ }
+ } catch( E ) {
+ obj['class_name'] = function () { return class_constructor };
+ //dump( super_dump(E) + "\n");
+ } finally {
+ return obj;
+ }
+}
+
+function JSON2js (json) {
+ json = json.replace( /\/\*--\s*S\w*?\s*?\s+\w+\s*--\*\//g, 'Cast(');
+ json = json.replace( /\/\*--\s*E\w*?\s*?\s+(\w+)\s*--\*\//g, ', "$1")');
+ var obj;
+ if (json != '') {
+ eval( 'obj = ' + json );
+ }
+ obj.toString = function () { return js2JSON(this) };
+ return obj;
+}
+
+function js2JSON(arg) {
+var i, o, u, v;
+
+ switch (typeof arg) {
+ case 'object':
+ if (arg) {
+ if (arg.constructor == Array) {
+ o = '';
+ for (i = 0; i < arg.length; ++i) {
+ v = js2JSON(arg[i]);
+ if (o) {
+ o += ',';
+ }
+ if (v !== u) {
+ o += v;
+ } else {
+ o += 'null,';
+ }
+ }
+ return '[' + o + ']';
+ } else if (typeof arg.toString != 'undefined') {
+ o = '';
+ for (i in arg) {
+ v = js2JSON(arg[i]);
+ if (v !== u) {
+ if (o) {
+ o += ',';
+ }
+ o += js2JSON(i) + ':' + v;
+ }
+ }
+ var obj_start = '{';
+ var obj_end = '}';
+ try {
+ if ( arg.class_name() ) {
+ obj_start = '/*--S ' + arg.class_name() + '--*/{';
+ obj_end = '}/*--E ' + arg.class_name() + '--*/';
+ }
+ } catch( E ) {}
+ o = obj_start + o + obj_end;
+ return o;
+ } else {
+ return;
+ }
+ }
+ return 'null';
+ case 'unknown':
+ case 'undefined':
+ case 'function':
+ return u;
+ case 'string':
+ default:
+ return '"' + String(arg).replace(/(["\\])/g, '\\$1') + '"';
+ }
+}
--- /dev/null
+/*
+ * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
+ * Digest Algorithm, as defined in RFC 1321.
+ * Version 2.1 Copyright (C) Paul Johnston 1999 - 2002.
+ * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
+ * Distributed under the BSD License
+ * See http://pajhome.org.uk/crypt/md5 for more info.
+ */
+
+/*
+ * Configurable variables. You may need to tweak these to be compatible with
+ * the server-side, but the defaults work in most cases.
+ */
+var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */
+var b64pad = ""; /* base-64 pad character. "=" for strict RFC compliance */
+var chrsz = 8; /* bits per input character. 8 - ASCII; 16 - Unicode */
+
+/*
+ * These are the functions you'll usually want to call
+ * They take string arguments and return either hex or base-64 encoded strings
+ */
+function hex_md5(s){ return binl2hex(core_md5(str2binl(s), s.length * chrsz));}
+function b64_md5(s){ return binl2b64(core_md5(str2binl(s), s.length * chrsz));}
+function str_md5(s){ return binl2str(core_md5(str2binl(s), s.length * chrsz));}
+function hex_hmac_md5(key, data) { return binl2hex(core_hmac_md5(key, data)); }
+function b64_hmac_md5(key, data) { return binl2b64(core_hmac_md5(key, data)); }
+function str_hmac_md5(key, data) { return binl2str(core_hmac_md5(key, data)); }
+
+/*
+ * Perform a simple self-test to see if the VM is working
+ */
+function md5_vm_test()
+{
+ return hex_md5("abc") == "900150983cd24fb0d6963f7d28e17f72";
+}
+
+/*
+ * Calculate the MD5 of an array of little-endian words, and a bit length
+ */
+function core_md5(x, len)
+{
+ /* append padding */
+ x[len >> 5] |= 0x80 << ((len) % 32);
+ x[(((len + 64) >>> 9) << 4) + 14] = len;
+
+ var a = 1732584193;
+ var b = -271733879;
+ var c = -1732584194;
+ var d = 271733878;
+
+ for(var i = 0; i < x.length; i += 16)
+ {
+ var olda = a;
+ var oldb = b;
+ var oldc = c;
+ var oldd = d;
+
+ a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936);
+ d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586);
+ c = md5_ff(c, d, a, b, x[i+ 2], 17, 606105819);
+ b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330);
+ a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897);
+ d = md5_ff(d, a, b, c, x[i+ 5], 12, 1200080426);
+ c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341);
+ b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983);
+ a = md5_ff(a, b, c, d, x[i+ 8], 7 , 1770035416);
+ d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417);
+ c = md5_ff(c, d, a, b, x[i+10], 17, -42063);
+ b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162);
+ a = md5_ff(a, b, c, d, x[i+12], 7 , 1804603682);
+ d = md5_ff(d, a, b, c, x[i+13], 12, -40341101);
+ c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290);
+ b = md5_ff(b, c, d, a, x[i+15], 22, 1236535329);
+
+ a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510);
+ d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632);
+ c = md5_gg(c, d, a, b, x[i+11], 14, 643717713);
+ b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302);
+ a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691);
+ d = md5_gg(d, a, b, c, x[i+10], 9 , 38016083);
+ c = md5_gg(c, d, a, b, x[i+15], 14, -660478335);
+ b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848);
+ a = md5_gg(a, b, c, d, x[i+ 9], 5 , 568446438);
+ d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690);
+ c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961);
+ b = md5_gg(b, c, d, a, x[i+ 8], 20, 1163531501);
+ a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467);
+ d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784);
+ c = md5_gg(c, d, a, b, x[i+ 7], 14, 1735328473);
+ b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734);
+
+ a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558);
+ d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463);
+ c = md5_hh(c, d, a, b, x[i+11], 16, 1839030562);
+ b = md5_hh(b, c, d, a, x[i+14], 23, -35309556);
+ a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060);
+ d = md5_hh(d, a, b, c, x[i+ 4], 11, 1272893353);
+ c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632);
+ b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640);
+ a = md5_hh(a, b, c, d, x[i+13], 4 , 681279174);
+ d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222);
+ c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979);
+ b = md5_hh(b, c, d, a, x[i+ 6], 23, 76029189);
+ a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487);
+ d = md5_hh(d, a, b, c, x[i+12], 11, -421815835);
+ c = md5_hh(c, d, a, b, x[i+15], 16, 530742520);
+ b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651);
+
+ a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844);
+ d = md5_ii(d, a, b, c, x[i+ 7], 10, 1126891415);
+ c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905);
+ b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055);
+ a = md5_ii(a, b, c, d, x[i+12], 6 , 1700485571);
+ d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606);
+ c = md5_ii(c, d, a, b, x[i+10], 15, -1051523);
+ b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799);
+ a = md5_ii(a, b, c, d, x[i+ 8], 6 , 1873313359);
+ d = md5_ii(d, a, b, c, x[i+15], 10, -30611744);
+ c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380);
+ b = md5_ii(b, c, d, a, x[i+13], 21, 1309151649);
+ a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070);
+ d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379);
+ c = md5_ii(c, d, a, b, x[i+ 2], 15, 718787259);
+ b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551);
+
+ a = safe_add(a, olda);
+ b = safe_add(b, oldb);
+ c = safe_add(c, oldc);
+ d = safe_add(d, oldd);
+ }
+ return Array(a, b, c, d);
+
+}
+
+/*
+ * These functions implement the four basic operations the algorithm uses.
+ */
+function md5_cmn(q, a, b, x, s, t)
+{
+ return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s),b);
+}
+function md5_ff(a, b, c, d, x, s, t)
+{
+ return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t);
+}
+function md5_gg(a, b, c, d, x, s, t)
+{
+ return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t);
+}
+function md5_hh(a, b, c, d, x, s, t)
+{
+ return md5_cmn(b ^ c ^ d, a, b, x, s, t);
+}
+function md5_ii(a, b, c, d, x, s, t)
+{
+ return md5_cmn(c ^ (b | (~d)), a, b, x, s, t);
+}
+
+/*
+ * Calculate the HMAC-MD5, of a key and some data
+ */
+function core_hmac_md5(key, data)
+{
+ var bkey = str2binl(key);
+ if(bkey.length > 16) bkey = core_md5(bkey, key.length * chrsz);
+
+ var ipad = Array(16), opad = Array(16);
+ for(var i = 0; i < 16; i++)
+ {
+ ipad[i] = bkey[i] ^ 0x36363636;
+ opad[i] = bkey[i] ^ 0x5C5C5C5C;
+ }
+
+ var hash = core_md5(ipad.concat(str2binl(data)), 512 + data.length * chrsz);
+ return core_md5(opad.concat(hash), 512 + 128);
+}
+
+/*
+ * Add integers, wrapping at 2^32. This uses 16-bit operations internally
+ * to work around bugs in some JS interpreters.
+ */
+function safe_add(x, y)
+{
+ var lsw = (x & 0xFFFF) + (y & 0xFFFF);
+ var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
+ return (msw << 16) | (lsw & 0xFFFF);
+}
+
+/*
+ * Bitwise rotate a 32-bit number to the left.
+ */
+function bit_rol(num, cnt)
+{
+ return (num << cnt) | (num >>> (32 - cnt));
+}
+
+/*
+ * Convert a string to an array of little-endian words
+ * If chrsz is ASCII, characters >255 have their hi-byte silently ignored.
+ */
+function str2binl(str)
+{
+ var bin = Array();
+ var mask = (1 << chrsz) - 1;
+ for(var i = 0; i < str.length * chrsz; i += chrsz)
+ bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (i%32);
+ return bin;
+}
+
+/*
+ * Convert an array of little-endian words to a string
+ */
+function binl2str(bin)
+{
+ var str = "";
+ var mask = (1 << chrsz) - 1;
+ for(var i = 0; i < bin.length * 32; i += chrsz)
+ str += String.fromCharCode((bin[i>>5] >>> (i % 32)) & mask);
+ return str;
+}
+
+/*
+ * Convert an array of little-endian words to a hex string.
+ */
+function binl2hex(binarray)
+{
+ var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
+ var str = "";
+ for(var i = 0; i < binarray.length * 4; i++)
+ {
+ str += hex_tab.charAt((binarray[i>>2] >> ((i%4)*8+4)) & 0xF) +
+ hex_tab.charAt((binarray[i>>2] >> ((i%4)*8 )) & 0xF);
+ }
+ return str;
+}
+
+/*
+ * Convert an array of little-endian words to a base-64 string
+ */
+function binl2b64(binarray)
+{
+ var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+ var str = "";
+ for(var i = 0; i < binarray.length * 4; i += 3)
+ {
+ var triplet = (((binarray[i >> 2] >> 8 * ( i %4)) & 0xFF) << 16)
+ | (((binarray[i+1 >> 2] >> 8 * ((i+1)%4)) & 0xFF) << 8 )
+ | ((binarray[i+2 >> 2] >> 8 * ((i+2)%4)) & 0xFF);
+ for(var j = 0; j < 4; j++)
+ {
+ if(i * 8 + j * 6 > binarray.length * 32) str += b64pad;
+ else str += tab.charAt((triplet >> 6*(3-j)) & 0x3F);
+ }
+ }
+ return str;
+}
--- /dev/null
+/** @file oils_app_session.js
+ * @brief AppRequest and AppSession.
+ * The AppSession class does most of the communication work and the AppRequest
+ * contains the top level client API.
+ */
+
+/** The default wait time when a client calls recv. It
+ * may be overrided by passing in a value to the recv method
+ */
+AppRequest.DEF_RECV_TIMEOUT = 10000;
+
+/** Provide a pre-built AppSession object and the payload of the REQUEST
+ * message you wish to send
+ */
+function AppRequest( session, payload ) {
+
+ /** Our controling session */
+ this.session = session;
+
+ /** This requests message thread trace */
+ this.thread_trace = null;
+
+ /** This request REQUEST payload */
+ this.payload = payload;
+
+ /** True if this request has completed is request cycle */
+ this.is_complete = false;
+
+ /** Stores responses received from requests */
+ this.recv_queue = new Array();
+}
+
+/** returns true if this AppRequest has completed its request cycle. */
+AppRequest.prototype.complete = function() {
+ if( this.is_complete ) { return true; }
+ this.session.queue_wait(0);
+ return this.is_complete;
+}
+
+/** When called on an AppRequest object, its status will be
+ * set to complete regardless of its previous status
+ */
+AppRequest.prototype.set_complete = function() {
+ this.is_complete = true;
+}
+
+/** Returns the current thread trace */
+AppRequest.prototype.get_thread_trace = function() {
+ return this.thread_trace;
+}
+
+/** Pushes some payload onto the recv queue */
+AppRequest.prototype.push_queue = function( payload ) {
+ this.recv_queue.push( payload );
+}
+
+/** Returns the current payload of this request */
+AppRequest.prototype.get_payload = function() {
+ return this.payload;
+}
+
+/** Removes this request from the our AppSession's request bucket
+ * Call this method when you are finished with a particular request
+ */
+AppRequest.prototype.finish = function() {
+ this.session.remove_request( this );
+}
+
+
+/** Retrieves the current thread trace from the associated AppSession object,
+ * increments that session's thread trace, sets this AppRequest's thread trace
+ * to the new value. The request is then sent.
+ */
+AppRequest.prototype.make_request = function() {
+ var tt = this.session.get_thread_trace();
+ this.session.set_thread_trace( ++tt );
+ this.thread_trace = tt;
+ this.session.add_request( this );
+ this.session.send( oilsMessage.REQUEST, tt, this.payload );
+}
+
+/** Checks the receive queue for message payloads. If any are found, the first
+ * is returned. Otherwise, this method will wait at most timeout seconds for
+ * a message to appear in the receive queue. Once it arrives it is returned.
+ * If no messages arrive in the timeout provided, null is returned.
+
+ * NOTE: timeout is in * milliseconds *
+ */
+
+AppRequest.prototype.recv = function( /*int*/ timeout ) {
+
+
+ if( this.recv_queue.length > 0 ) {
+ return this.recv_queue.shift();
+ }
+
+ //if( this.complete ) { return null; }
+
+ if( timeout == null ) {
+ timeout = AppRequest.DEF_RECV_TIMEOUT;
+ }
+
+ while( timeout > 0 ) {
+
+ var start = new Date().getTime();
+ this.session.queue_wait( timeout );
+
+ if( this.recv_queue.length > 0 ) {
+ return this.recv_queue.shift();
+ }
+
+ // shortcircuit the call if we're already complete
+ if( this.complete() ) { return null; }
+
+ new Logger().debug( "AppRequest looping in recv "
+ + this.get_thread_trace() + " with timeout " + timeout, Logger.DEBUG );
+
+ var end = new Date().getTime();
+ timeout -= ( end - start );
+ }
+
+ return null;
+}
+
+/** Resend this AppRequest's REQUEST message, useful for recovering
+ * from disconnects, etc.
+ */
+AppRequest.prototype.resend = function() {
+
+ new Logger().debug( "Resending msg with thread trace: "
+ + this.get_thread_trace(), Logger.DEBUG );
+ this.session.send( oilsMessage.REQUEST, this.get_thread_trace(), this.payload );
+}
+
+
+
+
+// -----------------------------------------------------------------------------
+// -----------------------------------------------------------------------------
+// AppSession code
+// -----------------------------------------------------------------------------
+
+/** Global cach of AppSession objects */
+AppSession.session_cache = new Array();
+
+// -----------------------------------------------------------------------------
+// Session states
+// -----------------------------------------------------------------------------
+/** True when we're attempting to connect to a remte service */
+AppSession.CONNECTING = 0;
+/** True when we have successfully connected to a remote service */
+AppSession.CONNECTED = 1;
+/** True when we have been disconnected from a remote service */
+AppSession.DISCONNECTED = 2;
+/** The current default method protocol */
+AppSession.PROTOCOL = 1;
+
+/** Our connection with the outside world */
+AppSession.transport_handle = null;
+
+
+/** Returns the session with the given session id */
+AppSession.find_session = function(session_id) {
+ return AppSession.session_cache[session_id];
+}
+
+/** Adds the given session to the global cache */
+AppSession.push_session = function(session) {
+ AppSession.session_cache[session.get_session_id()] = session;
+}
+
+/** Deletes the session with the given session id from the global cache */
+AppSession.delete_session = function(session_id) {
+ AppSession.session_cache[session_id] = null;
+}
+
+/** Builds a new session.
+ * @param remote_service The remote service we want to make REQUEST's of
+ */
+function AppSession( username, password, remote_service ) {
+
+
+ /** Our logger object */
+ this.logger = new Logger();
+
+ random_num = Math.random() + "";
+ random_num.replace( '.', '' );
+
+ /** Our session id */
+ this.session_id = new Date().getTime() + "" + random_num;
+
+ this.auth = new userAuth( username, password );
+
+ /** Our AppRequest queue */
+ this.request_queue = new Array();
+
+ /** Our connectivity state */
+ this.state = AppSession.DISCONNECTED;
+
+ var config = new Config();
+
+ /** The remote ID of the service we are communicating with as retrieved
+ * from the config file
+ */
+ this.orig_remote_id = config.get_value( "remote_service/" + remote_service );
+ if( ! this.orig_remote_id ) {
+ throw new oils_ex_config( "No remote service id for: " + remote_service );
+ }
+
+ /** The current remote ID of the service we are communicating with */
+ this.remote_id = this.orig_remote_id;
+
+ /** Our current request threadtrace, which is incremented with each
+ * newly sent AppRequest */
+ this.thread_trace = 0;
+
+ /** Our queue of AppRequest objects */
+ this.req_queue = new Array();
+
+ /** Our queue of AppRequests that are awaiting a resend of their payload */
+ this.resend_queue = new Array();
+
+ // Build the transport_handle if if doesn't already exist
+ if( AppSession.transport_handle == null ) {
+ this.build_transport();
+ }
+
+ AppSession.push_session( this );
+
+}
+
+/** The transport implementation is loaded from the config file and magically
+ * eval'ed into an object. All connection settings come from the client
+ * config.
+ * * This should only be called by the AppSession constructor and only when
+ * the transport_handle is null.
+ */
+AppSession.prototype.build_transport = function() {
+
+ var config = new Config();
+ var transport_impl = config.get_value( "transport/transport_impl" );
+ if( ! transport_impl ) {
+ throw new oils_ex_config( "No transport implementation defined in config file" );
+ }
+
+ var username = config.get_value( "transport/username" );
+ var password = config.get_value( "transport/password" );
+ var this_host = config.get_value( "system/hostname" );
+ var resource = this_host + "_" + new Date().getTime();
+ var server = config.get_value( "transport/primary" );
+ var port = config.get_value( "transport/port" );
+ var tim = config.get_value( "transport/connect_timeout" );
+ var timeout = tim * 1000;
+
+ var eval_string =
+ "AppSession.transport_handle = new " + transport_impl + "( username, password, resource );";
+
+ eval( eval_string );
+
+ if( AppSession.transport_handle == null ) {
+ throw new oils_ex_config( "Transport implementation defined in config file is not valid" );
+ }
+
+ if( !AppSession.transport_handle.connect( server, port, timeout ) ) {
+ throw new oils_ex_transport( "Connection attempt to remote service timed out" );
+ }
+
+ if( ! AppSession.transport_handle.connected() ) {
+ throw new oils_ex_transport( "AppSession is unable to connect to the remote service" );
+ }
+}
+
+
+/** Adds the given AppRequest object to this AppSession's request queue */
+AppSession.prototype.add_request = function( req_obj ) {
+ new Logger().debug( "Adding AppRequest: " + req_obj.get_thread_trace(), Logger.DEBUG );
+ this.req_queue[req_obj.get_thread_trace()] = req_obj;
+}
+
+/** Removes the AppRequest object from this AppSession's request queue */
+AppSession.prototype.remove_request = function( req_obj ) {
+ this.req_queue[req_obj.get_thread_trace()] = null;
+}
+
+/** Returns the AppRequest with the given thread_trace */
+AppSession.prototype.get_request = function( thread_trace ) {
+ return this.req_queue[thread_trace];
+}
+
+
+/** Returns this AppSession's session id */
+AppSession.prototype.get_session_id = function() {
+ return this.session_id;
+}
+
+/** Resets the remote_id for the transport to the original remote_id retrieved
+ * from the config file
+ */
+AppSession.prototype.reset_remote = function() {
+ this.remote_id = this.orig_remote_id;
+}
+
+/** Returns the current message thread trace */
+AppSession.prototype.get_thread_trace = function() {
+ return this.thread_trace;
+}
+
+/** Sets the current thread trace */
+AppSession.prototype.set_thread_trace = function( tt ) {
+ this.thread_trace = tt;
+}
+
+/** Returns the state that this session is in (i.e. CONNECTED) */
+AppSession.prototype.get_state = function() {
+ return this.state;
+}
+
+/** Sets the session state. The state should be one of the predefined
+ * session AppSession session states.
+ */
+AppSession.prototype.set_state = function(state) {
+ this.state = state;
+}
+
+/** Returns the current remote_id for this session */
+AppSession.prototype.get_remote_id = function() {
+ return this.remote_id;
+}
+
+/** Sets the current remote_id for this session */
+AppSession.prototype.set_remote_id = function( id ) {
+ this.remote_id = id;
+}
+
+/** Pushes an AppRequest object onto the resend queue */
+AppSession.prototype.push_resend = function( app_request ) {
+ this.resend_queue.push( app_request );
+}
+
+/** Destroys the current session. This will disconnect from the
+ * remote service, remove all AppRequests from the request
+ * queue, and finally remove this session from the global cache
+ */
+AppSession.prototype.destroy = function() {
+
+ new Logger().debug( "Destroying AppSession: " + this.get_session_id(), Logger.DEBUG );
+
+ // disconnect from the remote service
+ if( this.get_state() != AppSession.DISCONNECTED ) {
+ this.disconnect();
+ }
+ // remove us from the global cache
+ AppSession.delete_session( this.get_session_id() );
+
+ // Remove app request references
+ for( var index in this.req_queue ) {
+ this.req_queue[index] = null;
+ }
+}
+
+/** This forces a resend of all AppRequests that are currently
+ * in the resend queue
+ */
+AppSession.prototype.flush_resend = function() {
+
+ if( this.resend_queue.length > 0 ) {
+ new Logger().debug( "Resending "
+ + this.resend_queue.length + " messages", Logger.INFO );
+ }
+
+ var req = this.resend_queue.shift();
+
+ while( req != null ) {
+ req.resend();
+ req = this.resend_queue.shift();
+ }
+}
+
+/** This method tracks down the AppRequest with the given thread_trace and
+ * pushes the payload into that AppRequest's recieve queue.
+ */
+AppSession.prototype.push_queue = function( dom_payload, thread_trace ) {
+
+ var req = this.get_request( thread_trace );
+ if( ! req ) {
+ new Logger().debug( "No AppRequest exists for TT: " + thread_trace, Logger.ERROR );
+ return;
+ }
+ req.push_queue( dom_payload );
+}
+
+
+/** Connect to the remote service. The connect timeout is read from the config.
+ * This method returns null if the connection fails. It returns a reference
+ * to this AppSession object otherwise.
+ */
+AppSession.prototype.connect = function() {
+
+ if( this.get_state() == AppSession.CONNECTED ) {
+ return this;
+ }
+
+ var config = new Config();
+ var rem = config.get_value( "transport/connect_timeout" );
+ if( ! rem ) {
+ throw new oils_ex_config( "Unable to retreive timeout value from config" );
+ }
+
+ var remaining = rem * 1000; // milliseconds
+
+ this.reset_remote();
+ this.set_state( AppSession.CONNECTING );
+ this.send( oilsMessage.CONNECT, 0, "" );
+
+ new Logger().debug( "CONNECTING with timeout: " + remaining, Logger.DEBUG );
+
+ while( this.get_state() != AppSession.CONNECTED && remaining > 0 ) {
+
+ var starttime = new Date().getTime();
+ this.queue_wait( remaining );
+ var endtime = new Date().getTime();
+ remaining -= (endtime - starttime);
+ }
+
+ if( ! this.get_state() == AppSession.CONNECTED ) {
+ return null;
+ }
+
+ return this;
+}
+
+/** Disconnects from the remote service */
+AppSession.prototype.disconnect = function() {
+
+ if( this.get_state() == AppSession.DISCONNECTED ) {
+ return;
+ }
+
+ this.send( oilsMessage.DISCONNECT, this.get_thread_trace(), "" );
+ this.set_state( AppSession.DISCONNECTED );
+ this.reset_remote();
+}
+
+
+/** Builds a new message with the given type and thread_trace. If the message
+ * is a REQUEST, then the payload is added as well.
+ * This method will verify that the session is in the CONNECTED state before
+ * sending any REQUEST's by attempting to do a connect.
+ *
+ * Note: msg_type and thread_trace must be provided.
+ */
+AppSession.prototype.send = function( msg_type, thread_trace, payload ) {
+
+ if( msg_type == null || thread_trace == null ) {
+ throw new oils_ex_args( "Not enough arguments provided to AppSession.send method" );
+ }
+
+ // go ahead and make sure there's nothing new to process
+ this.queue_wait(0);
+
+ var msg;
+ if( msg_type == oilsMessage.CONNECT ) {
+ msg = new oilsMessage( msg_type, AppSession.PROTOCOL, this.auth );
+ } else {
+ msg = new oilsMessage( msg_type, AppSession.PROTOCOL );
+ }
+
+ msg.setThreadTrace( thread_trace );
+
+ if( msg_type == oilsMessage.REQUEST ) {
+ if( ! payload ) {
+ throw new oils_ex_args( "No payload provided for REQUEST message in AppSession.send" );
+ }
+ msg.add( payload );
+ }
+
+
+ // Make sure we're connected
+ if( (msg_type != oilsMessage.DISCONNECT) && (msg_type != oilsMessage.CONNECT) &&
+ (this.get_state() != AppSession.CONNECTED) ) {
+ if( ! this.connect() ) {
+ throw new oils_ex_session( this.get_session_id() + " | Unable to connect to remote service after redirect" );
+ }
+ }
+
+ this.logger.debug( "AppSession sending tt: "
+ + thread_trace + " to " + this.get_remote_id()
+ + " " + msg_type , Logger.INFO );
+
+ AppSession.transport_handle.send( this.get_remote_id(), this.get_session_id(), msg.toString(true) );
+
+}
+
+
+/** Waits up to 'timeout' milliseconds for some data to arrive.
+ * Any data that arrives will be process according to its
+ * payload and message type. This method will return after
+ * any data has arrived.
+ * @param timeout How many milliseconds to wait or data to arrive
+ */
+AppSession.prototype.queue_wait = function( timeout ) {
+ this.flush_resend(); // necessary if running parallel sessions
+ new Logger().debug( "In queue_wait " + timeout, Logger.DEBUG );
+ var tran_msg = AppSession.transport_handle.process_msg( timeout );
+ this.flush_resend();
+}
+
+
+
--- /dev/null
+/** @file oils_config.js
+ * Config code and Logger code
+ * The config module is simple. It parses an xml config file and
+ * the values from the file are accessed via XPATH queries.
+ */
+
+/** Searches up from Mozilla's chrome directory for the client
+ * config file and returns the file object
+ */
+function get_config_file() {
+
+ var dirService = Components.classes["@mozilla.org/file/directory_service;1"].
+ getService( Components.interfaces.nsIProperties );
+
+ chromeDir = dirService.get( "AChrom", Components.interfaces.nsIFile );
+ chromeDir.append("evergreen");
+ chromeDir.append("content");
+ chromeDir.append("conf");
+ chromeDir.append("client_config.xml");
+ return chromeDir;
+
+}
+
+
+/** Constructor. You may provide an optional path to the config file. **/
+function Config( config_file ) {
+
+ if( Config.config != null ) { return Config.config; }
+
+ config_file = get_config_file();
+
+ // ------------------------------------------------------------------
+ // Grab the data from the config file
+ // ------------------------------------------------------------------
+ var data = "";
+ var fstream = Components.classes["@mozilla.org/network/file-input-stream;1"]
+ .createInstance(Components.interfaces.nsIFileInputStream);
+
+ var sstream = Components.classes["@mozilla.org/scriptableinputstream;1"]
+ .createInstance(Components.interfaces.nsIScriptableInputStream);
+
+ fstream.init(config_file, 1, 0, false);
+ sstream.init(fstream);
+ data += sstream.read(-1);
+
+ sstream.close();
+ fstream.close();
+
+
+
+ var DOMParser = new Components.Constructor(
+ "@mozilla.org/xmlextras/domparser;1", "nsIDOMParser" );
+
+ this.config_doc = new DOMParser().parseFromString( data, "text/xml" );
+
+ Config.config = this;
+
+}
+
+/** Returns the value stored in the config file found with the
+ * given xpath expression
+ * E.g. config.get_value( "/oils_config/dirs/log_dir" );
+ * Note, the 'oils_config' node is the base of the xpath expression,
+ * so something like this will also work:
+ * config.get_value( "dirs/log_dir" );
+ */
+Config.prototype.get_value = function( xpath_query ) {
+
+ var evaluator = Components.classes["@mozilla.org/dom/xpath-evaluator;1"].
+ createInstance( Components.interfaces.nsIDOMXPathEvaluator );
+
+ var xpath_obj = evaluator.evaluate( xpath_query, this.config_doc.documentElement, null, 0, null );
+ if( ! xpath_obj ) { return null; }
+
+ var node = xpath_obj.iterateNext();
+ if( node == null ) {
+ throw new oils_ex_config( "No config option matching " + xpath_query );
+ }
+
+ return node.firstChild.nodeValue;
+}
+
+
+
+
+
+
+// ------------------------------------------------------------------
+// Logger code.
+// ------------------------------------------------------------------
+/** The global logging object */
+Logger.logger = null;
+/** No logging level */
+Logger.NONE = 0;
+/** Error log level */
+Logger.ERROR = 1;
+/** Info log level */
+Logger.INFO = 2;
+/* Debug log level */
+Logger.DEBUG = 3;
+
+/** There exists a single logger object that all components share.
+ * Calling var logger = new Logger() will return the same one
+ * with each call. This is so we only need one file handle for
+ * each type of log file.
+ */
+function Logger() {
+
+ if( Logger.logger != null ) { return Logger.logger }
+
+ var config = new Config();
+ this.log_level = config.get_value( "system/log_level" );
+
+ this.stdout_log = config.get_value( "system/stdout_log" );
+
+ if( ! this.stdout_log || this.stdout_log < 0 || this.stdout_log > 2 ) {
+ throw new oils_ex_config( "stdout_log setting is invalid: " + this.stdout_log +
+ ". Should be 0, 1, or 2." );
+ }
+
+ // ------------------------------------------------------------------
+ // Load up all of the log files
+ // ------------------------------------------------------------------
+ var transport_file = config.get_value( "logs/transport" );
+ if( transport_file == null ) {
+ throw new oils_ex_config( "Unable to load transport log file: 'logs/transport'" );
+ }
+
+ var debug_file = config.get_value( "logs/debug" );
+ if( debug_file == null ) {
+ throw new oils_ex_config( "Unable to load debug log file: 'logs/debug'" );
+ }
+
+ var error_file = config.get_value( "logs/error" );
+ if( error_file == null ) {
+ throw new oils_ex_config( "Unable to load debug log file: 'logs/error'" );
+ }
+
+
+ // ------------------------------------------------------------------
+ // Build the file objects
+ // ------------------------------------------------------------------
+ var transport_file_obj = Logger.get_log_file( transport_file );
+
+ var debug_file_obj = Logger.get_log_file( debug_file );
+
+ var error_file_obj = Logger.get_log_file( error_file );
+
+
+ // ------------------------------------------------------------------
+ // Build all of the file stream objects
+ // ------------------------------------------------------------------
+ this.transport_stream = Components.classes["@mozilla.org/network/file-output-stream;1"]
+ .createInstance(Components.interfaces.nsIFileOutputStream);
+
+ this.debug_stream = Components.classes["@mozilla.org/network/file-output-stream;1"]
+ .createInstance(Components.interfaces.nsIFileOutputStream);
+
+ this.error_stream = Components.classes["@mozilla.org/network/file-output-stream;1"]
+ .createInstance(Components.interfaces.nsIFileOutputStream);
+
+ // ------------------------------------------------------------------
+ // Init all of the streams
+ // use 0x02 | 0x10 to open file for appending.
+ // ------------------------------------------------------------------
+ this.transport_stream.init(transport_file_obj, 0x02 | 0x10 | 0x08, 0664, 0 );
+ this.debug_stream.init( debug_file_obj, 0x02 | 0x10 | 0x08, 0664, 0 );
+ this.error_stream.init( error_file_obj, 0x02 | 0x10 | 0x08, 0664, 0 );
+
+ Logger.logger = this;
+
+}
+
+/** Internal. Returns a XPCOM nsIFile object for the log file we're interested in */
+Logger.get_log_file = function( log_name ) {
+
+ var dirService = Components.classes["@mozilla.org/file/directory_service;1"].
+ getService( Components.interfaces.nsIProperties );
+
+ logFile = dirService.get( "AChrom", Components.interfaces.nsIFile );
+ logFile.append("evergreen");
+ logFile.append("content");
+ logFile.append("log");
+ logFile.append( log_name );
+
+ if( ! logFile.exists() ) {
+ logFile.create( 0, 0640 );
+ }
+
+ return logFile;
+}
+
+
+
+/** Internal. Builds a log message complete with data, etc. */
+Logger.prototype.build_string = function( message, level ) {
+
+ if( ! (message && level) ) { return null; }
+
+ var lev = "INFO";
+ if( level == Logger.ERROR ) { lev = "ERROR"; }
+ if( level == Logger.DEBUG ) { lev = "DEBUG"; }
+
+ var date = new Date();
+ var year = date.getYear();
+ year += 1900;
+
+ var month = ""+date.getMonth();
+ if(month.length==1) {month="0"+month;}
+ var day = ""+date.getDate();
+ if(day.length==1) {day="0"+day;}
+ var hour = ""+date.getHours();
+ if(hour.length== 1){hour="0"+hour;}
+ var min = ""+date.getMinutes();
+ if(min.length==1){min="0"+min;}
+ var sec = ""+date.getSeconds();
+ if(sec.length==1){sec="0"+sec;}
+ var mil = ""+date.getMilliseconds();
+ if(mil.length==1){sec="0"+sec;}
+
+ var date_string = year + "-" + month + "-" + day + " " +
+ hour + ":" + min + ":" + sec + "." + mil;
+
+ var str_array = message.split('\n');
+ var ret_array = new Array();
+ for( var i in str_array ) {
+ ret_str = "[" + date_string + "] " + lev + " " + str_array[i] + "\n";
+ ret_array.push( ret_str );
+ }
+
+ var line = "-------------------------\n";
+ ret_array.unshift( line );
+
+ return ret_array;
+}
+
+/** Internal. Does the actual writing */
+Logger.prototype._log = function( data, stream, level ) {
+
+ if( ! data ) { return; }
+ if( ! stream ) {
+ throw oils_ex_logger( "No file stream open for log message: " + data );
+ }
+ if( ! level ) { level = Logger.DEBUG; }
+
+ if( level > this.log_level ) { return; }
+ var str_array = this.build_string( data, level );
+ if( ! str_array ) { return; }
+
+ for( var i in str_array ) {
+ if( this.stdout_log > 0 ) { dump( str_array[i] ); }
+ if( this.stdout_log < 2 ) { stream.write( str_array[i], str_array[i].length ); }
+ }
+
+ // write errors to the error log if they were destined for anywhere else
+ if( level == Logger.ERROR && stream != this.error_stream ) {
+ for( var i in str_array ) {
+ if( this.stdout_log > 0 ) { dump( str_array[i] ); }
+ if( this.stdout_log < 2 ) { this.error_stream.write( str_array[i], str_array[i].length ); }
+ }
+ }
+}
+
+
+
+/** Writes the message to the error log */
+Logger.prototype.error = function( message, level ) {
+ this._log( message, this.error_stream, level );
+}
+
+
+/** Writes to the debug log */
+Logger.prototype.debug = function( message, level ) {
+ this._log( message, this.debug_stream, level );
+}
+
+
+/** Writes to the transport log */
+Logger.prototype.transport = function( message, level ) {
+ this._log( message, this.transport_stream, level );
+}
--- /dev/null
+/** @file oils_dom_element.js
+ * -----------------------------------------------------------------------------
+ * This file holds all of the core OILS DOM elements.
+ * -----------------------------------------------------------------------------
+
+ * -----------------------------------------------------------------------------
+ * Make sure you load md5.js into your script/html/etc. before loading this
+ * file
+ * -----------------------------------------------------------------------------
+ *
+ * -----------------------------------------------------------------------------
+ * 1. Use these if you are running via xpcshell, but not if you are running
+ * directly from mozilla.
+ * 2. BTW. XMLSerializer doesn't like being run outside of mozilla, so
+ * toString() fails.
+ *
+ * var DOMParser = new Components.Constructor(
+ * "@mozilla.org/xmlextras/domparser;1", "nsIDOMParser" );
+ *
+ * var XMLSerializer = new Components.Constructor(
+ * "@mozilla.org/xmlextras/xmlserializer;1", "nsIDOMSerializer" );
+ * -----------------------------------------------------------------------------
+ *
+ *
+ */
+
+/**
+ * -----------------------------------------------------------------------------
+ * DOM - Top level generic class
+ * -----------------------------------------------------------------------------
+ */
+
+function DOM() {
+ this.doc = null;
+ this.root = null;
+ this.element = null;
+}
+
+/** Returns a string representation of the XML doc. If full_bool
+ * is true, it will return the entire doc and not just the relevant
+ * portions (i.e. our object's element).
+ */
+DOM.prototype.toString = function(full_bool) {
+ if( full_bool ) {
+ return new XMLSerializer().serializeToString(this.doc);
+ } else {
+ return new XMLSerializer().serializeToString(this.element);
+ }
+}
+
+/** Initializes a document and sets this.doc, this.root and this.element */
+DOM.prototype.init = function( elementName ) {
+
+ this.doc = new DOMParser().parseFromString(
+ "<?xml version='1.0' encoding='UTF-8'?><oils:root xmlns:oils='http://open-ils.org/namespaces/oils_v1'></oils:root>", "text/xml" );
+ this.root = this.doc.documentElement;
+ this.element = this.doc.createElement( "oils:" + elementName );
+ return this.element;
+}
+
+/** Good for creating objects from raw xml. This builds a new doc and inserts
+ * the node provided. So you can build a dummy object, then call replaceNode
+ * with the new xml to build an object from XML on the fly.
+ */
+DOM.prototype._replaceNode = function( name, new_element ) {
+
+ var node = this.doc.importNode( new_element, true );
+
+ if( node.nodeName != "oils:" + name ) {
+ throw new oils_ex_dom( "Invalid Tag to " + name + "::replaceNode()" );
+ }
+
+ this.doc = new DOMParser().parseFromString(
+ "<?xml version='1.0' encoding='UTF-8'?><oils:root xmlns:oils='http://open-ils.org/namespaces/oils_v1'></oils:root>", "text/xml" );
+ this.root = this.doc.documentElement;
+
+ this.root.appendChild( node );
+ this.element = this.root.firstChild;
+ return this;
+}
+
+
+
+// -----------------------------------------------------------------------------
+// domainObjectAttr
+// -----------------------------------------------------------------------------
+
+domainObjectAttr.prototype = new DOM();
+domainObjectAttr.prototype.constructor = domainObjectAttr;
+domainObjectAttr.baseClass = DOM.prototype.constructor;
+
+/** A domainObjectAttr is a single XML node with 'name' and 'value' attributes */
+function domainObjectAttr( name, value ) {
+
+ var node = this.init( "domainObjectAttr" );
+ if( ! name && value ) { return; }
+ node.setAttribute( "name", name );
+ node.setAttribute( "value", value );
+ this.root.appendChild( node );
+}
+
+
+/** Returns the attribute name */
+domainObjectAttr.prototype.getName = function() {
+ return this.element.getAttribute("name");
+}
+
+/** Returns the attribut value. */
+domainObjectAttr.prototype. getValue = function() {
+ return this.element.getAttribute("value");
+}
+
+/** Sets the attribute value. */
+domainObjectAttr.prototype. setValue = function( value ) {
+ return this.element.setAttribute("value", value );
+}
+
+/** Pass in the this.element component of a domainObjectAttr and this will
+ * replace the old element with the new one
+ */
+domainObjectAttr.prototype.replaceNode = function( domainObjectAttr_element ) {
+ return this._replaceNode( "domainObjectAttr", domainObjectAttr_element );
+}
+
+// -----------------------------------------------------------------------------
+// userAuth
+// -----------------------------------------------------------------------------
+
+
+userAuth.prototype = new DOM();
+userAuth.prototype.constructor = userAuth;
+userAuth.baseClass = DOM.prototype.constructor;
+
+function userAuth( username, secret ) {
+
+ var node = this.init( "userAuth" );
+ if( !( username && secret) ) { return; }
+ node.setAttribute( "username", username );
+
+ //There is no way to specify the hash seed with the
+ //md5 utility provided
+ var hash = hex_md5( secret );
+ node.setAttribute( "secret", hash );
+ node.setAttribute( "hashseed", "" );
+
+ this.root.appendChild( node );
+}
+
+userAuth.prototype.getUsername = function() {
+ return this.element.getAttribute( "username" );
+}
+
+userAuth.prototype.getSecret = function() {
+ return this.element.getAttribute( "secret" );
+}
+
+userAuth.prototype.getHashseed = function() {
+ return this.element.getAttribute( "hashseed" );
+}
+
+userAuth.prototype.replaceNode = function( userAuth_element ) {
+ return this._replaceNode( "userAuth", userAuth_element );
+}
+
+
+// -----------------------------------------------------------------------------
+// domainObject
+// -----------------------------------------------------------------------------
+
+domainObject.prototype = new DOM();
+domainObject.baseClass = DOM.prototype.constructor;
+domainObject.prototype.constructor = domainObject;
+
+/** Holds the XML for a DomainObject (see oils_domain_object.js or the DomainObject class) */
+function domainObject( name ) {
+ this._init_domainObject( name );
+}
+
+/** Initializes the domainObject XML node */
+domainObject.prototype._init_domainObject = function( name ) {
+
+ var node = this.init( "domainObject" );
+
+ if( ! name ) { return; }
+ node.setAttribute( "name", name );
+ this.root.appendChild( node );
+
+}
+
+/** Pass in any DOM Element or DomainObject and this method will as the
+ * new object as the next child of the root (domainObject) node
+ */
+domainObject.prototype.add = function( new_DOM ) {
+
+ this.element.appendChild(
+ new_DOM.element.cloneNode( true ) );
+}
+
+/** Removes the original domainObject XML node and replaces it with the
+ * one provided. This is useful for building new domainObject's from
+ * raw xml. Just create a new one, then call replaceNode with the new XML.
+ */
+domainObject.prototype.replaceNode = function( domainObject_element ) {
+ return this._replaceNode( "domainObject", domainObject_element );
+}
+
+
+
+// -----------------------------------------------------------------------------
+// Param
+// -----------------------------------------------------------------------------
+
+
+param.prototype = new DOM();
+param.baseClass = DOM.prototype.constructor;
+param.prototype.constructor = param;
+
+function param( value ) {
+ var node = this.init( "param" );
+ node.appendChild( this.doc.createTextNode( value ) );
+ this.root.appendChild( node );
+}
+
+param.prototype.getValue = function() {
+ return this.element.textContent;
+}
+
+param.prototype.replaceNode = function( param_element ) {
+ return this._replaceNode( "param", param_element );
+}
+
+
+// -----------------------------------------------------------------------------
+// domainObjectCollection
+// -----------------------------------------------------------------------------
+
+domainObjectCollection.prototype = new DOM();
+domainObjectCollection.prototype.constructor =
+ domainObjectCollection;
+domainObjectCollection.baseClass = DOM.prototype.constructor;
+
+function domainObjectCollection( name ) {
+ var node = this.init( "domainObjectCollection" );
+ this.root.appendChild( node );
+ if(name) { this._initCollection( name ); }
+}
+
+domainObjectCollection.prototype._initCollection = function( name ) {
+ if( name ) {
+ this.element.setAttribute( "name", name );
+ }
+}
+
+domainObjectCollection.prototype.add = function( new_domainObject ) {
+ this.element.appendChild(
+ new_domainObject.element.cloneNode( true ) );
+}
+
+domainObjectCollection.prototype.replaceNode = function( new_node ) {
+ return this._replaceNode( "domainObjectCollection", new_node );
+}
+
+
--- /dev/null
+// -----------------------------------------------------------------------------
+// This houses all of the domain object code.
+// -----------------------------------------------------------------------------
+
+
+
+
+// -----------------------------------------------------------------------------
+// DomainObject
+
+DomainObject.prototype = new domainObject();
+DomainObject.prototype.constructor = DomainObject;
+DomainObject.prototype.baseClass = domainObject.prototype.constructor;
+
+/** Top level DomainObject class. This most provides convience methods
+ * and a shared superclass
+ */
+function DomainObject( name ) {
+ if( name ) { this._init_domainObject( name ); }
+}
+
+/** Returns the actual element of the given domainObjectAttr. */
+DomainObject.prototype._findAttr = function ( name ) {
+
+ var nodes = this.element.childNodes;
+
+ if( ! nodes || nodes.length < 1 ) {
+ throw new oils_ex_dom( "Invalid xml object in _findAttr: " + this.toString() );
+ }
+
+ var i=0;
+ var node = nodes.item(i);
+
+ while( node != null ) {
+
+ if( node.nodeName == "oils:domainObjectAttr" &&
+ node.getAttribute("name") == name ) {
+ return node;
+ }
+
+ node = nodes.item(++i);
+ }
+
+ return null;
+}
+
+
+
+
+/** Returns the value stored in the given attribute */
+DomainObject.prototype.getAttr = function ( name ) {
+
+ var node = this._findAttr( name );
+ if( node ) { return node.getAttribute( "value" ); }
+ else {
+ throw new oils_ex_dom( "getAttr(); Getting nonexistent attribute: " + name );
+ }
+}
+
+/** Updates the value held by the given attribute */
+DomainObject.prototype.setAttr = function ( name, value ) {
+
+ var node = this._findAttr( name );
+ if( node ) {
+ node.setAttribute( "value", value );
+ } else {
+ throw new oils_ex_dom( "setAttr(); Setting nonexistent attribute: " + name );
+ }
+}
+
+/** This takes a raw DOM Element node and creates the DomainObject that the node
+ * embodies and returns the object. All new DomainObjects should be added to
+ * this list if they require this type of dynamic functionality.
+ * NOTE Much of this will be deprecated as move to a more JSON-centric wire protocol
+ */
+DomainObject.newFromNode = function( node ) {
+
+ switch( node.getAttribute("name") ) {
+
+ case "oilsMethod":
+ return new oilsMethod().replaceNode( node );
+
+ case "oilsMessage":
+ return new oilsMessage().replaceNode( node );
+
+ case "oilsResponse":
+ return new oilsResponse().replaceNode( node );
+
+ case "oilsResult":
+ return new oilsResult().replaceNode( node );
+
+ case "oilsConnectStatus":
+ return new oilsConnectStatus().replaceNode( node );
+
+ case "oilsException":
+ return new oilsException().replaceNode( node );
+
+ case "oilsMethodException":
+ return new oilsMethodException().replaceNode( node );
+
+ case "oilsScalar":
+ return new oilsScalar().replaceNode( node );
+
+ case "oilsPair":
+ return new oilsPair().replaceNode( node );
+
+ case "oilsArray":
+ return new oilsArray().replaceNode( node );
+
+ case "oilsHash":
+ return new oilsHash().replaceNode( node );
+
+
+ }
+
+}
+
+
+
+// -----------------------------------------------------------------------------
+// oilsMethod
+
+oilsMethod.prototype = new DomainObject();
+oilsMethod.prototype.constructor = oilsMethod;
+oilsMethod.prototype.baseClass = DomainObject.prototype.constructor;
+
+/**
+ * oilsMethod Constructor
+ *
+ * @param method_name The name of the method your are sending
+ * to the remote server
+ * @param params_array A Javascript array of the params (any
+ * Javascript data type)
+ */
+
+function oilsMethod( method_name, params_array ) {
+ this._init_domainObject( "oilsMethod" );
+ this.add( new domainObjectAttr( "method", method_name ) );
+
+ if( params_array ) {
+ var params = this.doc.createElement( "oils:params" );
+ this.element.appendChild( params );
+ params.appendChild( this.doc.createTextNode(
+ js2JSON( params_array ) ) );
+ }
+}
+
+
+/** Locates the params node of this method */
+oilsMethod.prototype._getParamsNode = function() {
+
+ var nodes = this.element.childNodes;
+ if( ! nodes || nodes.length < 1 ) { return null; }
+ var x=0;
+ var node = nodes.item(x);
+ while( node != null ) {
+ if( node.nodeName == "oils:params" ) {
+ return node;
+ }
+ node = nodes.item(++x);
+ }
+
+ return null;
+}
+
+
+/** Returns an array of param objects */
+oilsMethod.prototype.getParams = function() {
+ var node = this._getParamsNode();
+ if(node != null ) { return JSON2js( node->textContent ); }
+}
+
+
+
+// -----------------------------------------------------------------------------
+// oilsMessage
+
+// -----------------------------------------------------------------------------
+// oilsMessage message types
+// -----------------------------------------------------------------------------
+/** CONNECT Message type */
+oilsMessage.CONNECT = 'CONNECT';
+/** DISCONNECT Message type */
+oilsMessage.DISCONNECT = 'DISCONNECT';
+/** STATUS Message type */
+oilsMessage.STATUS = 'STATUS';
+/** REQUEST Message type */
+oilsMessage.REQUEST = 'REQUEST';
+/** RESULT Message type */
+oilsMessage.RESULT = 'RESULT';
+
+
+oilsMessage.prototype = new DomainObject();
+oilsMessage.prototype.constructor = oilsMessage;
+oilsMessage.prototype.baseClass = DomainObject.prototype.constructor;
+
+/** Core XML object for message passing */
+function oilsMessage( type, protocol, user_auth ) {
+
+ if( !( type && protocol) ) {
+ type = oilsMessage.CONNECT;
+ protocol = "1";
+ }
+
+ if( ! ( type == oilsMessage.CONNECT ||
+ type == oilsMessage.DISCONNECT ||
+ type == oilsMessage.STATUS ||
+ type == oilsMessage.REQUEST ||
+ type == oilsMessage.RESULT ) ) {
+ throw new oils_ex_message( "Attempt to create oilsMessage with incorrect type: " + type );
+ }
+
+ this._init_domainObject( "oilsMessage" );
+
+ this.add( new domainObjectAttr( "type", type ) );
+ this.add( new domainObjectAttr( "threadTrace", 0 ) );
+ this.add( new domainObjectAttr( "protocol", protocol ) );
+
+ if( user_auth ) { this.add( user_auth ); }
+
+}
+
+/** Builds a new oilsMessage from raw xml.
+ * Checks are taken to make sure the xml is supposed to be an oilsMessage.
+ * If not, an oils_ex_dom exception is throw.
+ */
+oilsMessage.newFromXML = function( xml ) {
+
+ try {
+
+ if( ! xml || xml == "" ) { throw 1; }
+
+ var doc = new DOMParser().parseFromString( xml, "text/xml" );
+ if( ! doc ) { throw 1; }
+
+ var root = doc.documentElement;
+ if( ! root ) { throw 1; }
+
+ // -----------------------------------------------------------------------------
+ // There are two options here. One is that we were provided the full message
+ // xml (i.e. contains the <oils:root> tag. The other option is that we were
+ // provided just the message xml portion (top level element is the
+ // <oils:domainObject>
+ // -----------------------------------------------------------------------------
+
+ var element;
+ if( root.nodeName == "oils:root" ) {
+
+ element = root.firstChild;
+ if( ! element ) { throw 1; }
+
+ } else {
+
+ if( root.nodeName == "oils:domainObject" ) {
+ element = root;
+
+ } else { throw 1; }
+
+ }
+
+
+ if( element.nodeName != "oils:domainObject" ) { throw 1; }
+ if( element.getAttribute( "name" ) != "oilsMessage" ) { throw 1; }
+
+ return new oilsMessage().replaceNode( element );
+
+ } catch( E ) {
+
+ if( E && E.message ) {
+ throw new oils_ex_dom( "Bogus XML for creating oilsMessage: " + E.message + "\n" + xml );
+
+ } else {
+ throw new oils_ex_dom( "Bogus XML for creating oilsMessage:\n" + xml );
+ }
+ }
+
+ return msg;
+}
+
+
+
+/** Adds a copy of the given DomainObject to the message */
+oilsMessage.prototype.addPayload = function( new_DomainObject ) {
+ this.add( new_DomainObject );
+}
+
+
+/** Returns the top level DomainObject contained in this message
+ *
+ * Note: The object retuturned will be the actual object, not just a
+ * generic DomainObject - e.g. oilsException.
+ */
+oilsMessage.prototype.getPayload = function() {
+
+ var nodes = this.element.childNodes;
+ var x=0;
+ var node = nodes.item(0);
+
+ while( node != null ) {
+
+ if( node.nodeName == "oils:domainObject" ) {
+
+ new Logger().debug( "Building oilsMessage payload from\n" +
+ new XMLSerializer().serializeToString( node ), Logger.DEBUG );
+ return DomainObject.newFromNode( node );
+ }
+ node = nodes.item(++x);
+ }
+
+ return null;
+}
+
+oilsMessage.prototype.getUserAuth = function() {
+
+ var nodes = this.element.getElementsByTagName( "oils:userAuth" );
+ if( ! nodes ) { return null; }
+
+ var auth_elem = nodes.item(0); // should only be one
+ if ( ! auth_elem ) { return null; }
+
+ return new userAuth().replaceNode( auth_elem );
+
+}
+
+/** Returns the type of the message */
+oilsMessage.prototype.getType = function() {
+ return this.getAttr( "type" );
+}
+
+/** Returns the message protocol */
+oilsMessage.prototype.getProtocol = function() {
+ return this.getAttr( "protocol" );
+}
+
+/** Returns the threadTrace */
+oilsMessage.prototype.getThreadTrace = function() {
+ return this.getAttr( "threadTrace" );
+}
+
+/** Sets the thread trace - MUST be an integer */
+oilsMessage.prototype.setThreadTrace = function( trace ) {
+ this.setAttr( "threadTrace", trace );
+}
+
+/** Increments the thread trace by 1 */
+oilsMessage.prototype.updateThreadTrace = function() {
+ var tt = this.getThreadTrace();
+ return this.setThreadTrace( ++tt );
+}
+
+
+
+
+
+// -----------------------------------------------------------------------------
+// oilsResponse
+
+
+// -----------------------------------------------------------------------------
+// Response codes
+// -----------------------------------------------------------------------------
+
+/**
+ * Below are the various response statuses
+ */
+oilsResponse.STATUS_CONTINUE = 100
+
+oilsResponse.STATUS_OK = 200
+oilsResponse.STATUS_ACCEPTED = 202
+oilsResponse.STATUS_COMPLETE = 205
+
+oilsResponse.STATUS_REDIRECTED = 307
+
+oilsResponse.STATUS_BADREQUEST = 400
+oilsResponse.STATUS_UNAUTHORIZED = 401
+oilsResponse.STATUS_FORBIDDEN = 403
+oilsResponse.STATUS_NOTFOUND = 404
+oilsResponse.STATUS_NOTALLOWED = 405
+oilsResponse.STATUS_TIMEOUT = 408
+oilsResponse.STATUS_EXPFAILED = 417
+oilsResponse.STATUS_INTERNALSERVERERROR = 500
+oilsResponse.STATUS_NOTIMPLEMENTED = 501
+oilsResponse.STATUS_VERSIONNOTSUPPORTED = 505
+
+
+oilsResponse.prototype = new DomainObject();
+oilsResponse.prototype.constructor = oilsResponse;
+oilsResponse.prototype.baseClass = DomainObject.prototype.constructor;
+
+/** Constructor. status is the text describing the message status and
+ * statusCode must correspond to one of the oilsResponse status code numbers
+ */
+function oilsResponse( name, status, statusCode ) {
+ if( name && status && statusCode ) {
+ this._initResponse( name, status, statusCode );
+ }
+}
+
+/** Initializes the reponse XML */
+oilsResponse.prototype._initResponse = function( name, status, statusCode ) {
+
+ this._init_domainObject( name );
+ this.add( new domainObjectAttr( "status", status ));
+ this.add( new domainObjectAttr( "statusCode", statusCode ) );
+}
+
+
+
+/** Returns the status of the response */
+oilsResponse.prototype.getStatus = function() {
+ return this.getAttr( "status" );
+}
+
+/** Returns the statusCode of the response */
+oilsResponse.prototype.getStatusCode = function() {
+ return this.getAttr("statusCode");
+}
+
+
+oilsConnectStatus.prototype = new oilsResponse();
+oilsConnectStatus.prototype.constructor = oilsConnectStatus;
+oilsConnectStatus.prototype.baseClass = oilsResponse.prototype.constructor;
+
+/** Constructor. These give us info on our connection attempts **/
+function oilsConnectStatus( status, statusCode ) {
+ this._initResponse( "oilsConnectStatus", status, statusCode );
+}
+
+
+oilsResult.prototype = new oilsResponse();
+oilsResult.prototype.constructor = oilsResult;
+oilsResult.prototype.baseClass = oilsResponse.prototype.constructor;
+
+
+/** Constructor. These usually carry REQUEST responses */
+function oilsResult( status, statusCode ) {
+ if( status && statusCode ) {
+ this._initResponse( "oilsResult", status, statusCode );
+ }
+}
+
+/** Returns the top level DomainObject within this result message.
+ * Note: The object retuturned will be the actual object, not just a
+ * generic DomainObject - e.g. oilsException.
+ */
+oilsResult.prototype.getContent = function() {
+
+ var nodes = this.element.childNodes;
+ if( ! nodes || nodes.length < 1 ) {
+ throw new oils_ex_dom("Content node for oilsResult is invalid\n" + this.toString() );
+ }
+ var x = 0;
+ var node = nodes.item(x);
+ while( node != null ) {
+
+ if( node.nodeName == "oils:domainObject" ||
+ node.nodeName == "oils:domainObjectCollection" ) {
+
+ return DomainObject.newFromNode( node );
+ }
+ node = nodes.item(++x);
+ }
+
+ throw new oils_ex_dom("Content node for oilsResult is invalid\n" + this.toString() );
+}
+
+
+oilsException.prototype = new oilsResponse();
+oilsException.prototype.constructor = oilsException;
+oilsException.prototype.baseClass = oilsResponse.prototype.constructor;
+
+/** Top level exception */
+function oilsException( status, statusCode ) {
+ if( status && statusCode ) {
+ this._initResponse( "oilsException", status, statusCode );
+ }
+}
+
+
+oilsMethodException.prototype = new oilsException();
+oilsMethodException.prototype.constructor = oilsMethodException;
+oilsMethodException.prototype.baseClass = oilsException.prototype.constructor;
+
+/** Method exception */
+function oilsMethodException( status, statusCode ) {
+ if( status && statusCode ) {
+ this._initResponse( "oilsMethodException", status, statusCode );
+ }
+}
+
+
+// -----------------------------------------------------------------------------
+// oilsScalar
+
+
+oilsScalar.prototype = new DomainObject();
+oilsScalar.prototype.constructor = oilsScalar;
+oilsScalar.prototype.baseClass = DomainObject.prototype.constructor;
+
+/** Contains a single JSON item as its text content */
+function oilsScalar( value ) {
+ this._init_domainObject( "oilsScalar" );
+ this.element.appendChild( this.doc.createTextNode( value ) );
+}
+
+/** Returns the contained item as a Javascript object */
+oilsScalar.prototype.getValue = function() {
+ return JSON2js( this.element.textContent );
+}
+
+
+// -----------------------------------------------------------------------------
+// oilsPair
+
+
+oilsPair.prototype = new DomainObject()
+oilsPair.prototype.constructor = oilsPair;
+oilsPair.prototype.baseClass = DomainObject.prototype.constructor;
+
+function oilsPair( key, value ) {
+
+ this._init_domainObject( "oilsPair" );
+ this.element.appendChild( this.doc.createTextNode( value ) );
+ this.element.setAttribute( "key", key );
+}
+
+oilsPair.prototype.getKey = function() {
+ return this.element.getAttribute( "key" );
+}
+
+oilsPair.prototype.getValue = function() {
+ return this.element.textContent;
+}
+
+
+
+// -----------------------------------------------------------------------------
+// oilsArray - is a domainObjectCollection that stores a list of domainObject's
+// or domainObjectCollections.
+// -----------------------------------------------------------------------------
+
+oilsArray.prototype = new domainObjectCollection()
+oilsArray.prototype.constructor = oilsArray;
+oilsArray.prototype.baseClass = domainObjectCollection.prototype.constructor;
+
+function oilsArray( obj_list ) {
+ this._initCollection( "oilsArray" );
+ if(obj_list) { this.initArray( obj_list ); }
+}
+
+// -----------------------------------------------------------------------------
+// Adds the array of objects to the array, these should be actual objects, not
+// DOM nodes/elements, etc.
+// -----------------------------------------------------------------------------
+oilsArray.prototype.initArray = function( obj_list ) {
+ if( obj_array != null && obj_array.length > 0 ) {
+ for( var i= 0; i!= obj_array.length; i++ ) {
+ this.add( obj_array[i] );
+ }
+ }
+}
+
+// -----------------------------------------------------------------------------
+// Returns an array of DomainObjects or domainObjectCollections. The objects
+// returned will already be 'cast' into the correct object. i.e. you will not
+// receieve an array of DomainObjects, but instead an array of oilsScalar's or
+// an array of oilsHashe's, etc.
+// -----------------------------------------------------------------------------
+oilsArray.prototype.getObjects = function() {
+
+ var obj_list = new Array();
+ var nodes = this.element.childNodes;
+ var i=0;
+ var node = nodes.item(i);
+
+ while( node != null ) {
+
+ if( node.nodeName == "oils:domainObject" ||
+ node.nodeName == "oils:domainObjectCollection" ) {
+ obj_list[i++] = DomainObject.newFromNode( node );
+ }
+ node = nodes.item(i);
+ }
+
+ return obj_list;
+}
+
+
+// -----------------------------------------------------------------------------
+// oilsHash - an oilsHash is an oilsArray except it only stores oilsPair's
+// -----------------------------------------------------------------------------
+
+oilsHash.prototype = new oilsArray();
+oilsHash.prototype.constructor = oilsHash;
+oilsHash.prototype.baseClass = oilsArray.prototype.constructor;
+
+function oilsHash( pair_array ) {
+ this._initCollection("oilsHash");
+ if(pair_array) { this.initArray( pair_array ); }
+}
+
+// -----------------------------------------------------------------------------
+// Returns the array of oilsPairs objects that this hash contains
+// -----------------------------------------------------------------------------
+oilsHash.prototype.getPairs = function() {
+ return this.getObjects();
+}
+
+
--- /dev/null
+// ------------------------------------------------------------------
+// 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("<message></message>", "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( "<stream:stream to='"+host
+ +"' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>" );
+
+ 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( "<iq id='123456789' type='set'><query xmlns='jabber:iq:auth'><username>"
+ + username + "</username><password>" + password +
+ "</password><resource>" + resource + "</resource></query></iq>" );
+
+ 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 ) {
+ this.process_data( this.instream.read( count ) );
+ count = this.instream.available();
+ }
+
+ } 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;
+
+ while( ((now-then) <= timeout) ) {
+
+ if( this.buffer.length == 0 ) {
+ if( ! this.tcp_recv( timeout ) ) {
+ return null;
+ }
+ }
+
+ //new Logger().transport( "\n\nTCP Buffer Before: \n" + this.buffer, Logger.DEBUG );
+
+ var buf = this.buffer;
+ this.buffer = "";
+
+ buf = buf.replace( /\n/g, '' ); // remove pesky newlines
+
+ var reg = /<message.*?>.*?<\/message>/;
+ var iqr = /<iq.*?>.*?<\/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;
+
+ }
+ }
+
+ 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( "</stream:stream>" );
+ this.instream.close();
+ this.outstream.close();
+}
+
+/** True if connected */
+jabber_socket.prototype.connected = function() {
+ return this.is_connected;
+}
+
+
+
+
+
--- /dev/null
+// -----------------------------------------------------------------------------
+// Message stack code.
+// -----------------------------------------------------------------------------
+
+
+
+// -----------------------------------------------------------------------------
+// These just have to be defined for the 'static' methods to work
+// -----------------------------------------------------------------------------
+function Transport() {}
+function Message() {}
+function Application() {}
+
+/** Transport handler.
+ * Takes a transport_message as parameter
+ * Parses the incoming message data and builds one or more oilsMessage objects
+ * from the XML. Each message is passed in turn to the Message.handler
+ * method.
+ */
+Transport.handler = function( msg ) {
+
+ if( msg.is_error_msg ) {
+ throw new oils_ex_session( "Receved error message from jabber server for recipient: " + msg.get_sender() );
+ return;
+ }
+
+ var remote_id = msg.get_sender();
+ var session_id = msg.get_thread();
+ var body = msg.get_body();
+
+ var session = AppSession.find_session( session_id );
+
+ if( ! session ) {
+ new Logger().debug( "No AppSession found with id: " + session_id );
+ return;
+ }
+
+ session.set_remote_id( remote_id );
+
+ var nodelist; // oilsMessage nodes
+
+
+ // -----------------------------------------------------------------------------
+ // Parse the incoming XML
+ // -----------------------------------------------------------------------------
+ try {
+
+ var doc = new DOMParser().parseFromString( body, "text/xml" );
+ nodelist = doc.documentElement.getElementsByTagName( "oils:domainObject" );
+
+ if( ! nodelist || nodelist.length < 1 ) {
+ nodelist = doc.documentElement.getElementsByTagName( "domainObject" );
+ if( ! nodelist || nodelist.length < 1 ) { throw 1; }
+ }
+
+ } catch( E ) {
+
+ var str = "Error parsing incoming message document";
+
+ if( E ) { throw new oils_ex_dom( str + "\n" + E.message + "\n" + body );
+ } else { throw new oils_ex_dom( str + "\n" + body ); }
+
+ }
+
+ // -----------------------------------------------------------------------------
+ // Pass the messages up the chain.
+ // -----------------------------------------------------------------------------
+ try {
+
+ var i = 0;
+ var node = nodelist.item(i); // a single oilsMessage
+
+ while( node != null ) {
+
+ if( node.getAttribute("name") != "oilsMessage" ) {
+ node = nodelist.item(++i);
+ continue;
+ }
+
+ var oils_msg = new oilsMessage().replaceNode( node );
+
+
+ // -----------------------------------------------------------------------------
+ // this is really inefficient compared to the above line of code,
+ // however, this resolves some namespace oddities in DOMParser -
+ // namely, DOMParser puts dummy namesapaces in "a0" when, I'm assuming, it
+ // can't find the definition for the namesapace included.
+ // -----------------------------------------------------------------------------
+ // var oils_msg = oilsMessage.newFromXML( new XMLSerializer().serializeToString( node ) );
+
+ new Logger().transport( "Transport passing up:\n" + oils_msg.toString(true), Logger.INFO );
+
+ // Pass the message off to the message layer
+ Message.handler( session, oils_msg );
+ node = nodelist.item(++i);
+ }
+
+ } catch( E ) {
+
+ var str = "Processing Error";
+
+ if( E ) { throw new oils_ex_session( str + "\n" + E.message + "\n" + body ); }
+ else { throw new oils_ex_session( str + "\n" + body ); }
+ }
+}
+
+/** Checks to see what type of message has arrived. If it is a 'STATUS' message,
+ * the appropriate transport layer actions are taken. Otherwise (RESULT), the
+ * message is passed to the Application.handler method.
+ */
+Message.handler = function( session, msg ) {
+
+ var msg_type = msg.getType();
+ var domain_object_payload = msg.getPayload();
+ var tt = msg.getThreadTrace();
+
+ var code = domain_object_payload.getStatusCode();
+
+ new Logger().debug( "Message.handler received " + msg_type + " from " +
+ session.get_remote_id() + " with thread_trace " + tt + " and status " + code, Logger.INFO );
+ new Logger().debug( "Message.handler received:\n" + domain_object_payload.toString(), Logger.DEBUG );
+
+ if( msg_type == oilsMessage.STATUS ) {
+
+ switch( code ) {
+
+ case oilsResponse.STATUS_OK + "": {
+ session.set_state( AppSession.CONNECTED );
+ new Logger().debug( " * Connected Successfully: " + tt, Logger.INFO );
+ return;
+ }
+
+ case oilsResponse.STATUS_TIMEOUT + "": {
+ return Message.reset_session( session, tt, "Disconnected because of timeout" );
+ }
+
+ case oilsResponse.STATUS_REDIRECTED + "": {
+ return Message.reset_session( session, tt, "Disconnected because of redirect" );
+ }
+
+ case oilsResponse.STATUS_EXPFAILED + "": {
+ return Message.reset_session( session, tt, "Disconnected because of mangled session" );
+ }
+
+ case oilsResponse.STATUS_NOTALLOWED + "": {
+ new Logger().debug( "Method Not Allowed", Logger.ERROR );
+ session.destroy();
+ break; // we want the exception to be thrown below
+ }
+
+ case oilsResponse.STATUS_CONTINUE +"": {
+ return;
+ }
+
+ case oilsResponse.STATUS_COMPLETE + "": {
+ var req = session.get_request(tt);
+ if( req ) { req.set_complete(); }
+ new Logger().debug( " * Request completed: " + tt, Logger.INFO );
+ return;
+ }
+
+ default: { break; }
+ }
+
+ }
+
+ // throw any exceptions received from the server
+ if( domain_object_payload instanceof oilsException ) {
+ throw new oils_ex_session( domain_object_payload.getStatus() );
+ }
+
+ new Logger().debug( "Message Layer passing up:\n" + domain_object_payload.toString(), Logger.DEBUG );
+
+ Application.handler( session, domain_object_payload, tt );
+
+}
+
+/** Utility method for cleaning up a session. Sets state to disconnected.
+ * resets the remoted_id, pushes the current app_request onto the resend
+ * queue. Logs a message.
+ */
+Message.reset_session = function( session, thread_trace, message ) {
+ session.set_state( AppSession.DISCONNECTED );
+ session.reset_remote();
+ var req = session.get_request( thread_trace );
+ if( req && !req.complete ) { session.push_resend( req ); }
+ new Logger().debug( " * " + message + " : " + thread_trace, Logger.INFO );
+}
+
+
+/** Pushes all incoming messages onto the session message queue. **/
+Application.handler = function( session, domain_object_payload, thread_trace ) {
+
+ new Logger().debug( "Application Pushing onto queue: "
+ + thread_trace + "\n" + domain_object_payload.toString(), Logger.DEBUG );
+
+ session.push_queue( domain_object_payload, thread_trace );
+}
+
+
+
+
+
--- /dev/null
+/** @file oils_transport.js
+ * Houses the top level transport 'abstract' classes
+ * You can think of this like a header file which provides the
+ * interface that any transport code must implement
+ */
+
+
+// ------------------------------------------------------------------
+// TRANSPORT_CONNECTION
+
+/** Constructor */
+function transport_connection( username, password, resource ) { }
+
+/** Connects to the transport host */
+transport_connection.prototype.connect = function( host, /*int*/ port, /*int*/ timeout ) {}
+
+/** Sends a new message to recipient, with session_id and body */
+transport_connection.prototype.send = function( recipient, session_id, body ) {}
+
+
+/** Returns a transport_message. This function will return
+ * immediately if there is a message available. Otherwise, it will
+ * wait at most 'timeout' seconds for one to arrive. Returns
+ * null if a message does not arrivae in time.
+
+ * 'timeout' is specified in milliseconds
+ */
+transport_connection.prototype.recv = function( /*int*/ timeout ) {}
+
+/** This method calls recv and then passes the contents on to the
+ * message processing stack.
+ */
+transport_connection.prototype.process_msg = function( /*int*/ timeout ) {
+ var msg = this.recv( timeout );
+ if( msg ) { Transport.handler( msg ); }
+}
+
+/** Disconnects from the transpot host */
+transport_connection.prototype.disconnect = function() {}
+
+/** Returns true if this connection instance is currently connected
+ * to the transport host.
+ */
+transport_connection.prototype.connected = function() {}
+
+
+
+// ------------------------------------------------------------------
+// TRANSPORT_MESSAGE
+
+
+/** Constructor */
+function transport_message( sender, recipient, session_id, body ) {}
+
+/** Returns the sender of the message */
+transport_message.prototype.get_sender = function() {}
+
+/** Returns the session id */
+transport_message.prototype.get_session = function() {}
+
+/** Returns the message body */
+transport_message.prototype.get_body = function() {}
+
+
--- /dev/null
+// ------------------------------------------------------------------
+// Houses utility functions
+// ------------------------------------------------------------------
+
+/** Prints to console. If alert_bool = true, displays a popup as well */
+function _debug( message, alert_bool ) {
+
+ dump( "\n" + new Date() + "\n--------------------------------\n" +
+ message + "\n-----------------------------------\n" );
+ if( alert_bool == true ) { alert( message ) };
+
+}
+
+
+/** Provides millisec sleep times, enjoy... */
+function sleep(gap){
+
+ var threadService = Components.classes["@mozilla.org/thread;1"].
+ getService(Components.interfaces.nsIThread);
+
+ var th = threadService.currentThread;
+ th.sleep(gap);
+}
+
+
+
+/** Top level exception classe */
+function oils_ex() {}
+
+/** Initializes an exception */
+oils_ex.prototype.init_ex = function( name, message ) {
+ if( !(name && message) ) { return; }
+ this.name = name;
+ this.message = name + " : " + message;
+ new Logger().debug( "***\n" + this.message + "\n***", Logger.ERROR );
+}
+
+/** Returns a string representation of an exception */
+oils_ex.prototype.toString = function() {
+ return this.message;
+}
+
+
+oils_ex_transport.prototype = new oils_ex();
+oils_ex_transport.prototype.constructor = oils_ex_transport;
+oils_ex_transport.baseClass = oils_ex.prototype.constructor;
+
+/** Thrown when the transport connection has problems*/
+function oils_ex_transport( message ) {
+ this.init_ex( "Transport Exception", message );
+}
+
+
+oils_ex_transport_auth.prototype = new oils_ex_transport();
+oils_ex_transport_auth.prototype.constructor = oils_ex_transport_auth;
+oils_ex_transport_auth.baseClass = oils_ex_transport.prototype.constructor;
+
+/** Thrown when the initial transport connection fails */
+function oils_ex_transport_auth( message ) {
+ this.init_ex( "Transport Authentication Error", message );
+}
+
+oils_ex_dom.prototype = new oils_ex();
+oils_ex_dom.prototype.constructor = oils_ex_dom;
+oils_ex_dom.baseClass = oils_ex.prototype.constructor;
+
+/** Thrown when there is an XML problem */
+function oils_ex_dom( message ) {
+ this.init_ex( "DOM Error", message );
+}
+
+oils_ex_message.prototype = new oils_ex();
+oils_ex_message.prototype.constructor = oils_ex_message;
+oils_ex_message.baseClass = oils_ex.prototype.constructor;
+
+/** Thrown when there is a message problem */
+function oils_ex_message( message ) {
+ this.init_ex( "OILS Message Layer Error", message ) ;
+}
+
+oils_ex_config.prototype = new oils_ex();
+oils_ex_config.prototype.constructor = oils_ex_config;
+oils_ex_config.prototype.baseClass = oils_ex.prototype.constructor;
+
+/** Thrown when values cannot be retrieved from the config file */
+function oils_ex_config( message ) {
+ this.init_ex( "OILS Config Exception", message );
+}
+
+oils_ex_logger.prototype = new oils_ex();
+oils_ex_logger.prototype.constructor = oils_ex_logger;
+oils_ex_logger.prototype.baseClass = oils_ex.prototype.constructor;
+
+/** Thrown where there are logging problems */
+function oils_ex_logger( message ) {
+ this.init_ex( "OILS Logger Exception", message );
+}
+
+
+oils_ex_args.prototype = new oils_ex();
+oils_ex_args.prototype.constructor = oils_ex_args;
+oils_ex_args.prototype.baseClass = oils_ex.prototype.constructor;
+
+/** Thrown when when a method does not receive all of the required arguments */
+function oils_ex_args( message ) {
+ this.init_ex( "Method Argument Exception", message );
+}
+
+
+oils_ex_session.prototype = new oils_ex();
+oils_ex_session.prototype.constructor = oils_ex_session;
+oils_ex_session.prototype.baseClass = oils_ex.prototype.constructor;
+
+/** Thrown where there is a session processing problem */
+function oils_ex_session( message ) {
+ this.init_ex( "Session Exception", message );
+}
--- /dev/null
+# set this shell variable prior to calling make to run with malloc_check enabled
+#MALLOC_CHECK_=1 # XXX debug only
+
+CC = gcc
+LIB_DIR=../../lib
+CC_OPTS = -Wall -O2 -I /usr/include/libxml2 -I /usr/include/libxml2/libxml -I ../../include -I ../../../../cc/libxml2-2.6.16
+LD_OPTS = -lxml2
+EXE_LD_OPTS = -L $(LIB_DIR) -lxml2 -ltransport
+LIB_SOURCES = generic_utils.c transport_socket.c transport_session.c transport_message.c transport_client.c
+
+TARGETS=generic_utils.o transport_socket.o transport_message.o transport_session.o transport_client.o
+
+all: router basic_client
+
+basic_client: lib
+ $(CC) $(CC_OPTS) $(EXE_LD_OPTS) basic_client.c -o $@
+
+# --- Libs -----------------------------------------------
+
+lib:
+ $(CC) -c $(CC_OPTS) $(LIB_SOURCES)
+ $(CC) -shared -W1 $(LD_OPTS) $(TARGETS) -o $(LIB_DIR)/libtransport.so
+
+
+# The router is compiled as a static binary because of some
+# necessary #defines that would break the library
+router:
+ $(CC) $(LD_OPTS) -D_ROUTER $(CC_OPTS) $(LIB_SOURCES) transport_router.c -o $@
+
+clean:
+ /bin/rm -f *.o ../../lib/libtransport.so router basic_client
--- /dev/null
+#include "transport_client.h"
+#ifdef DMALLOC
+#include "dmalloc.h"
+#endif
+
+/**
+ * Simple jabber client
+ */
+
+
+
+
+/* connects and registers with the router */
+int main( int argc, char** argv ) {
+
+ if( argc < 5 ) {
+ fatal_handler( "Usage: %s <username> <host> <resource> <recipient> \n", argv[0] );
+ return 99;
+ }
+
+ transport_message* send;
+ transport_client* client = client_init( argv[2], 5222 );
+
+ // try to connect, allow 15 second connect timeout
+ if( client_connect( client, argv[1], "asdfjkjk", argv[3], 15 ) )
+ info_handler("Connected...\n");
+ else
+ fatal_handler( "NOT Connected...\n" );
+
+ if( fork() ) {
+
+ fprintf(stderr, "Listener: %d\n", getpid() );
+ char buf[300];
+ memset(buf, 0, 300);
+ printf("=> ");
+
+ while( fgets( buf, 299, stdin) ) {
+
+ // remove newline
+ buf[strlen(buf)-1] = '\0';
+
+ if( strcmp(buf, "exit")==0) {
+ client_free( client );
+ break;
+ }
+
+ send = message_init( buf, "", "123454321", argv[4], NULL );
+ client_send_message( client, send );
+ message_free( send );
+ printf("\n=> ");
+ memset(buf, 0, 300);
+ }
+ return 0;
+
+ } else {
+
+ fprintf(stderr, "Sender: %d\n", getpid() );
+
+ transport_message* recv;
+ while( (recv=client_recv( client, -1)) ) {
+ if( recv->is_error )
+ fprintf( stderr, "\nReceived Error\t: ------------------\nFrom:\t\t"
+ "%s\nRouterFrom:\t%s\nBody:\t\t%s\nType %s\nCode %d\n=> ", recv->sender, recv->router_from, recv->body, recv->error_type, recv->error_code );
+ else
+ fprintf( stderr, "\nReceived\t: ------------------\nFrom:\t\t"
+ "%s\nRouterFrom:\t%s\nBody:\t\t%s\n=> ", recv->sender, recv->router_from, recv->body );
+
+ message_free( recv );
+ }
+
+ }
+ return 0;
+
+}
+
+
+
+
--- /dev/null
+#include "generic_utils.h"
+#include <stdio.h>
+#include "pthread.h"
+
+int _init_log();
+
+int balance = 0;
+
+#define LOG_ERROR 1
+#define LOG_WARNING 2
+#define LOG_INFO 3
+
+void get_timestamp( char buf_25chars[]) {
+ time_t epoch = time(NULL);
+ char* localtime = strdup( ctime( &epoch ) );
+ strcpy( buf_25chars, localtime );
+ buf_25chars[ strlen(localtime)-1] = '\0'; // remove newline
+ free(localtime);
+}
+
+
+inline void* safe_malloc( int size ) {
+ void* ptr = (void*) malloc( size );
+ if( ptr == NULL )
+ fatal_handler("safe_malloc(): Out of Memory" );
+ memset( ptr, 0, size );
+ return ptr;
+}
+
+// ---------------------------------------------------------------------------------
+// Here we define how we want to handle various error levels.
+// ---------------------------------------------------------------------------------
+
+
+static FILE* log_file = NULL;
+static int log_level = -1;
+pthread_mutex_t mutex;
+
+void log_free() { if( log_file != NULL ) fclose(log_file ); }
+
+void fatal_handler( char* msg, ... ) {
+
+ char buf[25];
+ memset( buf, 0, 25 );
+ get_timestamp( buf );
+ pid_t pid = getpid();
+ va_list args;
+
+ if( _init_log() ) {
+
+ if( log_level < LOG_ERROR )
+ return;
+
+ pthread_mutex_lock( &(mutex) );
+ fprintf( log_file, "[%s %d] [%s] ", buf, pid, "ERR " );
+
+ va_start(args, msg);
+ vfprintf(log_file, msg, args);
+ va_end(args);
+
+ fprintf(log_file, "\n");
+ fflush( log_file );
+ pthread_mutex_unlock( &(mutex) );
+
+ }
+
+ /* also log to stderr for ERRORS*/
+ fprintf( stderr, "[%s %d] [%s] ", buf, pid, "ERR " );
+ va_start(args, msg);
+ vfprintf(stderr, msg, args);
+ va_end(args);
+ fprintf( stderr, "\n" );
+
+ exit(99);
+}
+
+void warning_handler( char* msg, ... ) {
+
+ char buf[25];
+ memset( buf, 0, 25 );
+ get_timestamp( buf );
+ pid_t pid = getpid();
+ va_list args;
+
+ if( _init_log() ) {
+
+ if( log_level < LOG_WARNING )
+ return;
+
+ pthread_mutex_lock( &(mutex) );
+ fprintf( log_file, "[%s %d] [%s] ", buf, pid, "WARN" );
+
+ va_start(args, msg);
+ vfprintf(log_file, msg, args);
+ va_end(args);
+
+ fprintf(log_file, "\n");
+ fflush( log_file );
+ pthread_mutex_unlock( &(mutex) );
+
+ } else {
+
+ fprintf( stderr, "[%s %d] [%s] ", buf, pid, "WARN" );
+ va_start(args, msg);
+ vfprintf(stderr, msg, args);
+ va_end(args);
+ fprintf( stderr, "\n" );
+ }
+
+}
+
+void info_handler( char* msg, ... ) {
+
+ char buf[25];
+ memset( buf, 0, 25 );
+ get_timestamp( buf );
+ pid_t pid = getpid();
+ va_list args;
+
+ if( _init_log() ) {
+
+ if( log_level < LOG_INFO )
+ return;
+ pthread_mutex_lock( &(mutex) );
+ fprintf( log_file, "[%s %d] [%s] ", buf, pid, "INFO" );
+
+ va_start(args, msg);
+ vfprintf(log_file, msg, args);
+ va_end(args);
+
+ fprintf(log_file, "\n");
+ fflush( log_file );
+ pthread_mutex_unlock( &(mutex) );
+
+ } else {
+
+ fprintf( stderr, "[%s %d] [%s] ", buf, pid, "INFO" );
+ va_start(args, msg);
+ vfprintf(stderr, msg, args);
+ va_end(args);
+ fprintf( stderr, "\n" );
+ fflush(stderr);
+
+ }
+}
+
+
+int _init_log() {
+
+ if( log_level != -1 )
+ return 1;
+
+
+ pthread_mutex_init( &(mutex), NULL );
+
+ /* load the log level setting if we haven't already */
+
+ if( conf_reader == NULL ) {
+ return 0;
+ //fprintf( stderr, "No config file specified" );
+ }
+
+ char* log_level_str = config_value( "//router/log/level");
+ if( log_level_str == NULL ) {
+ // fprintf( stderr, "No log level specified" );
+ return 0;
+ }
+ log_level = atoi(log_level_str);
+ free(log_level_str);
+
+ /* see if we have a log file yet */
+ char* f = config_value("//router/log/file");
+
+ if( f == NULL ) {
+ // fprintf( stderr, "No log file specified" );
+ return 0;
+ }
+
+ log_file = fopen( f, "a" );
+ if( log_file == NULL ) {
+ fprintf( stderr, "Unable to open log file %s for appending\n", f );
+ return 0;
+ }
+ free(f);
+ return 1;
+
+}
+
+
+
+// ---------------------------------------------------------------------------------
+// Flesh out a ubiqitous growing string buffer
+// ---------------------------------------------------------------------------------
+
+growing_buffer* buffer_init(int num_initial_bytes) {
+
+ if( num_initial_bytes > BUFFER_MAX_SIZE ) {
+ return NULL;
+ }
+
+
+ size_t len = sizeof(growing_buffer);
+
+ growing_buffer* gb = (growing_buffer*) safe_malloc(len);
+
+ gb->n_used = 0;/* nothing stored so far */
+ gb->size = num_initial_bytes;
+ gb->buf = (char *) safe_malloc(gb->size + 1);
+
+ return gb;
+}
+
+int buffer_add(growing_buffer* gb, char* data) {
+
+
+ if( ! gb || ! data ) { return 0; }
+ int data_len = strlen( data );
+
+ if( data_len == 0 ) { return 0; }
+ int total_len = data_len + gb->n_used;
+
+ while( total_len >= gb->size ) {
+ gb->size *= 2;
+ }
+
+ if( gb->size > BUFFER_MAX_SIZE ) {
+ warning_handler( "Buffer reached MAX_SIZE of %d", BUFFER_MAX_SIZE );
+ buffer_free( gb );
+ return 0;
+ }
+
+ char* new_data = (char*) safe_malloc( gb->size );
+
+ strcpy( new_data, gb->buf );
+ free( gb->buf );
+ gb->buf = new_data;
+
+ strcat( gb->buf, data );
+ gb->n_used = total_len;
+ return total_len;
+}
+
+
+int buffer_reset( growing_buffer *gb){
+ if( gb == NULL ) { return 0; }
+ if( gb->buf == NULL ) { return 0; }
+ memset( gb->buf, 0, gb->size );
+ gb->n_used = 0;
+ return 1;
+}
+
+int buffer_free( growing_buffer* gb ) {
+ if( gb == NULL )
+ return 0;
+ free( gb->buf );
+ free( gb );
+ return 1;
+}
+
+char* buffer_data( growing_buffer *gb) {
+ return strdup( gb->buf );
+}
+
+
+
+
+
+// ---------------------------------------------------------------------------------
+// Config module
+// ---------------------------------------------------------------------------------
+
+
+// ---------------------------------------------------------------------------------
+// Allocate and build the conf_reader. This only has to happen once in a given
+// system. Repeated calls are ignored.
+// ---------------------------------------------------------------------------------
+void config_reader_init( char* config_file ) {
+ if( conf_reader == NULL ) {
+
+ if( config_file == NULL || strlen(config_file) == 0 ) {
+ fatal_handler( "config_reader_init(): No config file specified" );
+ return;
+ }
+
+ size_t len = sizeof( config_reader );
+ conf_reader = (config_reader*) safe_malloc( len );
+
+ conf_reader->config_doc = xmlParseFile( config_file );
+ conf_reader->xpathCx = xmlXPathNewContext( conf_reader->config_doc );
+ if( conf_reader->xpathCx == NULL ) {
+ fatal_handler( "config_reader_init(): Unable to create xpath context");
+ return;
+ }
+ }
+}
+
+char* config_value( const char* xp_query, ... ) {
+
+ if( conf_reader == NULL || xp_query == NULL ) {
+ fatal_handler( "config_value(): NULL param(s)" );
+ return NULL;
+ }
+
+ int slen = strlen(xp_query) + 512;/* this is unsafe ... */
+ char xpath_query[ slen ];
+ memset( xpath_query, 0, slen );
+ va_list va_args;
+ va_start(va_args, xp_query);
+ vsprintf(xpath_query, xp_query, va_args);
+ va_end(va_args);
+
+
+ char* val;
+ int len = strlen(xpath_query) + 100;
+ char alert_buffer[len];
+ memset( alert_buffer, 0, len );
+
+ // build the xpath object
+ xmlXPathObjectPtr xpathObj = xmlXPathEvalExpression( BAD_CAST xpath_query, conf_reader->xpathCx );
+
+ if( xpathObj == NULL ) {
+ sprintf( alert_buffer, "Could not build xpath object: %s", xpath_query );
+ fatal_handler( alert_buffer );
+ return NULL;
+ }
+
+
+ if( xpathObj->type == XPATH_NODESET ) {
+
+ // ----------------------------------------------------------------------------
+ // Grab nodeset from xpath query, then first node, then first text node and
+ // finaly the text node's value
+ // ----------------------------------------------------------------------------
+ xmlNodeSet* node_list = xpathObj->nodesetval;
+ if( node_list == NULL ) {
+ sprintf( alert_buffer, "Could not build xpath object: %s", xpath_query );
+ warning_handler(alert_buffer);
+ return NULL;
+ }
+
+ if( node_list->nodeNr == 0 ) {
+ sprintf( alert_buffer, "Config XPATH query returned 0 results: %s", xpath_query );
+ warning_handler(alert_buffer);
+ return NULL;
+ }
+
+
+ xmlNodePtr element_node = *(node_list)->nodeTab;
+ if( element_node == NULL ) {
+ sprintf( alert_buffer, "Config XPATH query returned 0 results: %s", xpath_query );
+ warning_handler(alert_buffer);
+ return NULL;
+ }
+
+ xmlNodePtr text_node = element_node->children;
+ if( text_node == NULL ) {
+ sprintf( alert_buffer, "Config variable has no value: %s", xpath_query );
+ warning_handler(alert_buffer);
+ return NULL;
+ }
+
+ val = text_node->content;
+ if( val == NULL ) {
+ sprintf( alert_buffer, "Config variable has no value: %s", xpath_query );
+ warning_handler(alert_buffer);
+ return NULL;
+ }
+
+
+ } else {
+ sprintf( alert_buffer, "Xpath evaluation failed: %s", xpath_query );
+ warning_handler(alert_buffer);
+ return NULL;
+ }
+
+ char* value = strdup(val);
+ if( value == NULL ) { fatal_handler( "config_value(): Out of Memory!" ); }
+
+ // Free XPATH structures
+ if( xpathObj != NULL ) xmlXPathFreeObject( xpathObj );
+
+ return value;
+}
+
+
+void config_reader_free() {
+ if( conf_reader == NULL ) { return; }
+ xmlXPathFreeContext( conf_reader->xpathCx );
+ xmlFreeDoc( conf_reader->config_doc );
+ free( conf_reader );
+ conf_reader = NULL;
+}
--- /dev/null
+#include "transport_client.h"
+
+
+//int main( int argc, char** argv );
+
+/*
+int main( int argc, char** argv ) {
+
+ transport_message* recv;
+ transport_message* send;
+
+ transport_client* client = client_init( "spacely.georgialibraries.org", 5222 );
+
+ // try to connect, allow 15 second connect timeout
+ if( client_connect( client, "admin", "asdfjkjk", "system", 15 ) ) {
+ printf("Connected...\n");
+ } else {
+ printf( "NOT Connected...\n" ); exit(99);
+ }
+
+ while( (recv = client_recv( client, -1 )) ) {
+
+ if( recv->body ) {
+ int len = strlen(recv->body);
+ char buf[len + 20];
+ memset( buf, 0, len + 20);
+ sprintf( buf, "Echoing...%s", recv->body );
+ send = message_init( buf, "Echoing Stuff", "12345", recv->sender, "" );
+ } else {
+ send = message_init( " * ECHOING * ", "Echoing Stuff", "12345", recv->sender, "" );
+ }
+
+ if( send == NULL ) { printf("something's wrong"); }
+ client_send_message( client, send );
+
+ message_free( send );
+ message_free( recv );
+ }
+
+ printf( "ended recv loop\n" );
+
+ return 0;
+
+}
+*/
+
+
+transport_client* client_init( char* server, int port ) {
+
+ if(server == NULL) return NULL;
+
+ /* build and clear the client object */
+ size_t c_size = sizeof( transport_client);
+ transport_client* client = (transport_client*) safe_malloc( c_size );
+
+ /* build and clear the message list */
+ size_t l_size = sizeof( transport_message_list );
+ client->m_list = (transport_message_list*) safe_malloc( l_size );
+
+ client->m_list->type = MESSAGE_LIST_HEAD;
+ client->session = init_transport( server, port, client );
+
+
+ if(client->session == NULL) {
+ fatal_handler( "client_init(): Out of Memory");
+ return NULL;
+ }
+ client->session->message_callback = client_message_handler;
+
+ return client;
+}
+
+int client_connect( transport_client* client,
+ char* username, char* password, char* resource, int connect_timeout ) {
+ if(client == NULL) return 0;
+ return session_connect( client->session, username, password, resource, connect_timeout );
+}
+
+int client_disconnect( transport_client* client ) {
+ if( client == NULL ) { return 0; }
+ return session_disconnect( client->session );
+}
+
+int client_connected( transport_client* client ) {
+ if(client == NULL) return 0;
+ return client->session->state_machine->connected;
+}
+
+int client_send_message( transport_client* client, transport_message* msg ) {
+ if(client == NULL) return 0;
+ return session_send_msg( client->session, msg );
+}
+
+
+transport_message* client_recv( transport_client* client, int timeout ) {
+ if( client == NULL ) { return NULL; }
+
+ transport_message_node* node;
+ transport_message* msg;
+
+
+ /* see if there are any message in the messages queue */
+ if( client->m_list->next != NULL ) {
+ /* pop off the first one... */
+ node = client->m_list->next;
+ client->m_list->next = node->next;
+ msg = node->message;
+ free( node );
+ return msg;
+ }
+
+ if( timeout == -1 ) { /* wait potentially forever for data to arrive */
+
+ while( client->m_list->next == NULL ) {
+ if( ! session_wait( client->session, -1 ) ) {
+ return NULL;
+ }
+ }
+
+ } else { /* wait at most timeout seconds */
+
+
+ /* if not, loop up to 'timeout' seconds waiting for data to arrive */
+ time_t start = time(NULL);
+ time_t remaining = (time_t) timeout;
+
+ int counter = 0;
+
+ int wait_ret;
+ while( client->m_list->next == NULL && remaining >= 0 ) {
+
+ if( ! (wait_ret= session_wait( client->session, remaining)) )
+ return NULL;
+
+ ++counter;
+
+#ifdef _ROUTER
+ // session_wait returns -1 if there is no more data and we're a router
+ if( remaining == 0 && wait_ret == -1 ) {
+ break;
+ }
+#else
+ if( remaining == 0 ) // or infinite loop
+ break;
+#endif
+
+ remaining -= (int) (time(NULL) - start);
+ }
+
+ info_handler("It took %d reads to grab this messag", counter);
+ }
+
+ /* again, see if there are any messages in the message queue */
+ if( client->m_list->next != NULL ) {
+ /* pop off the first one... */
+ node = client->m_list->next;
+ client->m_list->next = node->next;
+ msg = node->message;
+ free( node );
+ return msg;
+ } else {
+ return NULL;
+ }
+}
+
+/* throw the message into the message queue */
+void client_message_handler( void* client, transport_message* msg ){
+
+ if(client == NULL) return;
+ if(msg == NULL) return;
+
+ transport_client* cli = (transport_client*) client;
+
+ size_t len = sizeof(transport_message_node);
+ transport_message_node* node =
+ (transport_message_node*) safe_malloc(len);
+ node->type = MESSAGE_LIST_ITEM;
+ node->message = msg;
+
+
+ /* find the last node and put this onto the end */
+ transport_message_node* tail = cli->m_list;
+ transport_message_node* current = tail->next;
+
+ while( current != NULL ) {
+ tail = current;
+ current = current->next;
+ }
+ tail->next = node;
+}
+
+
+int client_free( transport_client* client ){
+ if(client == NULL) return 0;
+
+ session_free( client->session );
+ transport_message_node* current = client->m_list->next;
+ transport_message_node* next;
+
+ /* deallocate the list of messages */
+ while( current != NULL ) {
+ next = current->next;
+ message_free( current->message );
+ free(current);
+ current = next;
+ }
+
+ free( client->m_list );
+ free( client );
+ return 1;
+}
+
--- /dev/null
+#include "transport_message.h"
+
+
+// ---------------------------------------------------------------------------------
+// Allocates and initializes a new transport_message
+// ---------------------------------------------------------------------------------
+transport_message* message_init( char* body,
+ char* subject, char* thread, char* recipient, char* sender ) {
+
+ transport_message* msg =
+ (transport_message*) safe_malloc( sizeof(transport_message) );
+
+ if( body == NULL ) { body = ""; }
+ if( thread == NULL ) { thread = ""; }
+ if( subject == NULL ) { subject = ""; }
+ if( sender == NULL ) { sender = ""; }
+ if( recipient == NULL ) { recipient = ""; }
+
+ msg->body = strdup(body);
+ msg->thread = strdup(thread);
+ msg->subject = strdup(subject);
+ msg->recipient = strdup(recipient);
+ msg->sender = strdup(sender);
+
+ if( msg->body == NULL || msg->thread == NULL ||
+ msg->subject == NULL || msg->recipient == NULL ||
+ msg->sender == NULL ) {
+
+ fatal_handler( "message_init(): Out of Memory" );
+ return NULL;
+ }
+
+ return msg;
+}
+
+void message_set_router_info( transport_message* msg, char* router_from,
+ char* router_to, char* router_class, char* router_command, int broadcast_enabled ) {
+
+ msg->router_from = strdup(router_from);
+ msg->router_to = strdup(router_to);
+ msg->router_class = strdup(router_class);
+ msg->router_command = strdup(router_command);
+ msg->broadcast = broadcast_enabled;
+
+ if( msg->router_from == NULL || msg->router_to == NULL ||
+ msg->router_class == NULL || msg->router_command == NULL )
+ fatal_handler( "message_set_router_info(): Out of Memory" );
+
+ return;
+}
+
+
+
+/* encodes the message for traversal */
+int message_prepare_xml( transport_message* msg ) {
+ if( msg->msg_xml != NULL ) { return 1; }
+ msg->msg_xml = message_to_xml( msg );
+ return 1;
+}
+
+
+// ---------------------------------------------------------------------------------
+//
+// ---------------------------------------------------------------------------------
+int message_free( transport_message* msg ){
+ if( msg == NULL ) { return 0; }
+
+ free(msg->body);
+ free(msg->thread);
+ free(msg->subject);
+ free(msg->recipient);
+ free(msg->sender);
+ free(msg->router_from);
+ free(msg->router_to);
+ free(msg->router_class);
+ free(msg->router_command);
+ if( msg->error_type != NULL ) free(msg->error_type);
+ if( msg->msg_xml != NULL ) free(msg->msg_xml);
+ free(msg);
+ return 1;
+}
+
+// ---------------------------------------------------------------------------------
+// Allocates a char* holding the XML representation of this jabber message
+// ---------------------------------------------------------------------------------
+char* message_to_xml( const transport_message* msg ) {
+
+ int bufsize;
+ xmlChar* xmlbuf;
+ char* encoded_body;
+
+ xmlNodePtr message_node;
+ xmlNodePtr body_node;
+ xmlNodePtr thread_node;
+ xmlNodePtr subject_node;
+ xmlNodePtr error_node;
+
+ xmlDocPtr doc;
+
+ xmlKeepBlanksDefault(0);
+
+ if( ! msg ) {
+ warning_handler( "Passing NULL message to message_to_xml()");
+ return 0;
+ }
+
+ doc = xmlReadDoc( BAD_CAST "<message/>", NULL, NULL, XML_PARSE_NSCLEAN );
+ message_node = xmlDocGetRootElement(doc);
+
+ if( msg->is_error ) {
+ error_node = xmlNewChild(message_node, NULL, BAD_CAST "error" , NULL );
+ xmlAddChild( message_node, error_node );
+ xmlNewProp( error_node, BAD_CAST "type", BAD_CAST msg->error_type );
+ char code_buf[16];
+ memset( code_buf, 0, 16);
+ sprintf(code_buf, "%d", msg->error_code );
+ xmlNewProp( error_node, BAD_CAST "code", BAD_CAST code_buf );
+ }
+
+
+ /* set from and to */
+ xmlNewProp( message_node, BAD_CAST "to", BAD_CAST msg->recipient );
+ xmlNewProp( message_node, BAD_CAST "from", BAD_CAST msg->sender );
+ xmlNewProp( message_node, BAD_CAST "router_from", BAD_CAST msg->router_from );
+ xmlNewProp( message_node, BAD_CAST "router_to", BAD_CAST msg->router_to );
+ xmlNewProp( message_node, BAD_CAST "router_class", BAD_CAST msg->router_class );
+ xmlNewProp( message_node, BAD_CAST "router_command", BAD_CAST msg->router_command );
+
+ if( msg->broadcast )
+ xmlNewProp( message_node, BAD_CAST "broadcast", BAD_CAST "1" );
+
+ /* Now add nodes where appropriate */
+ char* body = msg->body;
+ char* subject = msg->subject;
+ char* thread = msg->thread;
+
+ if( thread && strlen(thread) > 0 ) {
+ thread_node = xmlNewChild(message_node, NULL, (xmlChar*) "thread", (xmlChar*) thread );
+ xmlAddChild( message_node, thread_node );
+ }
+
+ if( subject && strlen(subject) > 0 ) {
+ subject_node = xmlNewChild(message_node, NULL, (xmlChar*) "subject", (xmlChar*) subject );
+ xmlAddChild( message_node, subject_node );
+ }
+
+ if( body && strlen(body) > 0 ) {
+ body_node = xmlNewChild(message_node, NULL, (xmlChar*) "body", (xmlChar*) body );
+ xmlAddChild( message_node, body_node );
+ }
+
+
+ xmlDocDumpFormatMemory( doc, &xmlbuf, &bufsize, 0 );
+ encoded_body = strdup( (char*) xmlbuf );
+
+ if( encoded_body == NULL )
+ fatal_handler("message_to_xml(): Out of Memory");
+
+ xmlFree(xmlbuf);
+ xmlFreeDoc( doc );
+ xmlCleanupParser();
+
+
+ /*** remove the XML declaration */
+
+ int len = strlen(encoded_body);
+ char tmp[len];
+ memset( tmp, 0, len );
+ int i;
+ int found_at = 0;
+
+ /* when we reach the first >, take everything after it */
+ for( i = 0; i!= len; i++ ) {
+ if( encoded_body[i] == 62) { /* ascii > */
+
+ /* found_at holds the starting index of the rest of the doc*/
+ found_at = i + 1;
+ break;
+ }
+ }
+
+ if( found_at ) {
+ /* move the shortened doc into the tmp buffer */
+ strncpy( tmp, encoded_body + found_at, len - found_at );
+ /* move the tmp buffer back into the allocated space */
+ memset( encoded_body, 0, len );
+ strcpy( encoded_body, tmp );
+ }
+
+ return encoded_body;
+}
+
+
+
+void jid_get_username( const char* jid, char buf[] ) {
+
+ if( jid == NULL ) { return; }
+
+ /* find the @ and return whatever is in front of it */
+ int len = strlen( jid );
+ int i;
+ for( i = 0; i != len; i++ ) {
+ if( jid[i] == 64 ) { /*ascii @*/
+ strncpy( buf, jid, i );
+ return;
+ }
+ }
+}
+
+
+void jid_get_resource( const char* jid, char buf[]) {
+ if( jid == NULL ) { return; }
+ int len = strlen( jid );
+ int i;
+ for( i = 0; i!= len; i++ ) {
+ if( jid[i] == 47 ) { /* ascii / */
+ strncpy( buf, jid + i + 1, len - (i+1) );
+ }
+ }
+}
+
+void set_msg_error( transport_message* msg, char* type, int err_code ) {
+
+ if( type != NULL && strlen( type ) > 0 ) {
+ msg->error_type = safe_malloc( strlen(type)+1);
+ strcpy( msg->error_type, type );
+ msg->error_code = err_code;
+ }
+ msg->is_error = 1;
+}
--- /dev/null
+#include "transport_session.h"
+
+
+
+// ---------------------------------------------------------------------------------
+// returns a built and allocated transport_session object.
+// This codes does no network activity, only memory initilization
+// ---------------------------------------------------------------------------------
+transport_session* init_transport( char* server, int port, void* user_data ) {
+
+ /* create the session struct */
+ transport_session* session =
+ (transport_session*) safe_malloc( sizeof(transport_session) );
+
+ session->user_data = user_data;
+
+ /* initialize the data buffers */
+ session->body_buffer = buffer_init( JABBER_BODY_BUFSIZE );
+ session->subject_buffer = buffer_init( JABBER_SUBJECT_BUFSIZE );
+ session->thread_buffer = buffer_init( JABBER_THREAD_BUFSIZE );
+ session->from_buffer = buffer_init( JABBER_JID_BUFSIZE );
+ session->status_buffer = buffer_init( JABBER_STATUS_BUFSIZE );
+ session->recipient_buffer = buffer_init( JABBER_JID_BUFSIZE );
+ session->message_error_type = buffer_init( JABBER_JID_BUFSIZE );
+
+ /* for OILS extensions */
+ session->router_to_buffer = buffer_init( JABBER_JID_BUFSIZE );
+ session->router_from_buffer = buffer_init( JABBER_JID_BUFSIZE );
+ session->router_class_buffer = buffer_init( JABBER_JID_BUFSIZE );
+ session->router_command_buffer = buffer_init( JABBER_JID_BUFSIZE );
+
+
+
+ if( session->body_buffer == NULL || session->subject_buffer == NULL ||
+ session->thread_buffer == NULL || session->from_buffer == NULL ||
+ session->status_buffer == NULL || session->recipient_buffer == NULL ||
+ session->router_to_buffer == NULL || session->router_from_buffer == NULL ||
+ session->router_class_buffer == NULL || session->router_command_buffer == NULL ) {
+
+ fatal_handler( "init_transport(): buffer_init returned NULL" );
+ return 0;
+ }
+
+
+ /* initialize the jabber state machine */
+ session->state_machine = (jabber_machine*) safe_malloc( sizeof(jabber_machine) );
+
+ /* initialize the sax push parser */
+ session->parser_ctxt = xmlCreatePushParserCtxt(SAXHandler, session, "", 0, NULL);
+
+ /* initialize the transport_socket structure */
+ session->sock_obj = (transport_socket*) safe_malloc( sizeof(transport_socket) );
+
+ //int serv_size = strlen( server );
+ session->sock_obj->server = server;
+ session->sock_obj->port = port;
+ session->sock_obj->data_received_callback = &grab_incoming;
+
+ /* this will be handed back to us in callbacks */
+ session->sock_obj->user_data = session;
+
+ return session;
+}
+
+/* XXX FREE THE BUFFERS */
+int session_free( transport_session* session ) {
+ if( ! session ) { return 0; }
+
+ if( session->sock_obj )
+ free( session->sock_obj );
+
+ if( session->state_machine ) free( session->state_machine );
+ if( session->parser_ctxt) {
+ xmlCleanupCharEncodingHandlers();
+ xmlFreeDoc( session->parser_ctxt->myDoc );
+ xmlFreeParserCtxt(session->parser_ctxt);
+ }
+ xmlCleanupParser();
+
+ buffer_free(session->body_buffer);
+ buffer_free(session->subject_buffer);
+ buffer_free(session->thread_buffer);
+ buffer_free(session->from_buffer);
+ buffer_free(session->recipient_buffer);
+ buffer_free(session->status_buffer);
+ buffer_free(session->message_error_type);
+ buffer_free(session->router_to_buffer);
+ buffer_free(session->router_from_buffer);
+ buffer_free(session->router_class_buffer);
+ buffer_free(session->router_command_buffer);
+
+
+ free( session );
+ return 1;
+}
+
+
+int session_wait( transport_session* session, int timeout ) {
+ if( ! session || ! session->sock_obj ) {
+ return 0;
+ }
+ int ret = tcp_wait( session->sock_obj, timeout );
+ if( ! ret ) {
+ session->state_machine->connected = 0;
+ }
+ return ret;
+}
+
+int session_send_msg(
+ transport_session* session, transport_message* msg ) {
+
+ if( ! session ) { return 0; }
+
+ if( ! session->state_machine->connected ) {
+ warning_handler("State machine is not connected in send_msg()");
+ return 0;
+ }
+
+ message_prepare_xml( msg );
+ tcp_send( session->sock_obj, msg->msg_xml );
+
+ return 1;
+
+}
+
+
+/* connects to server and connects to jabber */
+int session_connect( transport_session* session,
+ const char* username, const char* password, const char* resource, int connect_timeout ) {
+
+ int size1 = 0;
+ int size2 = 0;
+
+ if( ! session ) {
+ warning_handler( "session is null in connect" );
+ return 0;
+ }
+
+
+ /*
+ session->state_machine->connected =
+ tcp_connected( session->sock_obj );
+
+ if( session->state_machine->connected ) {
+ return 1;
+ }
+ */
+
+
+ char* server = session->sock_obj->server;
+
+ if( ! session->sock_obj ) {
+ return 0;
+ }
+
+ if( ! session->sock_obj->connected ) {
+ if( ! tcp_connect( session->sock_obj ))
+ return 0;
+ }
+
+ /* the first Jabber connect stanza */
+ size1 = 100 + strlen( server );
+ char stanza1[ size1 ];
+ memset( stanza1, 0, size1 );
+ sprintf( stanza1,
+ "<stream:stream to='%s' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>",
+ server );
+
+ /* the second jabber connect stanza including login info*/
+ /* currently, we only support plain text login */
+ size2 = 150 + strlen( username ) + strlen(password) + strlen(resource);
+ char stanza2[ size2 ];
+ memset( stanza2, 0, size2 );
+
+ sprintf( stanza2,
+ "<iq id='123456789' type='set'><query xmlns='jabber:iq:auth'><username>%s</username><password>%s</password><resource>%s</resource></query></iq>",
+ username, password, resource );
+
+ /* send the first stanze */
+ session->state_machine->connecting = CONNECTING_1;
+ if( ! tcp_send( session->sock_obj, stanza1 ) ) {
+ warning_handler("error sending");
+ return 0;
+ }
+
+ /* wait for reply */
+ tcp_wait( session->sock_obj, connect_timeout ); /* make the timeout smarter XXX */
+
+ /* server acknowledges our existence, now see if we can login */
+ if( session->state_machine->connecting == CONNECTING_2 ) {
+ if( ! tcp_send( session->sock_obj, stanza2 ) ) {
+ warning_handler("error sending");
+ return 0;
+ }
+ }
+
+ /* wait for reply */
+ tcp_wait( session->sock_obj, connect_timeout );
+
+
+ if( session->state_machine->connected ) {
+ /* yar! */
+ return 1;
+ }
+
+ return 0;
+}
+
+// ---------------------------------------------------------------------------------
+// TCP data callback. Shove the data into the push parser.
+// ---------------------------------------------------------------------------------
+void grab_incoming( void * session, char* data ) {
+ transport_session* ses = (transport_session*) session;
+ if( ! ses ) { return; }
+ xmlParseChunk(ses->parser_ctxt, data, strlen(data), 0);
+}
+
+
+void startElementHandler(
+ void *session, const xmlChar *name, const xmlChar **atts) {
+
+ transport_session* ses = (transport_session*) session;
+ if( ! ses ) { return; }
+
+
+ if( strcmp( name, "message" ) == 0 ) {
+ ses->state_machine->in_message = 1;
+ buffer_add( ses->from_buffer, get_xml_attr( atts, "from" ) );
+ buffer_add( ses->recipient_buffer, get_xml_attr( atts, "to" ) );
+ buffer_add( ses->router_from_buffer, get_xml_attr( atts, "router_from" ) );
+ buffer_add( ses->router_to_buffer, get_xml_attr( atts, "router_to" ) );
+ buffer_add( ses->router_class_buffer, get_xml_attr( atts, "router_class" ) );
+ buffer_add( ses->router_command_buffer, get_xml_attr( atts, "router_command" ) );
+ char* broadcast = get_xml_attr( atts, "broadcast" );
+ if( broadcast )
+ ses->router_broadcast = atoi( broadcast );
+
+ return;
+ }
+
+ if( ses->state_machine->in_message ) {
+
+ if( strcmp( name, "body" ) == 0 ) {
+ ses->state_machine->in_message_body = 1;
+ return;
+ }
+
+ if( strcmp( name, "subject" ) == 0 ) {
+ ses->state_machine->in_subject = 1;
+ return;
+ }
+
+ if( strcmp( name, "thread" ) == 0 ) {
+ ses->state_machine->in_thread = 1;
+ return;
+ }
+
+ }
+
+ if( strcmp( name, "presence" ) == 0 ) {
+ ses->state_machine->in_presence = 1;
+ buffer_add( ses->from_buffer, get_xml_attr( atts, "from" ) );
+ buffer_add( ses->recipient_buffer, get_xml_attr( atts, "to" ) );
+ return;
+ }
+
+ if( strcmp( name, "status" ) == 0 ) {
+ ses->state_machine->in_status = 1;
+ return;
+ }
+
+
+ if( strcmp( name, "stream:error" ) == 0 ) {
+ ses->state_machine->in_error = 1;
+ warning_handler( "Received <stream:error> message from Jabber server" );
+ return;
+ }
+
+
+ /* first server response from a connect attempt */
+ if( strcmp( name, "stream:stream" ) == 0 ) {
+ if( ses->state_machine->connecting == CONNECTING_1 ) {
+ ses->state_machine->connecting = CONNECTING_2;
+ }
+ }
+
+ if( strcmp( name, "error" ) == 0 ) {
+ ses->state_machine->in_message_error = 1;
+ buffer_add( ses->message_error_type, get_xml_attr( atts, "type" ) );
+ ses->message_error_code = atoi( get_xml_attr( atts, "code" ) );
+ warning_handler( "Received <error> message" );
+ return;
+ }
+
+ if( strcmp( name, "iq" ) == 0 ) {
+ ses->state_machine->in_iq = 1;
+
+ if( strcmp( get_xml_attr(atts, "type"), "result") == 0
+ && ses->state_machine->connecting == CONNECTING_2 ) {
+ ses->state_machine->connected = 1;
+ ses->state_machine->connecting = 0;
+ return;
+ }
+
+ if( strcmp( get_xml_attr(atts, "type"), "error") == 0 ) {
+ warning_handler( "Error connecting to jabber" );
+ return;
+ }
+ }
+}
+
+char* get_xml_attr( const xmlChar** atts, char* attr_name ) {
+ int i;
+ if (atts != NULL) {
+ for(i = 0;(atts[i] != NULL);i++) {
+ if( strcmp( atts[i++], attr_name ) == 0 ) {
+ if( atts[i] != NULL ) {
+ return (char*) atts[i];
+ }
+ }
+ }
+ }
+ return NULL;
+}
+
+
+// ------------------------------------------------------------------
+// See which tags are ending
+// ------------------------------------------------------------------
+void endElementHandler( void *session, const xmlChar *name) {
+ transport_session* ses = (transport_session*) session;
+ if( ! ses ) { return; }
+
+ if( strcmp( name, "message" ) == 0 ) {
+
+ /* pass off the message info the callback */
+ if( ses->message_callback ) {
+
+ /* here it's ok to pass in the raw buffers because
+ message_init allocates new space for the chars
+ passed in */
+ transport_message* msg = message_init(
+ ses->body_buffer->buf,
+ ses->subject_buffer->buf,
+ ses->thread_buffer->buf,
+ ses->recipient_buffer->buf,
+ ses->from_buffer->buf );
+
+ message_set_router_info( msg,
+ ses->router_from_buffer->buf,
+ ses->router_to_buffer->buf,
+ ses->router_class_buffer->buf,
+ ses->router_command_buffer->buf,
+ ses->router_broadcast );
+
+ if( ses->message_error_type->n_used > 0 ) {
+ set_msg_error( msg, ses->message_error_type->buf, ses->message_error_code );
+ }
+
+ if( msg == NULL ) { return; }
+ ses->message_callback( ses->user_data, msg );
+ }
+
+ ses->state_machine->in_message = 0;
+ reset_session_buffers( session );
+ return;
+ }
+
+ if( strcmp( name, "body" ) == 0 ) {
+ ses->state_machine->in_message_body = 0;
+ return;
+ }
+
+ if( strcmp( name, "subject" ) == 0 ) {
+ ses->state_machine->in_subject = 0;
+ return;
+ }
+
+ if( strcmp( name, "thread" ) == 0 ) {
+ ses->state_machine->in_thread = 0;
+ return;
+ }
+
+ if( strcmp( name, "iq" ) == 0 ) {
+ ses->state_machine->in_iq = 0;
+ if( ses->message_error_code > 0 ) {
+ warning_handler( "Error in IQ packet: code %d", ses->message_error_code );
+ warning_handler( "Error 401 means not authorized" );
+ }
+ reset_session_buffers( session );
+ return;
+ }
+
+ if( strcmp( name, "presence" ) == 0 ) {
+ ses->state_machine->in_presence = 0;
+ /*
+ if( ses->presence_callback ) {
+ // call the callback with the status, etc.
+ }
+ */
+ reset_session_buffers( session );
+ return;
+ }
+
+ if( strcmp( name, "status" ) == 0 ) {
+ ses->state_machine->in_status = 0;
+ return;
+ }
+
+ if( strcmp( name, "error" ) == 0 ) {
+ ses->state_machine->in_message_error = 0;
+ return;
+ }
+
+ if( strcmp( name, "error:error" ) == 0 ) {
+ ses->state_machine->in_error = 0;
+ return;
+ }
+}
+
+int reset_session_buffers( transport_session* ses ) {
+ buffer_reset( ses->body_buffer );
+ buffer_reset( ses->subject_buffer );
+ buffer_reset( ses->thread_buffer );
+ buffer_reset( ses->from_buffer );
+ buffer_reset( ses->recipient_buffer );
+ buffer_reset( ses->router_from_buffer );
+ buffer_reset( ses->router_to_buffer );
+ buffer_reset( ses->router_class_buffer );
+ buffer_reset( ses->router_command_buffer );
+ buffer_reset( ses->message_error_type );
+
+ return 1;
+}
+
+// ------------------------------------------------------------------
+// takes data out of the body of the message and pushes it into
+// the appropriate buffer
+// ------------------------------------------------------------------
+void characterHandler(
+ void *session, const xmlChar *ch, int len) {
+
+ char data[len+1];
+ memset( data, 0, len+1 );
+ strncpy( data, (char*) ch, len );
+ data[len] = 0;
+
+ //printf( "Handling characters: %s\n", data );
+ transport_session* ses = (transport_session*) session;
+ if( ! ses ) { return; }
+
+ /* set the various message parts */
+ if( ses->state_machine->in_message ) {
+
+ if( ses->state_machine->in_message_body ) {
+ buffer_add( ses->body_buffer, data );
+ }
+
+ if( ses->state_machine->in_subject ) {
+ buffer_add( ses->subject_buffer, data );
+ }
+
+ if( ses->state_machine->in_thread ) {
+ buffer_add( ses->thread_buffer, data );
+ }
+ }
+
+ /* set the presence status */
+ if( ses->state_machine->in_presence && ses->state_machine->in_status ) {
+ buffer_add( ses->status_buffer, data );
+ }
+
+ if( ses->state_machine->in_error ) {
+ /* for now... */
+ warning_handler( "ERROR Xml fragment: %s\n", ch );
+ }
+
+}
+
+/* XXX change to warning handlers */
+void parseWarningHandler( void *session, const char* msg, ... ) {
+
+ va_list args;
+ va_start(args, msg);
+ fprintf(stdout, "WARNING");
+ vfprintf(stdout, msg, args);
+ va_end(args);
+ fprintf(stderr, "XML WARNING: %s\n", msg );
+}
+
+void parseErrorHandler( void *session, const char* msg, ... ){
+
+ va_list args;
+ va_start(args, msg);
+ fprintf(stdout, "ERROR");
+ vfprintf(stdout, msg, args);
+ va_end(args);
+ fprintf(stderr, "XML ERROR: %s\n", msg );
+
+}
+
+int session_disconnect( transport_session* session ) {
+ if( session == NULL ) { return 0; }
+ tcp_send( session->sock_obj, "</stream:stream>");
+ return tcp_disconnect( session->sock_obj );
+}
+
--- /dev/null
+#include "transport_socket.h"
+
+
+/*
+int main( char* argc, char** argv ) {
+
+ transport_socket sock_obj;
+ sock_obj.port = 5222;
+ sock_obj.server = "10.0.0.4";
+ sock_obj.data_received_callback = &print_stuff;
+
+ printf("connecting...\n");
+ if( (tcp_connect( &sock_obj )) < 0 ) {
+ printf( "error connecting" );
+ }
+
+ printf("sending...\n");
+ if( tcp_send( &sock_obj, "<stream>\n" ) < 0 ) {
+ printf( "error sending" );
+ }
+
+ printf("waiting...\n");
+ if( tcp_wait( &sock_obj, 15 ) < 0 ) {
+ printf( "error receiving" );
+ }
+
+ printf("disconnecting...\n");
+ tcp_disconnect( &sock_obj );
+
+}
+*/
+
+
+// returns the socket fd, -1 on error
+int tcp_connect( transport_socket* sock_obj ){
+
+ if( sock_obj == NULL ) {
+ fatal_handler( "connect(): null sock_obj" );
+ return -1;
+ }
+ struct sockaddr_in remoteAddr, localAddr;
+ struct hostent *hptr;
+ int sock_fd;
+
+ #ifdef WIN32
+ WSADATA data;
+ char bfr;
+ if( WSAStartup(MAKEWORD(1,1), &data) ) {
+ fatal_handler( "somethin's broke with windows socket startup" );
+ return -1;
+ }
+ #endif
+
+
+
+ // ------------------------------------------------------------------
+ // Create the socket
+ // ------------------------------------------------------------------
+ if( (sock_fd = socket( AF_INET, SOCK_STREAM, 0 )) < 0 ) {
+ fatal_handler( "tcp_connect(): Cannot create socket" );
+ return -1;
+ }
+
+ // ------------------------------------------------------------------
+ // Get the hostname
+ // ------------------------------------------------------------------
+ if( (hptr = gethostbyname( sock_obj->server ) ) == NULL ) {
+ fatal_handler( "tcp_connect(): Unknown Host" );
+ return -1;
+ }
+
+ // ------------------------------------------------------------------
+ // Construct server info struct
+ // ------------------------------------------------------------------
+ memset( &remoteAddr, 0, sizeof(remoteAddr));
+ remoteAddr.sin_family = AF_INET;
+ remoteAddr.sin_port = htons( sock_obj->port );
+ memcpy( (char*) &remoteAddr.sin_addr.s_addr,
+ hptr->h_addr_list[0], hptr->h_length );
+
+ // ------------------------------------------------------------------
+ // Construct local info struct
+ // ------------------------------------------------------------------
+ memset( &localAddr, 0, sizeof( localAddr ) );
+ localAddr.sin_family = AF_INET;
+ localAddr.sin_addr.s_addr = htonl( INADDR_ANY );
+ localAddr.sin_port = htons(0);
+
+ // ------------------------------------------------------------------
+ // Bind to a local port
+ // ------------------------------------------------------------------
+ if( bind( sock_fd, (struct sockaddr *) &localAddr, sizeof( localAddr ) ) < 0 ) {
+ fatal_handler( "tcp_connect(): Cannot bind to local port" );
+ return -1;
+ }
+
+ // ------------------------------------------------------------------
+ // Connect to server
+ // ------------------------------------------------------------------
+ if( connect( sock_fd, (struct sockaddr*) &remoteAddr, sizeof( struct sockaddr_in ) ) < 0 ) {
+ fatal_handler( "tcp_connect(): Cannot connect to server %s", sock_obj->server );
+ return -1;
+ }
+
+ sock_obj->sock_fd = sock_fd;
+ sock_obj->connected = 1;
+ return sock_fd;
+
+}
+
+
+int tcp_send( transport_socket* sock_obj, const char* data ){
+
+ if( sock_obj == NULL ) {
+ fatal_handler( "tcp_send(): null sock_obj" );
+ return 0;
+ }
+
+ //fprintf( stderr, "TCP Sending: \n%s\n", data );
+
+ // ------------------------------------------------------------------
+ // Send the data down the TCP pipe
+ // ------------------------------------------------------------------
+ if( send( sock_obj->sock_fd, data, strlen(data), 0 ) < 0 ) {
+ fatal_handler( "tcp_send(): Error sending data" );
+ return 0;
+ }
+ return 1;
+}
+
+
+int tcp_disconnect( transport_socket* sock_obj ){
+
+ if( sock_obj == NULL ) {
+ fatal_handler( "tcp_disconnect(): null sock_obj" );
+ return -1;
+ }
+
+ if( close( sock_obj->sock_fd ) == -1 ) {
+
+ // ------------------------------------------------------------------
+ // Not really worth throwing an exception for... should be logged.
+ // ------------------------------------------------------------------
+ warning_handler( "tcp_disconnect(): Error closing socket" );
+ return -1;
+ }
+
+ return 0;
+}
+
+// ------------------------------------------------------------------
+// And now for the gory C socket code.
+// Returns 0 on failure, 1 otherwise
+// ------------------------------------------------------------------
+int tcp_wait( transport_socket* sock_obj, int timeout ){
+
+ if( sock_obj == NULL ) {
+ fatal_handler( "tcp_wait(): null sock_obj" );
+ return 0;
+ }
+
+ int n = 0;
+ int retval = 0;
+ char buf[BUFSIZE];
+ int sock_fd = sock_obj->sock_fd;
+
+
+ fd_set read_set;
+
+ FD_ZERO( &read_set );
+ FD_SET( sock_fd, &read_set );
+
+ // ------------------------------------------------------------------
+ // Build the timeval struct
+ // ------------------------------------------------------------------
+ struct timeval tv;
+ tv.tv_sec = timeout;
+ tv.tv_usec = 0;
+
+ if( timeout == -1 ) {
+
+ // ------------------------------------------------------------------
+ // If timeout is -1, there is no timeout passed to the call to select
+ // ------------------------------------------------------------------
+ if( (retval = select( sock_fd + 1 , &read_set, NULL, NULL, NULL)) == -1 ) {
+ warning_handler( "Call to select failed" );
+ return 0;
+ }
+
+ } else if( timeout != 0 ) { /* timeout of 0 means don't block */
+
+ if( (retval = select( sock_fd + 1 , &read_set, NULL, NULL, &tv)) == -1 ) {
+ warning_handler( "Call to select failed" );
+ return 0;
+ }
+ }
+
+ memset( &buf, 0, BUFSIZE );
+
+ if( set_fl( sock_fd, O_NONBLOCK ) < 0 )
+ return 0;
+
+#ifdef _ROUTER // just read one buffer full of data
+
+ n = recv(sock_fd, buf, BUFSIZE-1, 0);
+ sock_obj->data_received_callback( sock_obj->user_data, buf );
+ if( n == 0 )
+ n = -1;
+
+#else // read everything we can
+
+ while( (n = recv(sock_fd, buf, BUFSIZE-1, 0) ) > 0 ) {
+ //printf("\nReceived: %s\n", buf);
+ sock_obj->data_received_callback( sock_obj->user_data, buf );
+ memset( &buf, 0, BUFSIZE );
+ }
+
+#endif
+
+ if( clr_fl( sock_fd, O_NONBLOCK ) < 0 ) {
+ return 0;
+ }
+
+ if( n < 0 ) {
+ if( errno != EAGAIN ) {
+ warning_handler( " * Error reading socket with errno %d", errno );
+ return 0;
+ }
+ }
+
+#ifdef _ROUTER
+ return n;
+#else
+ return 1;
+#endif
+
+}
+
+int set_fl( int fd, int flags ) {
+
+ int val;
+
+ if( (val = fcntl( fd, F_GETFL, 0) ) < 0 ) {
+ fatal_handler("fcntl F_GETFL error");
+ return -1;
+ }
+
+ val |= flags;
+
+ if( fcntl( fd, F_SETFL, val ) < 0 ) {
+ fatal_handler( "fcntl F_SETFL error" );
+ return -1;
+ }
+ return 0;
+}
+
+int clr_fl( int fd, int flags ) {
+
+ int val;
+
+ if( (val = fcntl( fd, F_GETFL, 0) ) < 0 ) {
+ fatal_handler("fcntl F_GETFL error" );
+ return -1;
+ }
+
+ val &= ~flags;
+
+ if( fcntl( fd, F_SETFL, val ) < 0 ) {
+ fatal_handler( "fcntl F_SETFL error" );
+ return -1;
+ }
+ return 0;
+}
+
+
+/*
+int tcp_connected( transport_socket* obj ) {
+
+ int ret;
+ if( ! obj->sock_fd ) { return 0; }
+
+ ret = read( obj->sock_fd , NULL,0 );
+ if( ret <= 0 ) {
+ return 0;
+ }
+ return 1;
+}
+*/
+
--- /dev/null
+Patch Source Location Purpose
+------------------------------------------------------------------------------------------------------
+
+mod_offline.c jabberd-2.0s4 jabberd-2.0s4/sm/mod_offline.c Disables offline storage
+------------------------------------------------------------------------------------------------------
+
+nad.c jabberd-2.0s4 jabberd-2.0s4/util/nad.c Fixes segfault in a branch of code that we don't use
+
--- /dev/null
+/*
+ * jabberd - Jabber Open Source Server
+ * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney,
+ * Ryan Eatmon, Robert Norris
+ *
+ * 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, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * 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, MA02111-1307USA
+ */
+
+
+/** ! ! ! Patched version to disable offline storage for jabberd-2.0s4 ! ! ! */
+
+#include "sm.h"
+
+/** @file sm/mod_offline.c
+ * @brief offline storage
+ * @author Robert Norris
+ * $Date$
+ * $Revision$
+ */
+
+typedef struct _mod_offline_st {
+ int dropmessages;
+ int dropsubscriptions;
+} *mod_offline_t;
+
+static mod_ret_t _offline_in_sess(mod_instance_t mi, sess_t sess, pkt_t pkt) {
+ st_ret_t ret;
+ os_t os;
+ os_object_t o;
+ os_type_t ot;
+ nad_t nad;
+ pkt_t queued;
+ int ns, elem, attr;
+ char cttl[15], cstamp[18];
+ time_t ttl, stamp;
+
+ /* if they're becoming available for the first time */
+ if(pkt->type == pkt_PRESENCE && pkt->to == NULL && sess->user->top == NULL) {
+
+ ret = storage_get(pkt->sm->st, "queue", jid_user(sess->jid), NULL, &os);
+ if(ret != st_SUCCESS) {
+ log_debug(ZONE, "storage_get returned %d", ret);
+ return mod_PASS;
+ }
+
+ if(os_iter_first(os))
+ do {
+ o = os_iter_object(os);
+
+ if(os_object_get(o, "xml", (void **) &nad, &ot)) {
+ queued = pkt_new(pkt->sm, nad_copy(nad));
+ if(queued == NULL) {
+ log_debug(ZONE, "invalid queued packet, not delivering");
+ } else {
+ /* check expiry as necessary */
+ if((ns = nad_find_scoped_namespace(queued->nad, uri_EXPIRE, NULL)) >= 0 &&
+ (elem = nad_find_elem(queued->nad, 1, ns, "x", 1)) >= 0 &&
+ (attr = nad_find_attr(queued->nad, elem, -1, "seconds", NULL)) >= 0) {
+ snprintf(cttl, 15, "%.*s", NAD_AVAL_L(queued->nad, attr), NAD_AVAL(queued->nad, attr));
+ ttl = atoi(cttl);
+
+ /* it should have a x:delay stamp, because we stamp everything we store */
+ if((ns = nad_find_scoped_namespace(queued->nad, uri_DELAY, NULL)) >= 0 &&
+ (elem = nad_find_elem(queued->nad, 1, ns, "x", 1)) >= 0 &&
+ (attr = nad_find_attr(queued->nad, elem, -1, "stamp", NULL)) >= 0) {
+ snprintf(cstamp, 18, "%.*s", NAD_AVAL_L(queued->nad, attr), NAD_AVAL(queued->nad, attr));
+ stamp = datetime_in(cstamp);
+
+ if(stamp + ttl <= time(NULL)) {
+ log_debug(ZONE, "queued packet has expired, dropping");
+ pkt_free(queued);
+ continue;
+ }
+ }
+ }
+
+ log_debug(ZONE, "delivering queued packet to %s", jid_full(sess->jid));
+ pkt_sess(queued, sess);
+ }
+ }
+ } while(os_iter_next(os));
+
+ os_free(os);
+
+ /* drop the spool */
+ storage_delete(pkt->sm->st, "queue", jid_user(sess->jid), NULL);
+ }
+
+ /* pass it so that other modules and mod_presence can get it */
+ return mod_PASS;
+}
+
+static mod_ret_t _offline_pkt_user(mod_instance_t mi, user_t user, pkt_t pkt) {
+ mod_offline_t offline = (mod_offline_t) mi->mod->private;
+ int ns, elem, attr;
+ os_t os;
+ os_object_t o;
+ pkt_t event;
+
+ /* send messages and s10ns to the top session */
+ if(user->top != NULL && (pkt->type & pkt_MESSAGE || pkt->type & pkt_S10N)) {
+ pkt_sess(pkt, user->top);
+ return mod_HANDLED;
+ }
+
+ /* save messages and s10ns for later */
+ if((pkt->type & pkt_MESSAGE && !offline->dropmessages) ||
+ (pkt->type & pkt_S10N && !offline->dropsubscriptions)) {
+ log_debug(ZONE, "saving message for later");
+
+ pkt_delay(pkt, time(NULL), user->sm->id);
+
+ /* new object */
+ os = os_new();
+ o = os_object_new(os);
+
+ os_object_put(o, "xml", pkt->nad, os_type_NAD);
+
+ /* store it */
+ switch(storage_put(user->sm->st, "queue", jid_user(user->jid), os)) {
+ case st_FAILED:
+ os_free(os);
+ return -stanza_err_INTERNAL_SERVER_ERROR;
+
+ case st_NOTIMPL:
+ os_free(os);
+ return -stanza_err_SERVICE_UNAVAILABLE; /* xmpp-im 9.5#4 */
+
+ default:
+ os_free(os);
+
+ /* send offline events if they asked for it */
+ if((ns = nad_find_scoped_namespace(pkt->nad, uri_EVENT, NULL)) >= 0 &&
+ (elem = nad_find_elem(pkt->nad, 1, ns, "x", 1)) >= 0 &&
+ nad_find_elem(pkt->nad, elem, ns, "offline", 1) >= 0) {
+
+ event = pkt_create(user->sm, "message", NULL, jid_full(pkt->from), jid_full(pkt->to));
+
+ attr = nad_find_attr(pkt->nad, 1, -1, "type", NULL);
+ if(attr >= 0)
+ nad_set_attr(event->nad, 1, -1, "type", NAD_AVAL(pkt->nad, attr), NAD_AVAL_L(pkt->nad, attr));
+
+ ns = nad_add_namespace(event->nad, uri_EVENT, NULL);
+ nad_append_elem(event->nad, ns, "x", 2);
+ nad_append_elem(event->nad, ns, "offline", 3);
+
+ nad_append_elem(event->nad, ns, "id", 3);
+ attr = nad_find_attr(pkt->nad, 1, -1, "id", NULL);
+ if(attr >= 0)
+ nad_append_cdata(event->nad, NAD_AVAL(pkt->nad, attr), NAD_AVAL_L(pkt->nad, attr), 4);
+
+ pkt_router(event);
+ }
+
+ pkt_free(pkt);
+ return mod_HANDLED;
+ }
+ }
+
+ return mod_PASS;
+}
+
+static void _offline_user_delete(mod_instance_t mi, jid_t jid) {
+ os_t os;
+ os_object_t o;
+ os_type_t ot;
+ nad_t nad;
+ pkt_t queued;
+ int ns, elem, attr;
+ char cttl[15], cstamp[18];
+ time_t ttl, stamp;
+
+ log_debug(ZONE, "deleting queue for %s", jid_user(jid));
+
+ /* bounce the queue */
+ if(storage_get(mi->mod->mm->sm->st, "queue", jid_user(jid), NULL, &os) == st_SUCCESS) {
+ if(os_iter_first(os))
+ do {
+ o = os_iter_object(os);
+
+ if(os_object_get(o, "xml", (void **) &nad, &ot)) {
+ queued = pkt_new(mi->mod->mm->sm, nad);
+ if(queued == NULL) {
+ log_debug(ZONE, "invalid queued packet, not delivering");
+ } else {
+ /* check expiry as necessary */
+ if((ns = nad_find_scoped_namespace(queued->nad, uri_EXPIRE, NULL)) >= 0 &&
+ (elem = nad_find_elem(queued->nad, 1, ns, "x", 1)) >= 0 &&
+ (attr = nad_find_attr(queued->nad, elem, -1, "seconds", NULL)) >= 0) {
+ snprintf(cttl, 15, "%.*s", NAD_AVAL_L(queued->nad, attr), NAD_AVAL(queued->nad, attr));
+ ttl = atoi(cttl);
+
+ /* it should have a x:delay stamp, because we stamp everything we store */
+ if((ns = nad_find_scoped_namespace(queued->nad, uri_DELAY, NULL)) >= 0 &&
+ (elem = nad_find_elem(queued->nad, 1, ns, "x", 1)) >= 0 &&
+ (attr = nad_find_attr(queued->nad, elem, -1, "stamp", NULL)) >= 0) {
+ snprintf(cstamp, 18, "%.*s", NAD_AVAL_L(queued->nad, attr), NAD_AVAL(queued->nad, attr));
+ stamp = datetime_in(cstamp);
+
+ if(stamp + ttl <= time(NULL)) {
+ log_debug(ZONE, "queued packet has expired, dropping");
+ pkt_free(queued);
+ continue;
+ }
+ }
+ }
+
+ log_debug(ZONE, "bouncing queued packet from %s", jid_full(queued->from));
+ pkt_router(pkt_error(queued, stanza_err_ITEM_NOT_FOUND));
+ }
+ }
+ } while(os_iter_next(os));
+
+ os_free(os);
+ }
+
+ storage_delete(mi->sm->st, "queue", jid_user(jid), NULL);
+}
+
+static void _offline_free(module_t mod) {
+ mod_offline_t offline = (mod_offline_t) mod->private;
+
+ free(offline);
+}
+
+int offline_init(mod_instance_t mi, char *arg) {
+ module_t mod = mi->mod;
+ char *configval;
+ mod_offline_t offline;
+ int dropmessages = 0;
+ int dropsubscriptions = 0;
+
+ if(mod->init) return 0;
+
+ configval = config_get_one(mod->mm->sm->config, "offline.dropmessages", 0);
+ if (configval != NULL)
+ dropmessages = 1;
+ configval = config_get_one(mod->mm->sm->config, "offline.dropsubscriptions", 0);
+ if (configval != NULL)
+ dropsubscriptions = 1;
+
+ offline = (mod_offline_t) malloc(sizeof(struct _mod_offline_st));
+ offline->dropmessages = dropmessages;
+ offline->dropsubscriptions = dropsubscriptions;
+
+ mod->private = offline;
+
+ mod->in_sess = _offline_in_sess;
+ mod->pkt_user = _offline_pkt_user;
+ mod->user_delete = _offline_user_delete;
+ mod->free = _offline_free;
+
+ return 0;
+}
--- /dev/null
+/*
+ * jabberd - Jabber Open Source Server
+ * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney,
+ * Ryan Eatmon, Robert Norris
+ *
+ * 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, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * 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, MA02111-1307USA
+ */
+
+
+/** ! ! ! Patched version to fix segfault issue for jabberd-2.0s4 ! ! ! */
+
+/**
+ * !!! Things to do (after 2.0)
+ *
+ * - make nad_find_scoped_namespace() take an element index, and only search
+ * the scope on that element (currently, it searchs all elements from
+ * end to start, which isn't really correct, though it works in most cases
+ *
+ * - new functions:
+ * * insert one nad (or part thereof) into another nad
+ * * clear a part of a nad (like xmlnode_hide)
+ *
+ * - audit use of depth array and parent (see j2 bug #792)
+ */
+
+#include "util.h"
+
+#ifdef HAVE_EXPAT
+#include "expat/expat.h"
+#endif
+
+/* define NAD_DEBUG to get pointer tracking - great for weird bugs that you can't reproduce */
+#ifdef NAD_DEBUG
+
+static xht _nad_alloc_tracked = NULL;
+static xht _nad_free_tracked = NULL;
+
+static void _nad_ptr_check(const char *func, nad_t nad) {
+ char loc[24];
+ snprintf(loc, sizeof(loc), "%x", (int) nad);
+
+ if(xhash_get(_nad_alloc_tracked, loc) == NULL) {
+ fprintf(stderr, ">>> NAD OP %s: 0x%x not allocated!\n", func, (int) nad);
+ abort();
+ }
+
+ if(xhash_get(_nad_free_tracked, loc) != NULL) {
+ fprintf(stderr, ">>> NAD OP %s: 0x%x previously freed!\n", func, (int) nad);
+ abort();
+ }
+
+ fprintf(stderr, ">>> NAD OP %s: 0x%x\n", func, (int) nad);
+}
+#else
+#define _nad_ptr_check(func,nad)
+#endif
+
+#define BLOCKSIZE 1024
+
+/** internal: do and return the math and ensure it gets realloc'd */
+int _nad_realloc(void **oblocks, int len)
+{
+ void *nblocks;
+ int nlen;
+
+ /* round up to standard block sizes */
+ nlen = (((len-1)/BLOCKSIZE)+1)*BLOCKSIZE;
+
+ /* keep trying till we get it */
+ while((nblocks = realloc(*oblocks, nlen)) == NULL) sleep(1);
+ *oblocks = nblocks;
+ return nlen;
+}
+
+/** this is the safety check used to make sure there's always enough mem */
+#define NAD_SAFE(blocks, size, len) if((size) > len) len = _nad_realloc((void**)&(blocks),(size));
+
+/** internal: append some cdata and return the index to it */
+int _nad_cdata(nad_t nad, const char *cdata, int len)
+{
+ NAD_SAFE(nad->cdata, nad->ccur + len, nad->clen);
+
+ memcpy(nad->cdata + nad->ccur, cdata, len);
+ nad->ccur += len;
+ return nad->ccur - len;
+}
+
+/** internal: create a new attr on any given elem */
+int _nad_attr(nad_t nad, int elem, int ns, const char *name, const char *val, int vallen)
+{
+ int attr;
+
+ /* make sure there's mem for us */
+ NAD_SAFE(nad->attrs, (nad->acur + 1) * sizeof(struct nad_attr_st), nad->alen);
+
+ attr = nad->acur;
+ nad->acur++;
+ nad->attrs[attr].next = nad->elems[elem].attr;
+ nad->elems[elem].attr = attr;
+ nad->attrs[attr].lname = strlen(name);
+ nad->attrs[attr].iname = _nad_cdata(nad,name,nad->attrs[attr].lname);
+ if(vallen > 0)
+ nad->attrs[attr].lval = vallen;
+ else
+ nad->attrs[attr].lval = strlen(val);
+ nad->attrs[attr].ival = _nad_cdata(nad,val,nad->attrs[attr].lval);
+ nad->attrs[attr].my_ns = ns;
+
+ return attr;
+}
+
+/** create a new cache, simple pointer to a list of nads */
+nad_cache_t nad_cache_new(void)
+{
+ nad_cache_t cache;
+ while((cache = malloc(sizeof(nad_cache_t))) == NULL) sleep(1);
+ *cache = NULL;
+
+#ifdef NAD_DEBUG
+ if(_nad_alloc_tracked == NULL) _nad_alloc_tracked = xhash_new(501);
+ if(_nad_free_tracked == NULL) _nad_free_tracked = xhash_new(501);
+#endif
+
+ return cache;
+}
+
+
+/** free the cache and any nads in it */
+void nad_cache_free(nad_cache_t cache)
+{
+ nad_t cur;
+ while((cur = *cache) != NULL)
+ {
+ *cache = cur->next;
+ free(cur->elems);
+ free(cur->attrs);
+ free(cur->nss);
+ free(cur->cdata);
+ free(cur->depths);
+ free(cur);
+ }
+ free(cache);
+}
+
+/** get the next nad from the cache, or create some */
+nad_t nad_new(nad_cache_t cache)
+{
+ nad_t nad;
+
+#ifndef NAD_DEBUG
+ /* If cache==NULL, then this NAD is not in a cache */
+
+ if ((cache!=NULL) && (*cache != NULL))
+ {
+ nad = *cache;
+ *cache = nad->next;
+ nad->ccur = nad->ecur = nad->acur = nad->ncur = 0;
+ nad->scope = -1;
+ nad->cache = cache;
+ nad->next = NULL;
+ return nad;
+ }
+#endif
+
+ while((nad = malloc(sizeof(struct nad_st))) == NULL) sleep(1);
+ memset(nad,0,sizeof(struct nad_st));
+
+ nad->scope = -1;
+ nad->cache = cache;
+
+#ifdef NAD_DEBUG
+ {
+ char loc[24];
+ snprintf(loc, sizeof(loc), "%x", (int) nad);
+ xhash_put(_nad_alloc_tracked, pstrdup(xhash_pool(_nad_alloc_tracked), loc), (void *) 1);
+ }
+ _nad_ptr_check(__func__, nad);
+#endif
+
+ return nad;
+}
+
+nad_t nad_copy(nad_t nad)
+{
+ nad_t copy;
+
+ _nad_ptr_check(__func__, nad);
+
+ if(nad == NULL) return NULL;
+
+ /* create a new nad not participating in a cache */
+ copy = nad_new(NULL);
+
+ /* if it's not large enough, make bigger */
+ NAD_SAFE(copy->elems, nad->elen, copy->elen);
+ NAD_SAFE(copy->attrs, nad->alen, copy->alen);
+ NAD_SAFE(copy->nss, nad->nlen, copy->nlen);
+ NAD_SAFE(copy->cdata, nad->clen, copy->clen);
+
+ /* copy all data */
+ memcpy(copy->elems, nad->elems, nad->elen);
+ memcpy(copy->attrs, nad->attrs, nad->alen);
+ memcpy(copy->nss, nad->nss, nad->nlen);
+ memcpy(copy->cdata, nad->cdata, nad->clen);
+
+ /* sync data */
+ copy->ecur = nad->ecur;
+ copy->acur = nad->acur;
+ copy->ncur = nad->ncur;
+ copy->ccur = nad->ccur;
+
+ copy->scope = nad->scope;
+
+ return copy;
+}
+
+/** free nad, or plug nad back in the cache */
+void nad_free(nad_t nad)
+{
+ if(nad == NULL) return;
+
+#ifdef NAD_DEBUG
+ _nad_ptr_check(__func__, nad);
+ {
+ char loc[24];
+ snprintf(loc, sizeof(loc), "%x", (int) nad);
+ xhash_zap(_nad_alloc_tracked, loc);
+ xhash_put(_nad_free_tracked, pstrdup(xhash_pool(_nad_free_tracked), loc), (void *) nad);
+ }
+#else
+ /* If nad->cache != NULL, then put back into cache, otherwise this nad is not in a cache */
+
+ if (nad->cache != NULL) {
+ nad->next = *(nad->cache);
+ *(nad->cache) = nad;
+ return;
+ }
+#endif
+
+ /* Free nad */
+ free(nad->elems);
+ free(nad->attrs);
+ free(nad->cdata);
+ free(nad->nss);
+ free(nad->depths);
+}
+
+/** locate the next elem at a given depth with an optional matching name */
+int nad_find_elem(nad_t nad, int elem, int ns, const char *name, int depth)
+{
+ int my_ns;
+ int lname = 0;
+
+ _nad_ptr_check(__func__, nad);
+
+ /* make sure there are valid args */
+ if(elem >= nad->ecur || name == NULL) return -1;
+
+ /* set up args for searching */
+ depth = nad->elems[elem].depth + depth;
+ if(name != NULL) lname = strlen(name);
+
+ /* search */
+ for(elem++;elem < nad->ecur;elem++)
+ {
+ /* if we hit one with a depth less than ours, then we don't have the
+ * same parent anymore, bail */
+ if(nad->elems[elem].depth < depth)
+ return -1;
+
+ if(nad->elems[elem].depth == depth && (lname <= 0 || (lname == nad->elems[elem].lname && strncmp(name,nad->cdata + nad->elems[elem].iname, lname) == 0)) &&
+ (ns < 0 || ((my_ns = nad->elems[elem].my_ns) >= 0 && NAD_NURI_L(nad, ns) == NAD_NURI_L(nad, my_ns) && strncmp(NAD_NURI(nad, ns), NAD_NURI(nad, my_ns), NAD_NURI_L(nad, ns)) == 0)))
+ return elem;
+ }
+
+ return -1;
+}
+
+/** get a matching attr on this elem, both name and optional val */
+int nad_find_attr(nad_t nad, int elem, int ns, const char *name, const char *val)
+{
+ int attr, my_ns;
+ int lname, lval = 0;
+
+ _nad_ptr_check(__func__, nad);
+
+ /* make sure there are valid args */
+ if(elem >= nad->ecur || name == NULL) return -1;
+
+ attr = nad->elems[elem].attr;
+ lname = strlen(name);
+ if(val != NULL) lval = strlen(val);
+
+ while(attr >= 0)
+ {
+ /* hefty, match name and if a val, also match that */
+ if(lname == nad->attrs[attr].lname && strncmp(name,nad->cdata + nad->attrs[attr].iname, lname) == 0 &&
+ (lval <= 0 || (lval == nad->attrs[attr].lval && strncmp(val,nad->cdata + nad->attrs[attr].ival, lval) == 0)) &&
+ (ns < 0 || ((my_ns = nad->attrs[attr].my_ns) >= 0 && NAD_NURI_L(nad, ns) == NAD_NURI_L(nad, my_ns) && strncmp(NAD_NURI(nad, ns), NAD_NURI(nad, my_ns), NAD_NURI_L(nad, ns)) == 0)))
+ return attr;
+ attr = nad->attrs[attr].next;
+ }
+ return -1;
+}
+
+/** get a matching ns on this elem, both uri and optional prefix */
+int nad_find_namespace(nad_t nad, int elem, const char *uri, const char *prefix)
+{
+ int check, ns;
+
+ _nad_ptr_check(__func__, nad);
+
+ if(uri == NULL)
+ return -1;
+
+ /* work backwards through our parents, looking for our namespace on each one.
+ * if we find it, link it. if not, the namespace is undeclared - for now, just drop it */
+ check = elem;
+ while(check >= 0)
+ {
+ ns = nad->elems[check].ns;
+ while(ns >= 0)
+ {
+ if(strlen(uri) == NAD_NURI_L(nad, ns) && strncmp(uri, NAD_NURI(nad, ns), NAD_NURI_L(nad, ns)) == 0 && (prefix == NULL || (nad->nss[ns].iprefix >= 0 && strlen(prefix) == NAD_NPREFIX_L(nad, ns) && strncmp(prefix, NAD_NPREFIX(nad, ns), NAD_NPREFIX_L(nad, ns)) == 0)))
+ return ns;
+ ns = nad->nss[ns].next;
+ }
+ check = nad->elems[check].parent;
+ }
+
+ return -1;
+}
+
+/** find a namespace in scope */
+int nad_find_scoped_namespace(nad_t nad, const char *uri, const char *prefix)
+{
+ int ns;
+
+ _nad_ptr_check(__func__, nad);
+
+ if(uri == NULL)
+ return -1;
+
+ for(ns = 0; ns < nad->ncur; ns++)
+ {
+ if(strlen(uri) == NAD_NURI_L(nad, ns) && strncmp(uri, NAD_NURI(nad, ns), NAD_NURI_L(nad, ns)) == 0 &&
+ (prefix == NULL ||
+ (nad->nss[ns].iprefix >= 0 &&
+ strlen(prefix) == NAD_NPREFIX_L(nad, ns) && strncmp(prefix, NAD_NPREFIX(nad, ns), NAD_NPREFIX_L(nad, ns)) == 0)))
+ return ns;
+ }
+
+ return -1;
+}
+
+/** create, update, or zap any matching attr on this elem */
+void nad_set_attr(nad_t nad, int elem, int ns, const char *name, const char *val, int vallen)
+{
+ int attr;
+
+ _nad_ptr_check(__func__, nad);
+
+ /* find one to replace first */
+ if((attr = nad_find_attr(nad, elem, ns, name, NULL)) < 0)
+ {
+ /* only create new if there's a value to store */
+ if(val != NULL)
+ _nad_attr(nad, elem, ns, name, val, vallen);
+ return;
+ }
+
+ /* got matching, update value or zap */
+ if(val == NULL)
+ {
+ nad->attrs[attr].lval = nad->attrs[attr].lname = 0;
+ }else{
+ if(vallen > 0)
+ nad->attrs[attr].lval = vallen;
+ else
+ nad->attrs[attr].lval = strlen(val);
+ nad->attrs[attr].ival = _nad_cdata(nad,val,nad->attrs[attr].lval);
+ }
+
+}
+
+/** shove in a new child elem after the given one */
+int nad_insert_elem(nad_t nad, int parent, int ns, const char *name, const char *cdata)
+{
+ int elem = parent + 1;
+
+ _nad_ptr_check(__func__, nad);
+
+ NAD_SAFE(nad->elems, (nad->ecur + 1) * sizeof(struct nad_elem_st), nad->elen);
+
+ /* relocate all the rest of the elems (unless we're at the end already) */
+ if(nad->ecur != elem)
+ memmove(&nad->elems[elem + 1], &nad->elems[elem], (nad->ecur - elem) * sizeof(struct nad_elem_st));
+ nad->ecur++;
+
+ /* set up req'd parts of new elem */
+ nad->elems[elem].parent = parent;
+ nad->elems[elem].lname = strlen(name);
+ nad->elems[elem].iname = _nad_cdata(nad,name,nad->elems[elem].lname);
+ nad->elems[elem].attr = -1;
+ nad->elems[elem].ns = nad->scope; nad->scope = -1;
+ nad->elems[elem].itail = nad->elems[elem].ltail = 0;
+ nad->elems[elem].my_ns = ns;
+
+ /* add cdata if given */
+ if(cdata != NULL)
+ {
+ nad->elems[elem].lcdata = strlen(cdata);
+ nad->elems[elem].icdata = _nad_cdata(nad,cdata,nad->elems[elem].lcdata);
+ }else{
+ nad->elems[elem].icdata = nad->elems[elem].lcdata = 0;
+ }
+
+ /* parent/child */
+ nad->elems[elem].depth = nad->elems[parent].depth + 1;
+
+ return elem;
+}
+
+/** wrap an element with another element */
+void nad_wrap_elem(nad_t nad, int elem, int ns, const char *name)
+{
+ int cur;
+
+ _nad_ptr_check(__func__, nad);
+
+ if(elem >= nad->ecur) return;
+
+ NAD_SAFE(nad->elems, (nad->ecur + 1) * sizeof(struct nad_elem_st), nad->elen);
+
+ /* relocate all the rest of the elems after us */
+ memmove(&nad->elems[elem + 1], &nad->elems[elem], (nad->ecur - elem) * sizeof(struct nad_elem_st));
+ nad->ecur++;
+
+ /* set up req'd parts of new elem */
+ nad->elems[elem].lname = strlen(name);
+ nad->elems[elem].iname = _nad_cdata(nad,name,nad->elems[elem].lname);
+ nad->elems[elem].attr = -1;
+ nad->elems[elem].ns = nad->scope; nad->scope = -1;
+ nad->elems[elem].itail = nad->elems[elem].ltail = 0;
+ nad->elems[elem].icdata = nad->elems[elem].lcdata = 0;
+ nad->elems[elem].my_ns = ns;
+
+ /* raise the bar on all the children */
+ nad->elems[elem+1].depth++;
+ for(cur = elem + 2; cur < nad->ecur && nad->elems[cur].depth > nad->elems[elem].depth; cur++) nad->elems[cur].depth++;
+
+ /* relink the parents */
+ nad->elems[elem].parent = nad->elems[elem + 1].parent;
+ nad->elems[elem + 1].parent = elem;
+}
+
+/** create a new elem on the list */
+int nad_append_elem(nad_t nad, int ns, const char *name, int depth)
+{
+ int elem;
+
+ _nad_ptr_check(__func__, nad);
+
+ /* make sure there's mem for us */
+ NAD_SAFE(nad->elems, (nad->ecur + 1) * sizeof(struct nad_elem_st), nad->elen);
+
+ elem = nad->ecur;
+ nad->ecur++;
+ nad->elems[elem].lname = strlen(name);
+ nad->elems[elem].iname = _nad_cdata(nad,name,nad->elems[elem].lname);
+ nad->elems[elem].icdata = nad->elems[elem].lcdata = 0;
+ nad->elems[elem].itail = nad->elems[elem].ltail = 0;
+ nad->elems[elem].attr = -1;
+ nad->elems[elem].ns = nad->scope; nad->scope = -1;
+ nad->elems[elem].depth = depth;
+ nad->elems[elem].my_ns = ns;
+
+ /* make sure there's mem in the depth array, then track us */
+ NAD_SAFE(nad->depths, (depth + 1) * sizeof(int), nad->dlen);
+ nad->depths[depth] = elem;
+
+ /* our parent is the previous guy in the depth array */
+ if(depth <= 0)
+ nad->elems[elem].parent = -1;
+ else
+ nad->elems[elem].parent = nad->depths[depth - 1];
+
+ return elem;
+}
+
+/** attach new attr to the last elem */
+int nad_append_attr(nad_t nad, int ns, const char *name, const char *val)
+{
+ _nad_ptr_check(__func__, nad);
+
+ return _nad_attr(nad, nad->ecur - 1, ns, name, val, 0);
+}
+
+/** append new cdata to the last elem */
+void nad_append_cdata(nad_t nad, const char *cdata, int len, int depth)
+{
+ int elem = nad->ecur - 1;
+
+ _nad_ptr_check(__func__, nad);
+
+ /* make sure this cdata is the child of the last elem to append */
+ if(nad->elems[elem].depth == depth - 1)
+ {
+ if(nad->elems[elem].icdata == 0)
+ nad->elems[elem].icdata = nad->ccur;
+ _nad_cdata(nad,cdata,len);
+ nad->elems[elem].lcdata += len;
+ return;
+ }
+
+ /* otherwise, pin the cdata on the tail of the last element at this depth */
+ elem = nad->depths[depth];
+ if(nad->elems[elem].itail == 0)
+ nad->elems[elem].itail = nad->ccur;
+ _nad_cdata(nad,cdata,len);
+ nad->elems[elem].ltail += len;
+}
+
+/** bring a new namespace into scope */
+int nad_add_namespace(nad_t nad, const char *uri, const char *prefix)
+{
+ int ns;
+
+ _nad_ptr_check(__func__, nad);
+
+ /* only add it if its not already in scope */
+ ns = nad_find_scoped_namespace(nad, uri, NULL);
+ if(ns >= 0)
+ return ns;
+
+ /* make sure there's mem for us */
+ NAD_SAFE(nad->nss, (nad->ncur + 1) * sizeof(struct nad_ns_st), nad->nlen);
+
+ ns = nad->ncur;
+ nad->ncur++;
+ nad->nss[ns].next = nad->scope;
+ nad->scope = ns;
+
+ nad->nss[ns].luri = strlen(uri);
+ nad->nss[ns].iuri = _nad_cdata(nad, uri, nad->nss[ns].luri);
+ if(prefix != NULL)
+ {
+ nad->nss[ns].lprefix = strlen(prefix);
+ nad->nss[ns].iprefix = _nad_cdata(nad, prefix, nad->nss[ns].lprefix);
+ }
+ else
+ nad->nss[ns].iprefix = -1;
+
+ return ns;
+}
+
+/** declare a namespace on an already-existing element */
+int nad_append_namespace(nad_t nad, int elem, const char *uri, const char *prefix) {
+ int ns;
+
+ _nad_ptr_check(__func__, nad);
+
+ /* see if its already scoped on this element */
+ ns = nad_find_namespace(nad, elem, uri, NULL);
+ if(ns >= 0)
+ return ns;
+
+ /* make some room */
+ NAD_SAFE(nad->nss, (nad->ncur + 1) * sizeof(struct nad_ns_st), nad->nlen);
+
+ ns = nad->ncur;
+ nad->ncur++;
+ nad->nss[ns].next = nad->elems[elem].ns;
+ nad->elems[elem].ns = ns;
+
+ nad->nss[ns].luri = strlen(uri);
+ nad->nss[ns].iuri = _nad_cdata(nad, uri, nad->nss[ns].luri);
+ if(prefix != NULL)
+ {
+ nad->nss[ns].lprefix = strlen(prefix);
+ nad->nss[ns].iprefix = _nad_cdata(nad, prefix, nad->nss[ns].lprefix);
+ }
+ else
+ nad->nss[ns].iprefix = -1;
+
+ return ns;
+}
+
+void _nad_escape(nad_t nad, int data, int len, int flag)
+{
+ char *c;
+ int ic;
+
+ if(len <= 0) return;
+
+ /* first, if told, find and escape ' */
+ while(flag >= 3 && (c = memchr(nad->cdata + data,'\'',len)) != NULL)
+ {
+ /* get offset */
+ ic = c - nad->cdata;
+
+ /* cute, eh? handle other data before this normally */
+ _nad_escape(nad, data, ic - data, 2);
+
+ /* ensure enough space, and add our escaped ' */
+ NAD_SAFE(nad->cdata, nad->ccur + 6, nad->clen);
+ memcpy(nad->cdata + nad->ccur, "'", 6);
+ nad->ccur += 6;
+
+ /* just update and loop for more */
+ len -= (ic+1) - data;
+ data = ic+1;
+ }
+
+ /* next look for < */
+ while(flag >= 2 && (c = memchr(nad->cdata + data,'<',len)) != NULL)
+ {
+ ic = c - nad->cdata;
+ _nad_escape(nad, data, ic - data, 1);
+
+ /* ensure enough space, and add our escaped < */
+ NAD_SAFE(nad->cdata, nad->ccur + 4, nad->clen);
+ memcpy(nad->cdata + nad->ccur, "<", 4);
+ nad->ccur += 4;
+
+ /* just update and loop for more */
+ len -= (ic+1) - data;
+ data = ic+1;
+ }
+
+ /* check for ]]>, we need to escape the > */
+ /* WE DID THIS (add the '0' as the first test to the while loop
+ because the loops dies 3 lines in... (and we don't reall
+ need this)) ...
+ */
+ while( 0 && flag >= 1 && (c = memchr(nad->cdata + data, '>', len)) != NULL)
+ {
+ ic = c - nad->cdata;
+
+ _nad_escape(nad, data, ic - data, 0);
+
+ /* check for the sequence */
+
+ if( c >= nad->cdata + 2 && c[-1] == ']' && c[-2] == ']')
+ {
+ /* ensure enough space, and add our escaped > */
+ NAD_SAFE(nad->cdata, nad->ccur + 4, nad->clen);
+ memcpy(nad->cdata + nad->ccur, ">", 4);
+ nad->ccur += 4;
+ }
+
+ /* otherwise, just plug the > in as-is */
+ else
+ {
+ NAD_SAFE(nad->cdata, nad->ccur + 1, nad->clen);
+ *(nad->cdata + nad->ccur) = '>';
+ nad->ccur++;
+ }
+
+ /* just update and loop for more */
+ len -= (ic+1) - data;
+ data = ic+1;
+ }
+
+ /* if & is found, escape it */
+ while((c = memchr(nad->cdata + data,'&',len)) != NULL)
+ {
+ ic = c - nad->cdata;
+
+ /* ensure enough space */
+ NAD_SAFE(nad->cdata, nad->ccur + 5 + (ic - data), nad->clen);
+
+ /* handle normal data */
+ memcpy(nad->cdata + nad->ccur, nad->cdata + data, (ic - data));
+ nad->ccur += (ic - data);
+
+ /* append escaped < */
+ memcpy(nad->cdata + nad->ccur, "&", 5);
+ nad->ccur += 5;
+
+ /* just update and loop for more */
+ len -= (ic+1) - data;
+ data = ic+1;
+ }
+
+ /* nothing exciting, just append normal cdata */
+ NAD_SAFE(nad->cdata, nad->ccur + len, nad->clen);
+ memcpy(nad->cdata + nad->ccur, nad->cdata + data, len);
+ nad->ccur += len;
+}
+
+/** internal recursive printing function */
+int _nad_lp0(nad_t nad, int elem)
+{
+ int attr;
+ int ndepth;
+ int ns;
+
+ /* there's a lot of code in here, but don't let that scare you, it's just duplication in order to be a bit more efficient cpu-wise */
+
+ /* this whole thing is in a big loop for processing siblings */
+ while(elem != nad->ecur)
+ {
+
+ /* make enough space for the opening element */
+ ns = nad->elems[elem].my_ns;
+ if(ns >= 0 && nad->nss[ns].iprefix >= 0)
+ {
+ NAD_SAFE(nad->cdata, nad->ccur + nad->elems[elem].lname + nad->nss[ns].lprefix + 2, nad->clen);
+ } else {
+ NAD_SAFE(nad->cdata, nad->ccur + nad->elems[elem].lname + 1, nad->clen);
+ }
+
+ /* opening tag */
+ *(nad->cdata + nad->ccur++) = '<';
+
+ /* add the prefix if necessary */
+ if(ns >= 0 && nad->nss[ns].iprefix >= 0)
+ {
+ memcpy(nad->cdata + nad->ccur, nad->cdata + nad->nss[ns].iprefix, nad->nss[ns].lprefix);
+ nad->ccur += nad->nss[ns].lprefix;
+ *(nad->cdata + nad->ccur++) = ':';
+ }
+
+ /* copy in the name */
+ memcpy(nad->cdata + nad->ccur, nad->cdata + nad->elems[elem].iname, nad->elems[elem].lname);
+ nad->ccur += nad->elems[elem].lname;
+
+ /* add the namespaces */
+ for(ns = nad->elems[elem].ns; ns >= 0; ns = nad->nss[ns].next)
+ {
+ /* never explicitly declare the implicit xml namespace */
+ if(nad->nss[ns].luri == strlen(uri_XML) && strncmp(uri_XML, nad->cdata + nad->nss[ns].iuri, nad->nss[ns].luri) == 0)
+ continue;
+
+ /* make space */
+ if(nad->nss[ns].iprefix >= 0)
+ {
+ NAD_SAFE(nad->cdata, nad->ccur + nad->nss[ns].luri + nad->nss[ns].lprefix + 10, nad->clen);
+ } else {
+ NAD_SAFE(nad->cdata, nad->ccur + nad->nss[ns].luri + 9, nad->clen);
+ }
+
+ /* start */
+ memcpy(nad->cdata + nad->ccur, " xmlns", 6);
+ nad->ccur += 6;
+
+ /* prefix if necessary */
+ if(nad->nss[ns].iprefix >= 0)
+ {
+ *(nad->cdata + nad->ccur++) = ':';
+ memcpy(nad->cdata + nad->ccur, nad->cdata + nad->nss[ns].iprefix, nad->nss[ns].lprefix);
+ nad->ccur += nad->nss[ns].lprefix;
+ }
+
+ *(nad->cdata + nad->ccur++) = '=';
+ *(nad->cdata + nad->ccur++) = '\'';
+
+ /* uri */
+ memcpy(nad->cdata + nad->ccur, nad->cdata + nad->nss[ns].iuri, nad->nss[ns].luri);
+ nad->ccur += nad->nss[ns].luri;
+
+ *(nad->cdata + nad->ccur++) = '\'';
+ }
+
+ for(attr = nad->elems[elem].attr; attr >= 0; attr = nad->attrs[attr].next)
+ {
+ if(nad->attrs[attr].lname <= 0) continue;
+
+ /* make enough space for the wrapper part */
+ ns = nad->attrs[attr].my_ns;
+ if(ns >= 0 && nad->nss[ns].iprefix >= 0)
+ {
+ NAD_SAFE(nad->cdata, nad->ccur + nad->attrs[attr].lname + nad->nss[ns].lprefix + 4, nad->clen);
+ } else {
+ NAD_SAFE(nad->cdata, nad->ccur + nad->attrs[attr].lname + 3, nad->clen);
+ }
+
+ *(nad->cdata + nad->ccur++) = ' ';
+
+ /* add the prefix if necessary */
+ if(ns >= 0 && nad->nss[ns].iprefix >= 0)
+ {
+ memcpy(nad->cdata + nad->ccur, nad->cdata + nad->nss[ns].iprefix, nad->nss[ns].lprefix);
+ nad->ccur += nad->nss[ns].lprefix;
+ *(nad->cdata + nad->ccur++) = ':';
+ }
+
+ /* copy in the name parts */
+ memcpy(nad->cdata + nad->ccur, nad->cdata + nad->attrs[attr].iname, nad->attrs[attr].lname);
+ nad->ccur += nad->attrs[attr].lname;
+ *(nad->cdata + nad->ccur++) = '=';
+ *(nad->cdata + nad->ccur++) = '\'';
+
+ /* copy in the escaped value */
+ _nad_escape(nad, nad->attrs[attr].ival, nad->attrs[attr].lval, 3);
+
+ /* make enough space for the closing quote and add it */
+ NAD_SAFE(nad->cdata, nad->ccur + 1, nad->clen);
+ *(nad->cdata + nad->ccur++) = '\'';
+ }
+
+ /* figure out what's next */
+ if(elem+1 == nad->ecur)
+ ndepth = -1;
+ else
+ ndepth = nad->elems[elem+1].depth;
+
+ /* handle based on if there are children, update nelem after done */
+ if(ndepth <= nad->elems[elem].depth)
+ {
+ /* make sure there's enough for what we could need */
+ NAD_SAFE(nad->cdata, nad->ccur + 2, nad->clen);
+ if(nad->elems[elem].lcdata == 0)
+ {
+ memcpy(nad->cdata + nad->ccur, "/>", 2);
+ nad->ccur += 2;
+ }else{
+ *(nad->cdata + nad->ccur++) = '>';
+
+ /* copy in escaped cdata */
+ _nad_escape(nad, nad->elems[elem].icdata, nad->elems[elem].lcdata,2);
+
+ /* make room */
+ ns = nad->elems[elem].my_ns;
+ if(ns >= 0 && nad->nss[ns].iprefix >= 0)
+ {
+ NAD_SAFE(nad->cdata, nad->ccur + 4 + nad->elems[elem].lname + nad->nss[ns].lprefix, nad->clen);
+ } else {
+ NAD_SAFE(nad->cdata, nad->ccur + 3 + nad->elems[elem].lname, nad->clen);
+ }
+
+ /* close tag */
+ memcpy(nad->cdata + nad->ccur, "</", 2);
+ nad->ccur += 2;
+
+ /* add the prefix if necessary */
+ if(ns >= 0 && nad->nss[ns].iprefix >= 0)
+ {
+ memcpy(nad->cdata + nad->ccur, nad->cdata + nad->nss[ns].iprefix, nad->nss[ns].lprefix);
+ nad->ccur += nad->nss[ns].lprefix;
+ *(nad->cdata + nad->ccur++) = ':';
+ }
+
+ memcpy(nad->cdata + nad->ccur, nad->cdata + nad->elems[elem].iname, nad->elems[elem].lname);
+ nad->ccur += nad->elems[elem].lname;
+ *(nad->cdata + nad->ccur++) = '>';
+ }
+
+ /* always try to append the tail */
+ _nad_escape(nad, nad->elems[elem].itail, nad->elems[elem].ltail,2);
+
+ /* if no siblings either, bail */
+ if(ndepth < nad->elems[elem].depth)
+ return elem+1;
+
+ /* next sibling */
+ elem++;
+ }else{
+ int nelem;
+ /* process any children */
+
+ /* close ourself and append any cdata first */
+ NAD_SAFE(nad->cdata, nad->ccur + 1, nad->clen);
+ *(nad->cdata + nad->ccur++) = '>';
+ _nad_escape(nad, nad->elems[elem].icdata, nad->elems[elem].lcdata,2);
+
+ /* process children */
+ nelem = _nad_lp0(nad,elem+1);
+
+ /* close and tail up */
+ ns = nad->elems[elem].my_ns;
+ if(ns >= 0 && nad->nss[ns].iprefix >= 0)
+ {
+ NAD_SAFE(nad->cdata, nad->ccur + 4 + nad->elems[elem].lname + nad->nss[ns].lprefix, nad->clen);
+ } else {
+ NAD_SAFE(nad->cdata, nad->ccur + 3 + nad->elems[elem].lname, nad->clen);
+ }
+ memcpy(nad->cdata + nad->ccur, "</", 2);
+ nad->ccur += 2;
+ if(ns >= 0 && nad->nss[ns].iprefix >= 0)
+ {
+ memcpy(nad->cdata + nad->ccur, nad->cdata + nad->nss[ns].iprefix, nad->nss[ns].lprefix);
+ nad->ccur += nad->nss[ns].lprefix;
+ *(nad->cdata + nad->ccur++) = ':';
+ }
+ memcpy(nad->cdata + nad->ccur, nad->cdata + nad->elems[elem].iname, nad->elems[elem].lname);
+ nad->ccur += nad->elems[elem].lname;
+ *(nad->cdata + nad->ccur++) = '>';
+ _nad_escape(nad, nad->elems[elem].itail, nad->elems[elem].ltail,2);
+
+ /* if the next element is not our sibling, we're done */
+ if(nelem < nad->ecur && nad->elems[nelem].depth < nad->elems[elem].depth)
+ return nelem;
+
+ /* for next sibling in while loop */
+ elem = nelem;
+ }
+
+ /* here's the end of that big while loop */
+ }
+
+ return elem;
+}
+
+void nad_print(nad_t nad, int elem, char **xml, int *len)
+{
+ int ixml = nad->ccur;
+
+ _nad_ptr_check(__func__, nad);
+
+ _nad_lp0(nad,elem);
+ *len = nad->ccur - ixml;
+ *xml = nad->cdata + ixml;
+}
+
+/**
+ * nads serialize to a buffer of this form:
+ *
+ * [buflen][ecur][acur][ncur][ccur][elems][attrs][nss][cdata]
+ *
+ * nothing is done with endianness or word length, so the nad must be
+ * serialized and deserialized on the same platform
+ *
+ * buflen is not actually used by deserialize(), but is provided as a
+ * convenience to the application so it knows how many bytes to read before
+ * passing them in to deserialize()
+ *
+ * the depths array is not stored, so after deserialization
+ * nad_append_elem() and nad_append_cdata() will not work. this is rarely
+ * a problem
+ */
+
+void nad_serialize(nad_t nad, char **buf, int *len) {
+ char *pos;
+
+ _nad_ptr_check(__func__, nad);
+
+ *len = sizeof(int) * 5 + /* 4 ints in nad_t, plus one for len */
+ sizeof(struct nad_elem_st) * nad->ecur +
+ sizeof(struct nad_attr_st) * nad->acur +
+ sizeof(struct nad_ns_st) * nad->ncur +
+ sizeof(char) * nad->ccur;
+
+ *buf = (char *) malloc(*len);
+ pos = *buf;
+
+ * (int *) pos = *len; pos += sizeof(int);
+ * (int *) pos = nad->ecur; pos += sizeof(int);
+ * (int *) pos = nad->acur; pos += sizeof(int);
+ * (int *) pos = nad->ncur; pos += sizeof(int);
+ * (int *) pos = nad->ccur; pos += sizeof(int);
+
+ memcpy(pos, nad->elems, sizeof(struct nad_elem_st) * nad->ecur); pos += sizeof(struct nad_elem_st) * nad->ecur;
+ memcpy(pos, nad->attrs, sizeof(struct nad_attr_st) * nad->acur); pos += sizeof(struct nad_attr_st) * nad->acur;
+ memcpy(pos, nad->nss, sizeof(struct nad_ns_st) * nad->ncur); pos += sizeof(struct nad_ns_st) * nad->ncur;
+ memcpy(pos, nad->cdata, sizeof(char) * nad->ccur);
+}
+
+nad_t nad_deserialize(nad_cache_t cache, const char *buf) {
+ nad_t nad = nad_new(cache);
+ const char *pos = buf + sizeof(int); /* skip len */
+
+ _nad_ptr_check(__func__, nad);
+
+ nad->ecur = * (int *) pos; pos += sizeof(int);
+ nad->acur = * (int *) pos; pos += sizeof(int);
+ nad->ncur = * (int *) pos; pos += sizeof(int);
+ nad->ccur = * (int *) pos; pos += sizeof(int);
+ nad->elen = nad->ecur;
+ nad->alen = nad->acur;
+ nad->nlen = nad->ncur;
+ nad->clen = nad->ccur;
+
+ if(nad->ecur > 0)
+ {
+ nad->elems = (struct nad_elem_st *) malloc(sizeof(struct nad_elem_st) * nad->ecur);
+ memcpy(nad->elems, pos, sizeof(struct nad_elem_st) * nad->ecur);
+ pos += sizeof(struct nad_elem_st) * nad->ecur;
+ }
+
+ if(nad->acur > 0)
+ {
+ nad->attrs = (struct nad_attr_st *) malloc(sizeof(struct nad_attr_st) * nad->acur);
+ memcpy(nad->attrs, pos, sizeof(struct nad_attr_st) * nad->acur);
+ pos += sizeof(struct nad_attr_st) * nad->acur;
+ }
+
+ if(nad->ncur > 0)
+ {
+ nad->nss = (struct nad_ns_st *) malloc(sizeof(struct nad_ns_st) * nad->ncur);
+ memcpy(nad->nss, pos, sizeof(struct nad_ns_st) * nad->ncur);
+ pos += sizeof(struct nad_ns_st) * nad->ncur;
+ }
+
+ if(nad->ccur > 0)
+ {
+ nad->cdata = (char *) malloc(sizeof(char) * nad->ccur);
+ memcpy(nad->cdata, pos, sizeof(char) * nad->ccur);
+ }
+
+ return nad;
+}
+
+#ifdef HAVE_EXPAT
+
+/** parse a buffer into a nad */
+
+struct build_data {
+ nad_t nad;
+ int depth;
+};
+
+static void _nad_parse_element_start(void *arg, const char *name, const char **atts) {
+ struct build_data *bd = (struct build_data *) arg;
+ char buf[1024];
+ char *uri, *elem, *prefix;
+ const char **attr;
+ int ns;
+
+ /* make a copy */
+ strncpy(buf, name, 1024);
+ buf[1023] = '\0';
+
+ /* expat gives us:
+ prefixed namespaced elem: uri|elem|prefix
+ default namespaced elem: uri|elem
+ un-namespaced elem: elem
+ */
+
+ /* extract all the bits */
+ uri = buf;
+ elem = strchr(uri, '|');
+ if(elem != NULL) {
+ *elem = '\0';
+ elem++;
+ prefix = strchr(elem, '|');
+ if(prefix != NULL) {
+ *prefix = '\0';
+ prefix++;
+ }
+ ns = nad_add_namespace(bd->nad, uri, prefix);
+ } else {
+ /* un-namespaced, just take it as-is */
+ uri = NULL;
+ elem = buf;
+ prefix = NULL;
+ ns = -1;
+ }
+
+ /* add it */
+ nad_append_elem(bd->nad, ns, elem, bd->depth);
+
+ /* now the attributes, one at a time */
+ attr = atts;
+ while(attr[0] != NULL) {
+
+ /* make a copy */
+ strncpy(buf, attr[0], 1024);
+ buf[1023] = '\0';
+
+ /* extract all the bits */
+ uri = buf;
+ elem = strchr(uri, '|');
+ if(elem != NULL) {
+ *elem = '\0';
+ elem++;
+ prefix = strchr(elem, '|');
+ if(prefix != NULL) {
+ *prefix = '\0';
+ prefix++;
+ }
+ ns = nad_add_namespace(bd->nad, uri, prefix);
+ } else {
+ /* un-namespaced, just take it as-is */
+ uri = NULL;
+ elem = buf;
+ prefix = NULL;
+ ns = -1;
+ }
+
+ /* add it */
+ nad_append_attr(bd->nad, ns, elem, (char *) attr[1]);
+
+ attr += 2;
+ }
+
+ bd->depth++;
+}
+
+static void _nad_parse_element_end(void *arg, const char *name) {
+ struct build_data *bd = (struct build_data *) arg;
+
+ bd->depth--;
+}
+
+static void _nad_parse_cdata(void *arg, const char *str, int len) {
+ struct build_data *bd = (struct build_data *) arg;
+
+ /* go */
+ nad_append_cdata(bd->nad, (char *) str, len, bd->depth);
+}
+
+static void _nad_parse_namespace_start(void *arg, const char *prefix, const char *uri) {
+ struct build_data *bd = (struct build_data *) arg;
+
+ nad_add_namespace(bd->nad, (char *) uri, (char *) prefix);
+}
+
+nad_t nad_parse(nad_cache_t cache, const char *buf, int len) {
+ struct build_data bd;
+ XML_Parser p;
+
+ if(len == 0)
+ len = strlen(buf);
+
+ p = XML_ParserCreateNS(NULL, '|');
+ if(p == NULL)
+ return NULL;
+
+ bd.nad = nad_new(cache);
+ bd.depth = 0;
+
+ XML_SetUserData(p, (void *) &bd);
+ XML_SetElementHandler(p, _nad_parse_element_start, _nad_parse_element_end);
+ XML_SetCharacterDataHandler(p, _nad_parse_cdata);
+ XML_SetStartNamespaceDeclHandler(p, _nad_parse_namespace_start);
+
+ if(!XML_Parse(p, buf, len, 1)) {
+ XML_ParserFree(p);
+ nad_free(bd.nad);
+ return NULL;
+ }
+
+ XML_ParserFree(p);
+
+ if(bd.depth != 0)
+ return NULL;
+
+ return bd.nad;
+}
+
+#endif
--- /dev/null
+package JSON::number;
+sub new {
+ my $class = shift;
+ my $x = shift || $class;
+ return bless \$x => __PACKAGE__;
+}
+use overload ( '""' => \&toString );
+use overload ( '0+' => sub { ${$_[0]} } );
+
+sub toString { defined($_[1]) ? ${$_[1]} : ${$_[0]} }
+
+package JSON::bool::true;
+sub new { return bless {} => __PACKAGE__ }
+use overload ( '""' => \&toString );
+use overload ( 'bool' => sub { 1 } );
+use overload ( '0+' => sub { 1 } );
+
+sub toString { 'true' }
+
+package JSON::bool::false;
+sub new { return bless {} => __PACKAGE__ }
+use overload ( '""' => \&toString );
+use overload ( 'bool' => sub { 0 } );
+use overload ( '0+' => sub { 0 } );
+
+sub toString { 'false' }
+
+package JSON;
+use vars qw/%_class_map/;
+
+sub register_class_hint {
+ my $class = shift;
+ my %args = @_;
+
+ $_class_map{$args{hint}} = \%args;
+ $_class_map{$args{name}} = \%args;
+}
+
+sub JSON2perl {
+ my ($class, $json) = @_;
+ $json ||= $class;
+
+ $json =~ s/\/\/.+$//gmo; # remove C++ comments
+ $json =~ s/(?<!\\)\$/\\\$/gmo; # fixup $ for later
+ $json =~ s/(?<!\\)\@/\\\@/gmo; # fixup @ for later
+
+ my @casts;
+ my $casting_depth = 0;
+ my $current_cast;
+ my $output = '';
+ while ($json =~ s/^\s* (
+ { | # start object
+ \[ | # start array
+ -?\d+\.?\d* | # number literal
+ "(?:(?:\\[\"])|[^\"])+" | # string literal
+ (?:\/\*.+?\*\/) | # C comment
+ true | # bool true
+ false | # bool false
+ null | # undef()
+ : | # object key-value sep
+ , | # list sep
+ \] | # array end
+ } # object end
+ )
+ \s*//sox) {
+ my $element = $1;
+
+ if ($element eq 'null') {
+ $output .= ' undef() ';
+ next;
+ } elsif ($element =~ /^\/\*--\s*S\w*?\s+(\w+)\s*--\*\/$/) {
+ my $hint = $1;
+ if (exists $_class_map{$hint}) {
+ $casts[$casting_depth] = $hint;
+ $output .= ' bless(';
+ }
+ next;
+ } elsif ($element =~ /^\/\*/) {
+ next;
+ } elsif ($element =~ /^\d/) {
+ $output .= "do { JSON::number::new($element) }";
+ next;
+ } elsif ($element eq '{' or $element eq '[') {
+ $casting_depth++;
+ } elsif ($element eq '}' or $element eq ']') {
+ $casting_depth--;
+ my $hint = $casts[$casting_depth];
+ $casts[$casting_depth] = undef;
+ if (defined $hint and exists $_class_map{$hint}) {
+ $output .= $element . ',"'. $_class_map{$hint}{name} . '")';
+ next;
+ }
+ } elsif ($element eq ':') {
+ $output .= ' => ';
+ next;
+ } elsif ($element eq 'true') {
+ $output .= 'bless( {}, "JSON::bool::true")';
+ next;
+ } elsif ($element eq 'false') {
+ $output .= 'bless( {}, "JSON::bool::false")';
+ next;
+ }
+
+ $output .= $element;
+ }
+
+ return eval $output;
+}
+
+sub perl2JSON {
+ my ($class, $perl) = @_;
+ $perl ||= $class;
+
+ my $output = '';
+ if (!defined($perl)) {
+ $output = 'null';
+ } elsif (ref($perl) and ref($perl) =~ /^JSON/) {
+ $output .= $perl;
+ } elsif ( ref($perl) && exists($_class_map{ref($perl)}) ) {
+ $output .= '/*--S '.$_class_map{ref($perl)}{hint}.'--*/';
+ if (lc($_class_map{ref($perl)}{type}) eq 'hash') {
+ my %hash = %$perl;
+ $output .= perl2JSON(\%hash);
+ } elsif (lc($_class_map{ref($perl)}{type}) eq 'array') {
+ my @array = @$perl;
+ $output .= perl2JSON(\@array);
+ }
+ $output .= '/*--E '.$_class_map{ref($perl)}{hint}.'--*/';
+ } elsif (ref($perl) and ref($perl) =~ /HASH/) {
+ $output .= '{';
+ my $c = 0;
+ for my $key (sort keys %$perl) {
+ $output .= ',' if ($c);
+
+ $output .= perl2JSON($key).':'.perl2JSON($$perl{$key});
+ $c++;
+ }
+ $output .= '}';
+ } elsif (ref($perl) and ref($perl) =~ /ARRAY/) {
+ $output .= '[';
+ my $c = 0;
+ for my $part (@$perl) {
+ $output .= ',' if ($c);
+
+ $output .= perl2JSON($part);
+ $c++;
+ }
+ $output .= ']';
+ } else {
+ $perl =~ s/\\/\\\\/sgo;
+ $perl =~ s/"/\\"/sgo;
+ $perl =~ s/\t/\\t/sgo;
+ $perl =~ s/\f/\\f/sgo;
+ $perl =~ s/\r/\\r/sgo;
+ $perl =~ s/\n/\\n/sgo;
+ $output = '"'.$perl.'"';
+ }
+
+ return $output;
+}
+
+my $depth = 0;
+sub perl2prettyJSON {
+ my ($class, $perl, $nospace) = @_;
+ $perl ||= $class;
+
+ my $output = '';
+ if (!defined($perl)) {
+ $output = 'null';
+ } elsif (ref($perl) and ref($perl) =~ /^JSON/) {
+ $output .= $perl;
+ } elsif ( ref($perl) && exists($_class_map{ref($perl)}) ) {
+ $depth++;
+ $output .= "\n";
+ $output .= " "x$depth;
+ $output .= '/*--S '.$_class_map{ref($perl)}{hint}."--*/ ";
+ if (lc($_class_map{ref($perl)}{type}) eq 'hash') {
+ my %hash = %$perl;
+ $output .= perl2prettyJSON(\%hash,undef,1);
+ } elsif (lc($_class_map{ref($perl)}{type}) eq 'array') {
+ my @array = @$perl;
+ $output .= perl2prettyJSON(\@array,undef,1);
+ }
+ #$output .= " "x$depth;
+ $output .= ' /*--E '.$_class_map{ref($perl)}{hint}.'--*/';
+ $depth--;
+ } elsif (ref($perl) and ref($perl) =~ /HASH/) {
+ #$depth++;
+ $output .= " "x$depth unless ($nospace);
+ $output .= "{\n";
+ my $c = 0;
+ $depth++;
+ for my $key (sort keys %$perl) {
+ $output .= ",\n" if ($c);
+
+ $output .= perl2prettyJSON($key)." : ".perl2prettyJSON($$perl{$key}, undef, 1);
+ $c++;
+ }
+ $depth--;
+ $output .= "\n";
+ $output .= " "x$depth;
+ $output .= '}';
+ #$depth--;
+ } elsif (ref($perl) and ref($perl) =~ /ARRAY/) {
+ #$depth++;
+ $output .= " "x$depth unless ($nospace);
+ $output .= "[\n";
+ my $c = 0;
+ $depth++;
+ for my $part (@$perl) {
+ $output .= ",\n" if ($c);
+
+ $output .= perl2prettyJSON($part);
+ $c++;
+ }
+ $depth--;
+ $output .= "\n";
+ $output .= " "x$depth;
+ $output .= "]";
+ #$depth--;
+ } else {
+ $perl =~ s/\\/\\\\/sgo;
+ $perl =~ s/"/\\"/sgo;
+ $perl =~ s/\t/\\t/sgo;
+ $perl =~ s/\f/\\f/sgo;
+ $perl =~ s/\r/\\r/sgo;
+ $perl =~ s/\n/\\n/sgo;
+ $output .= " "x$depth unless($nospace);
+ $output .= '"'.$perl.'"';
+ }
+
+ return $output;
+}
+
+1;
--- /dev/null
+package OpenILS;
+use strict;
+use Error;
+use vars qw/$VERSION $AUTOLOAD/;
+$VERSION = do { my @r=(q$Revision$=~/\d+/g); sprintf "%d."."%02d"x$#r,@r };
+
+=head1 OpenILS
+
+=cut
+
+=head2 Overview
+
+ Top level class for OpenILS perl modules.
+
+=cut
+
+# Exception base classes
+#use Exception::Class
+# ( OpenILSException => { fields => [ 'errno' ] });
+#push @Exception::Class::ISA, 'Error';
+
+=head3 AUTOLOAD()
+
+ Traps methods calls for methods that have not been defined so they
+ don't propogate up the class hierarchy.
+
+=cut
+sub AUTOLOAD {
+ my $self = shift;
+ my $type = ref($self) || $self;
+ my $name = $AUTOLOAD;
+ my $otype = ref $self;
+
+ my ($package, $filename, $line) = caller;
+ my ($package1, $filename1, $line1) = caller(1);
+ my ($package2, $filename2, $line2) = caller(2);
+ my ($package3, $filename3, $line3) = caller(3);
+ my ($package4, $filename4, $line4) = caller(4);
+ my ($package5, $filename5, $line5) = caller(5);
+ $name =~ s/.*://; # strip fully-qualified portion
+ warn <<" WARN";
+****
+** ${name}() isn't there. Please create me somewhere (like in $type)!
+** Error at $package ($filename), line $line
+** Call Stack (5 deep):
+** $package1 ($filename1), line $line1
+** $package2 ($filename2), line $line2
+** $package3 ($filename3), line $line3
+** $package4 ($filename4), line $line4
+** $package5 ($filename5), line $line5
+** Object type was $otype
+****
+ WARN
+}
+
+
+
+=head3 alert_abstract()
+
+ This method is called by abstract methods to ensure that
+ the process dies when an undefined abstract method is called
+
+=cut
+sub alert_abstract() {
+ my $c = shift;
+ my $class = ref( $c ) || $c;
+ my ($file, $line, $method) = (caller(1))[1..3];
+ die " * Call to abstract method $method at $file, line $line";
+}
+
+sub class {
+ return scalar(caller);
+}
+
+1;
--- /dev/null
+package OpenSRF::AppSession;
+use OpenSRF::DOM;
+use OpenSRF::DOM::Element::userAuth;
+use OpenSRF::DomainObject::oilsMessage;
+use OpenSRF::DomainObject::oilsMethod;
+use OpenSRF::DomainObject::oilsResponse qw/:status/;
+use OpenSRF::Transport::PeerHandle;
+use OpenSRF::Utils::Config;
+use OpenSRF::Utils::Logger qw(:level);
+use OpenSRF::EX;
+use OpenSRF;
+use Exporter;
+use base qw/Exporter OpenSRF/;
+use Time::HiRes qw( time usleep );
+use warnings;
+use strict;
+
+our @EXPORT_OK = qw/CONNECTING INIT_CONNECTED CONNECTED DISCONNECTED CLIENT SERVER/;
+our %EXPORT_TAGS = ( state => [ qw/CONNECTING INIT_CONNECTED CONNECTED DISCONNECTED/ ],
+ endpoint => [ qw/CLIENT SERVER/ ],
+);
+
+my $logger = "OpenSRF::Utils::Logger";
+
+our %_CACHE;
+our @_CLIENT_CACHE;
+our @_RESEND_QUEUE;
+
+sub kill_client_session_cache {
+ for my $session ( @_CLIENT_CACHE ) {
+ $session->kill_me;
+ }
+}
+
+sub CONNECTING { return 3 };
+sub INIT_CONNECTED { return 4 };
+sub CONNECTED { return 1 };
+sub DISCONNECTED { return 2 };
+
+sub CLIENT { return 2 };
+sub SERVER { return 1 };
+
+sub find {
+ return undef unless (defined $_[1]);
+ return $_CACHE{$_[1]} if (exists($_CACHE{$_[1]}));
+}
+
+sub find_client {
+ my( $self, $app ) = @_;
+ $logger->debug( "Client Cache contains: " .scalar(@_CLIENT_CACHE), INTERNAL );
+ my ($client) = grep { $_->[0] eq $app and $_->[1] == 1 } @_CLIENT_CACHE;
+ $client->[1] = 0;
+ return $client->[2];
+}
+
+sub transport_connected {
+ my $self = shift;
+ if( ! exists $self->{peer_handle} ||
+ ! $self->{peer_handle} ) {
+ return 0;
+ }
+ return $self->{peer_handle}->tcp_connected();
+}
+
+# ----------------------------------------------------------------------------
+# Clears the transport buffers
+# call this if you are not through with the sesssion, but you want
+# to have a clean slate. You shouldn't have to call this if
+# you are correctly 'recv'ing all of the data from a request.
+# however, if you don't want all of the data, this will
+# slough off any excess
+# * * Note: This will delete data for all sessions using this transport
+# handle. For example, all client sessions use the same handle.
+# ----------------------------------------------------------------------------
+sub buffer_reset {
+
+ my $self = shift;
+ if( ! exists $self->{peer_handle} || ! $self->{peer_handle} ) {
+ return 0;
+ }
+ $self->{peer_handle}->buffer_reset();
+}
+
+
+sub client_cache {
+ my $self = shift;
+ push @_CLIENT_CACHE, [ $self->app, 1, $self ];
+}
+
+# when any incoming data is received, this method is called.
+sub server_build {
+ my $class = shift;
+ $class = ref($class) || $class;
+
+ my $sess_id = shift;
+ my $remote_id = shift;
+ my $service = shift;
+
+ return undef unless ($sess_id and $remote_id and $service);
+
+ my $conf = OpenSRF::Utils::Config->current;
+
+ if( ! $conf ) {
+ OpenSRF::EX::Config->throw( "No suitable config found" );
+ }
+
+ if ( my $thingy = $class->find($sess_id) ) {
+ $thingy->remote_id( $remote_id );
+ $logger->debug( "AppSession returning existing session $sess_id", DEBUG );
+ return $thingy;
+ } else {
+ $logger->debug( "AppSession building new server session $sess_id", DEBUG );
+ }
+
+ if( $service eq "client" ) {
+ #throw OpenSRF::EX::PANIC ("Attempting to build a client session as a server" .
+ # " Session ID [$sess_id], remote_id [$remote_id]");
+ $logger->debug("Attempting to build a client session as ".
+ "a server Session ID [$sess_id], remote_id [$remote_id]", ERROR );
+ }
+
+
+ my $max_requests = $conf->$service->max_requests;
+ $logger->debug( "Max Requests for $service is $max_requests", INTERNAL );# if $max_requests;
+
+ $logger->transport( "AppSession creating new session: $sess_id", INTERNAL );
+
+ my $self = bless { recv_queue => [],
+ request_queue => [],
+ requests => 0,
+ endpoint => SERVER,
+ state => CONNECTING,
+ session_id => $sess_id,
+ remote_id => $remote_id,
+ peer_handle => OpenSRF::Transport::PeerHandle->retrieve($service),
+ max_requests => $max_requests,
+ session_threadTrace => 0,
+ service => $service,
+ } => $class;
+
+ return $_CACHE{$sess_id} = $self;
+}
+
+sub service { return shift()->{service}; }
+
+sub continue_request {
+ my $self = shift;
+ $self->{'requests'}++;
+ return $self->{'requests'} <= $self->{'max_requests'} ? 1 : 0;
+}
+
+sub last_sent_payload {
+ my( $self, $payload ) = @_;
+ if( $payload ) {
+ return $self->{'last_sent_payload'} = $payload;
+ }
+ return $self->{'last_sent_payload'};
+}
+
+sub last_sent_type {
+ my( $self, $type ) = @_;
+ if( $type ) {
+ return $self->{'last_sent_type'} = $type;
+ }
+ return $self->{'last_sent_type'};
+}
+
+# When we're a client and we want to connect to a remote service
+# create( $app, username => $user, secret => $passwd );
+# OR
+# create( $app, sysname => $user, secret => $shared_secret );
+sub create {
+ my $class = shift;
+ $class = ref($class) || $class;
+
+ my $app = shift;
+ my %auth_args = @_;
+
+
+ unless ( $app &&
+ exists($auth_args{secret}) &&
+ ( exists($auth_args{username}) ||
+ exists($auth_args{sysname}) ) ) {
+ throw OpenSRF::EX::User ( 'Insufficient authentication information for session creation');
+ }
+
+ if( my $thingy = OpenSRF::AppSession->find_client( $app ) ) {
+ $logger->debug( "AppSession returning existing client session for $app", DEBUG );
+ return $thingy;
+ } else {
+ $logger->debug( "AppSession creating new client session for $app", DEBUG );
+ }
+
+
+
+ my $auth = OpenSRF::DOM::Element::userAuth->new( %auth_args );
+
+ my $conf = OpenSRF::Utils::Config->current;
+
+ if( ! $conf ) {
+ OpenSRF::EX::Config->throw( "No suitable config found" );
+ }
+
+ my $sess_id = time . rand( $$ );
+ while ( $class->find($sess_id) ) {
+ $sess_id = time . rand( $$ );
+ }
+
+ my $r_id = $conf->$app->transport_target ||
+ die("No remote id for $app!");
+
+ my $self = bless { app_name => $app,
+ client_auth => $auth,
+ #recv_queue => [],
+ request_queue => [],
+ endpoint => CLIENT,
+ state => DISCONNECTED,#since we're init'ing
+ session_id => $sess_id,
+ remote_id => $r_id,
+ orig_remote_id => $r_id,
+ # peer_handle => OpenSRF::Transport::PeerHandle->retrieve($app),
+ peer_handle => OpenSRF::Transport::PeerHandle->retrieve("client"),
+ session_threadTrace => 0,
+ } => $class;
+
+ $self->client_cache();
+ $_CACHE{$sess_id} = $self;
+ return $self->find_client( $app );
+}
+
+sub app {
+ return shift()->{app_name};
+}
+
+sub reset {
+ my $self = shift;
+ $self->remote_id($$self{orig_remote_id});
+}
+
+# 'connect' can be used as a constructor if called as a class method,
+# or used to connect a session that has disconnectd if called against
+# an existing session that seems to be disconnected, or was just built
+# using 'create' above.
+
+# connect( $app, username => $user, secret => $passwd );
+# OR
+# connect( $app, sysname => $user, secret => $shared_secret );
+
+# --- Returns undef if the connect attempt times out.
+# --- Returns the OpenSRF::EX object if one is returned by the server
+# --- Returns self if connected
+sub connect {
+ my $self = shift;
+ my $class = ref($self) || $self;
+
+ return $self if ( ref( $self ) and $self->state && $self->state == CONNECTED );
+
+ my $app = shift;
+
+ $self = $class->create($app, @_) if (!ref($self));
+ return undef unless ($self);
+
+
+ $self->reset;
+ $self->state(CONNECTING);
+ $self->send('CONNECT', "");
+
+ my $time_remaining = OpenSRF::Utils::Config->current->client->connect_timeout;
+
+ while ( $self->state != CONNECTED and $time_remaining > 0 ) {
+ my $starttime = time;
+ $self->queue_wait($time_remaining);
+ my $endtime = time;
+ $time_remaining -= ($endtime - $starttime);
+ }
+
+ return undef unless($self->state == CONNECTED);
+
+ return $self;
+}
+
+sub finish {
+ my $self = shift;
+ #$self->disconnect if ($self->endpoint == CLIENT);
+ for my $ses ( @_CLIENT_CACHE ) {
+ if ($ses->[2]->session_id eq $self->session_id) {
+ $ses->[1] = 1;
+ }
+ }
+}
+
+sub kill_me {
+ my $self = shift;
+ if( ! $self->session_id ) { return 0; }
+ $self->disconnect;
+ $logger->transport( "AppSession killing self: " . $self->session_id(), DEBUG );
+ my @a;
+ for my $ses ( @_CLIENT_CACHE ) {
+ push @a, $ses
+ if ($ses->[2]->session_id ne $self->session_id);
+ }
+ @_CLIENT_CACHE = @a;
+ delete $_CACHE{$self->session_id};
+ delete($$self{$_}) for (keys %$self);
+}
+
+sub disconnect {
+ my $self = shift;
+ unless( $self->state == DISCONNECTED ) {
+ $self->send('DISCONNECT', "") if ($self->endpoint == CLIENT);;
+ $self->state( DISCONNECTED );
+ }
+ $self->reset;
+}
+
+sub request {
+ my $self = shift;
+ my $meth = shift;
+
+ my $method;
+ if (!ref $meth) {
+ $method = new OpenSRF::DomainObject::oilsMethod ( method => $meth );
+ } else {
+ $method = $meth;
+ }
+
+ $method->params( @_ );
+
+ $self->send('REQUEST',$method);
+}
+
+
+sub send {
+ my $self = shift;
+ my @payload_list = @_; # this is a Domain Object
+
+ $logger->debug( "In send", INTERNAL );
+
+ my $tT;
+
+ if( @payload_list % 2 ) { $tT = pop @payload_list; }
+
+ if( ! @payload_list ) {
+ $logger->debug( "payload_list param is incomplete in AppSession::send()", ERROR );
+ return undef;
+ }
+
+ my $doc = OpenSRF::DOM->createDocument();
+
+ $logger->debug( "In send2", INTERNAL );
+
+ my $disconnect = 0;
+ my $connecting = 0;
+
+ while( @payload_list ) {
+
+ my ($msg_type, $payload) = ( shift(@payload_list), shift(@payload_list) );
+
+ if ($msg_type eq 'DISCONNECT' ) {
+ $disconnect++;
+ if( $self->state == DISCONNECTED) {
+ next;
+ }
+ }
+
+ if( $msg_type eq "CONNECT" ) { $connecting++; }
+
+
+ if( $payload ) {
+ $logger->debug( "Payload is ".$payload->toString, INTERNAL );
+ }
+
+
+ my $msg = OpenSRF::DomainObject::oilsMessage->new();
+ $logger->debug( "AppSession after creating oilsMessage $msg", INTERNAL );
+
+ $msg->type($msg_type);
+ $logger->debug( "AppSession after adding type" . $msg->toString(), INTERNAL );
+
+ $msg->userAuth($self->client_auth) if ($self->endpoint == CLIENT && $msg_type eq 'CONNECT');
+
+ no warnings;
+ $msg->threadTrace( $tT || int($self->session_threadTrace) || int($self->last_threadTrace) );
+ use warnings;
+
+ if ($msg->type eq 'REQUEST') {
+ if ( !defined($tT) || $self->last_threadTrace != $tT ) {
+ $msg->update_threadTrace;
+ $self->session_threadTrace( $msg->threadTrace );
+ $tT = $self->session_threadTrace;
+ OpenSRF::AppRequest->new($self, $payload);
+ }
+ }
+
+ $msg->protocol(1);
+ $msg->payload($payload) if $payload;
+
+ $doc->documentElement->appendChild( $msg );
+
+
+ $logger->debug( "AppSession sending ".$msg->type." to ".$self->remote_id.
+ " with threadTrace [".$msg->threadTrace."]", INFO );
+
+ }
+
+ if ($self->endpoint == CLIENT and ! $disconnect) {
+ $self->queue_wait(0);
+ unless ($self->state == CONNECTED || ($self->state == CONNECTING && $connecting )) {
+ my $v = $self->connect();
+ if( ! $v ) {
+ $logger->debug( "Unable to connect to remote service in AppSession::send()", ERROR );
+ return undef;
+ }
+ if( $v and $v->class->isa( "OpenSRF::EX" ) ) {
+ return $v;
+ }
+ }
+ }
+
+ $logger->debug( "AppSession sending doc: " . $doc->toString(), INTERNAL );
+
+
+ $self->{peer_handle}->send(
+ to => $self->remote_id,
+ thread => $self->session_id,
+ body => $doc->toString );
+
+ return $self->app_request( $tT );
+}
+
+sub app_request {
+ my $self = shift;
+ my $tT = shift;
+
+ return undef unless (defined $tT);
+ my ($req) = grep { $_->threadTrace == $tT } @{ $self->{request_queue} };
+
+ return $req;
+}
+
+sub remove_app_request {
+ my $self = shift;
+ my $req = shift;
+
+ my @list = grep { $_->threadTrace != $req->threadTrace } @{ $self->{request_queue} };
+
+ $self->{request_queue} = \@list;
+}
+
+sub endpoint {
+ return $_[0]->{endpoint};
+}
+
+
+sub session_id {
+ my $self = shift;
+ return $self->{session_id};
+}
+
+sub push_queue {
+ my $self = shift;
+ my $resp = shift;
+ my $req = $self->app_request($resp->[1]);
+ return $req->push_queue( $resp->[0] ) if ($req);
+ push @{ $self->{recv_queue} }, $resp->[0];
+}
+
+sub last_threadTrace {
+ my $self = shift;
+ my $new_last_threadTrace = shift;
+
+ my $old_last_threadTrace = $self->{last_threadTrace};
+ if (defined $new_last_threadTrace) {
+ $self->{last_threadTrace} = $new_last_threadTrace;
+ return $new_last_threadTrace unless ($old_last_threadTrace);
+ }
+
+ return $old_last_threadTrace;
+}
+
+sub session_threadTrace {
+ my $self = shift;
+ my $new_last_threadTrace = shift;
+
+ my $old_last_threadTrace = $self->{session_threadTrace};
+ if (defined $new_last_threadTrace) {
+ $self->{session_threadTrace} = $new_last_threadTrace;
+ return $new_last_threadTrace unless ($old_last_threadTrace);
+ }
+
+ return $old_last_threadTrace;
+}
+
+sub last_message_type {
+ my $self = shift;
+ my $new_last_message_type = shift;
+
+ my $old_last_message_type = $self->{last_message_type};
+ if (defined $new_last_message_type) {
+ $self->{last_message_type} = $new_last_message_type;
+ return $new_last_message_type unless ($old_last_message_type);
+ }
+
+ return $old_last_message_type;
+}
+
+sub last_message_protocol {
+ my $self = shift;
+ my $new_last_message_protocol = shift;
+
+ my $old_last_message_protocol = $self->{last_message_protocol};
+ if (defined $new_last_message_protocol) {
+ $self->{last_message_protocol} = $new_last_message_protocol;
+ return $new_last_message_protocol unless