Fixed incorrect usage of result (now object rather than scalar), thanks Michal Zygmun...
[pwlib.git] / src / ptclib / psoap.cxx
blob79c2eca3511142f6753e174b03aa2347797b5217
1 /*
2 * psoap.cxx
4 * SOAP client / server classes.
6 * Portable Windows Library
8 * Copyright (c) 2003 Andreas Sikkema
10 * The contents of this file are subject to the Mozilla Public License
11 * Version 1.0 (the "License"); you may not use this file except in
12 * compliance with the License. You may obtain a copy of the License at
13 * http://www.mozilla.org/MPL/
15 * Software distributed under the License is distributed on an "AS IS"
16 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
17 * the License for the specific language governing rights and limitations
18 * under the License.
20 * The Original Code is Portable Windows Library.
22 * The Initial Developer of the Original Code is Andreas Sikkema
24 * Contributor(s): ______________________________________.
26 * $Log$
27 * Revision 1.8 2004/04/24 01:06:32 rjongbloed
28 * Apploed patch that impliments a number of checks to avoid segfaults when dealing with
29 * various clients. Thanks Ben Lear
31 * Revision 1.7 2004/01/17 17:45:59 csoutheren
32 * Changed to use PString::MakeEmpty
34 * Revision 1.6 2003/10/08 21:58:13 dereksmithies
35 * Add client authentication support. many thanks to Ben Lear.
37 * Revision 1.5 2003/04/28 00:09:14 craigs
38 * Patches from Andreas Sikkema
40 * Revision 1.4 2003/03/31 06:20:56 craigs
41 * Split the expat wrapper from the XML file handling to allow reuse of the parser
43 * Revision 1.3 2003/02/09 23:31:54 robertj
44 * Added referention PString's for efficiency.
46 * Revision 1.2 2003/02/09 23:22:46 robertj
47 * Fixed spelling errors, and setting return values, thanks Andreas Sikkema
49 * Revision 1.1 2003/02/04 22:46:48 robertj
50 * Added basic SOAP support, thanks Andreas Sikkema
54 #ifdef __GNUC__
55 #pragma implementation "psoap.h"
56 #endif
59 #include <ptlib.h>
62 #if P_EXPAT
64 #include <ptclib/psoap.h>
69 SOAP message classes
70 ####################
74 PSOAPMessage::PSOAPMessage( int options ) :
75 PXML( options ),
76 pSOAPBody( 0 ),
77 pSOAPMethod( 0 ),
78 faultCode( PSOAPMessage::NoFault )
82 PSOAPMessage::PSOAPMessage( const PString & method, const PString & nameSpace ) :
83 PXML( PXMLParser::Indent + PXMLParser::NewLineAfterElement ),
84 pSOAPBody( 0 ),
85 pSOAPMethod( 0 ),
86 faultCode( PSOAPMessage::NoFault )
88 SetMethod( method, nameSpace );
93 void PSOAPMessage::SetMethod( const PString & name, const PString & nameSpace )
95 PXMLElement* rtElement = 0;
97 if ( pSOAPBody == 0 )
99 SetRootElement("SOAP-ENV:Envelope");
101 rtElement = GetRootElement();
103 rtElement->SetAttribute("xmlns:SOAP-ENV", "http://schemas.xmlsoap.org/soap/envelope/", TRUE );
104 rtElement->SetAttribute("xmlns:xsi", "http://www.w3.org/1999/XMLSchema-instance", TRUE );
105 rtElement->SetAttribute("xmlns:xsd", "http://www.w3.org/1999/XMLSchema", TRUE );
106 rtElement->SetAttribute("xmlns:SOAP-ENC", "http://schemas.xmlsoap.org/soap/encoding/", TRUE );
108 pSOAPBody = new PXMLElement( rtElement, "SOAP-ENV:Body");
110 rtElement->AddChild( pSOAPBody, TRUE );
113 if ( pSOAPMethod == 0 )
115 rtElement = GetRootElement();
117 pSOAPMethod = new PXMLElement( rtElement, PString( "m:") + name );
118 if ( nameSpace != "" )
120 pSOAPMethod->SetAttribute("xmlns:m", nameSpace, TRUE );
122 pSOAPBody->AddChild( pSOAPMethod, TRUE );
127 void PSOAPMessage::GetMethod( PString & name, PString & nameSpace )
129 PString fullMethod = pSOAPMethod->GetName();
130 PINDEX sepLocation = fullMethod.Find(':');
131 if (sepLocation != P_MAX_INDEX) {
132 PString methodID = fullMethod.Left(sepLocation);
133 name = fullMethod.Right(fullMethod.GetSize() - 2 - sepLocation);
134 nameSpace = pSOAPMethod->GetAttribute( "xmlns:" + methodID );
139 void PSOAPMessage::AddParameter( PString name, PString type, PString value )
141 if ( pSOAPMethod )
143 PXMLElement* rtElement = GetRootElement();
145 PXMLElement* pParameter = new PXMLElement( rtElement, name);
146 PXMLData* pParameterData = new PXMLData( pParameter, value);
148 if ( type != "" )
150 pParameter->SetAttribute( "xsi:type", PString( "xsd:" ) + type );
153 pParameter->AddChild( pParameterData, TRUE );
155 AddParameter( pParameter, TRUE );
159 void PSOAPMessage::AddParameter( PXMLElement* parameter, BOOL dirty )
161 if ( pSOAPMethod )
163 pSOAPMethod->AddChild( parameter, dirty );
167 void PSOAPMessage::PrintOn(ostream & strm) const
169 BOOL newLine = ( options & PXMLParser::NewLineAfterElement ) != 0;
171 PString ver = version;
172 PString enc = encoding;
173 int salone = standAlone;
175 if ( ver.IsEmpty() )
176 ver= "1.0";
177 if ( enc.IsEmpty() )
178 enc = "UTF-8";
179 if ( salone == -2 )
180 salone = -1;
182 strm << "<?xml version=\"" << ver << "\" encoding=\"" << enc << "\"";
183 switch ( salone ) {
184 case 0:
185 strm << " standalone=\"no\"";
186 break;
187 case 1:
188 strm << " standalone=\"yes\"";
189 break;
190 default:
191 break;
194 strm << "?>";
195 if ( newLine )
196 strm << endl;
198 if ( rootElement != NULL ) {
199 rootElement->Output(strm, *(this), 2 );
203 PString PSOAPMessage::AsString( void )
205 PStringStream stringStream;
206 PrintOn( stringStream );
208 PString SOAPString = stringStream;
210 return SOAPString;
214 PString faultCodeToString( PINDEX faultCode )
216 PString faultCodeStr;
217 switch ( faultCode )
219 case PSOAPMessage::VersionMismatch:
220 faultCodeStr = "VersionMisMatch";
221 break;
222 case PSOAPMessage::MustUnderstand:
223 faultCodeStr = "MustUnderstand";
224 break;
225 case PSOAPMessage::Client:
226 faultCodeStr = "Client";
227 break;
228 case PSOAPMessage::Server:
229 faultCodeStr = "Server";
230 break;
231 default:
232 // Default it's the server's fault. Can't blame it on the customer, because he/she is king ;-)
233 faultCodeStr = "Server";
234 break;
237 return faultCodeStr;
240 PINDEX stringToFaultCode( PString & faultStr )
242 if ( faultStr == "VersionMisMatch" )
243 return PSOAPMessage::VersionMismatch;
245 if ( faultStr == "MustUnderstand" )
246 return PSOAPMessage::MustUnderstand;
248 if ( faultStr == "Client" )
249 return PSOAPMessage::Client;
251 if ( faultStr == "Server" )
252 return PSOAPMessage::Server;
254 return PSOAPMessage::Server;
257 BOOL PSOAPMessage::GetParameter( const PString & name, PString & value )
259 PXMLElement* pElement = GetParameter( name );
260 if(pElement == NULL)
261 return FALSE;
264 if ( pElement->GetAttribute( "xsi:type") == "xsd:string" )
266 value = pElement->GetData();
267 return TRUE;
270 value.MakeEmpty();
271 return FALSE;
274 BOOL PSOAPMessage::GetParameter( const PString & name, int & value )
276 PXMLElement* pElement = GetParameter( name );
277 if(pElement == NULL)
278 return FALSE;
280 if ( pElement->GetAttribute( "xsi:type") == "xsd:int" )
282 value = pElement->GetData().AsInteger();
283 return TRUE;
286 value = -1;
287 return FALSE;
290 PXMLElement* PSOAPMessage::GetParameter( const PString & name )
292 if ( pSOAPMethod )
294 return pSOAPMethod->GetElement( name, 0 );
296 else
298 return 0;
302 BOOL PSOAPMessage::Load( const PString & str )
304 if ( !PXML::Load( str ) )
305 return FALSE;
307 if ( rootElement != NULL )
309 PString soapEnvelopeName = rootElement->GetName();
310 PString soapEnvelopeID = soapEnvelopeName.Left( soapEnvelopeName.Find(':') );
312 pSOAPBody = rootElement->GetElement( soapEnvelopeID + ":Body", 0 );
314 if ( pSOAPBody != NULL )
316 PXMLObjectArray subObjects = pSOAPBody->GetSubObjects() ;
318 PINDEX idx;
319 PINDEX size = subObjects.GetSize();
321 for ( idx = 0; idx < size; idx++ ) {
322 if ( subObjects[ idx ].IsElement() ) {
323 // First subobject being an element is the method
324 pSOAPMethod = ( PXMLElement * ) &subObjects[ idx ];
326 PString method;
327 PString nameSpace;
329 GetMethod( method, nameSpace );
331 // Check if method name is "Fault"
332 if ( method == "Fault" )
334 // The SOAP server has signalled an error
335 PString faultCodeData = GetParameter( "faultcode" )->GetData();
336 faultCode = stringToFaultCode( faultCodeData );
337 faultText = GetParameter( "faultstring" )->GetData();
339 else
341 return TRUE;
347 return FALSE;
350 void PSOAPMessage::SetFault( PINDEX code, const PString & text)
352 faultCode = code;
353 faultText = text;
355 PString faultCodeStr = faultCodeToString( code );
357 SetMethod( "Fault", "" );
359 AddParameter( "faultcode", "", faultCodeStr );
360 AddParameter( "faultstring", "", text );
367 SOAP server classes
368 ####################
373 PSOAPServerResource::PSOAPServerResource()
374 : PHTTPResource( DEFAULT_SOAP_URL ),
375 soapAction( " " )
379 PSOAPServerResource::PSOAPServerResource(
380 const PHTTPAuthority & auth ) // Authorisation for the resource.
381 : PHTTPResource( DEFAULT_SOAP_URL, auth ),
382 soapAction( " " )
385 PSOAPServerResource::PSOAPServerResource(
386 const PURL & url ) // Name of the resource in URL space.
387 : PHTTPResource(url )
391 PSOAPServerResource::PSOAPServerResource(
392 const PURL & url, // Name of the resource in URL space.
393 const PHTTPAuthority & auth // Authorisation for the resource.
395 : PHTTPResource( url, auth )
399 BOOL PSOAPServerResource::SetMethod(const PString & methodName, const PNotifier & func)
401 // Set the method for the notifier function and add it to the list
402 PWaitAndSignal m( methodMutex );
404 // Find the method, or create a new one
405 PSOAPServerMethod * methodInfo;
407 PINDEX pos = methodList.GetValuesIndex( methodName );
408 if (pos != P_MAX_INDEX)
410 methodInfo = ( PSOAPServerMethod *) methodList.GetAt( pos );
412 else
414 methodInfo = new PSOAPServerMethod( methodName );
415 methodList.Append( methodInfo );
418 // set the function
419 methodInfo->methodFunc = func;
421 return TRUE;
424 BOOL PSOAPServerResource::LoadHeaders( PHTTPRequest& /* request */ ) // Information on this request.
426 return TRUE;
429 BOOL PSOAPServerResource::OnPOSTData( PHTTPRequest & request,
430 const PStringToString & /*data*/)
432 PTRACE( 2, "PSOAPServerResource\tReceived post data, request: " << request.entityBody );
434 PString reply;
436 BOOL ok = FALSE;
438 // Check for the SOAPAction header
439 PString* pSOAPAction = request.inMIME.GetAt( "SOAPAction" );
440 if ( pSOAPAction )
442 // If it's available check if we are expecting a special header value
443 if ( soapAction.IsEmpty() || soapAction == " " )
445 // A space means anything goes
446 ok = OnSOAPRequest( request.entityBody, reply );
448 else
450 // Check if the incoming header is the same as we expected
451 if ( *pSOAPAction == soapAction )
453 ok = OnSOAPRequest( request.entityBody, reply );
455 else
457 ok = FALSE;
458 reply = FormatFault( PSOAPMessage::Client, "Incorrect SOAPAction in HTTP Header: " + *pSOAPAction ).AsString();
462 else
464 ok = FALSE;
465 reply = FormatFault( PSOAPMessage::Client, "SOAPAction is missing in HTTP Header" ).AsString();
468 // If everything went OK, reply with ReturnCode 200 (OK)
469 if ( ok )
470 request.code = PHTTP::RequestOK;
471 else
472 // Reply with InternalServerError (500)
473 request.code = PHTTP::InternalServerError;
475 // Set the correct content-type
476 request.outMIME.SetAt(PHTTP::ContentTypeTag, "text/xml");
478 // Start constructing the response
479 PINDEX len = reply.GetLength();
480 request.server.StartResponse( request.code, request.outMIME, len );
482 // Write the reply to the client
483 return request.server.Write( (const char* ) reply, len );
487 BOOL PSOAPServerResource::OnSOAPRequest( const PString & body, PString & reply )
489 // Load the HTTP body into the SOAP (XML) parser
490 PSOAPMessage request;
491 BOOL ok = request.Load( body );
493 // If parsing the XML to SOAP failed reply with an error
494 if ( !ok )
496 reply = FormatFault( PSOAPMessage::Client, "XML error:" + request.GetErrorString() ).AsString();
497 return FALSE;
501 PString method;
502 PString nameSpace;
504 // Retrieve the method from the SOAP messsage
505 request.GetMethod( method, nameSpace );
507 PTRACE( 3, "PSOAPServerResource\tReceived SOAP message for method " << method);
509 return OnSOAPRequest( method, request, reply );
512 BOOL PSOAPServerResource::OnSOAPRequest( const PString & methodName,
513 PSOAPMessage & request,
514 PString & reply )
516 methodMutex.Wait();
518 // Find the method information
519 PINDEX pos = methodList.GetValuesIndex( methodName );
521 if ( pos == P_MAX_INDEX )
523 reply = FormatFault( PSOAPMessage::Client, "Unknown method = " + methodName ).AsString();
524 return FALSE;
527 PSOAPServerMethod * methodInfo = ( PSOAPServerMethod * )methodList.GetAt( pos );
528 PNotifier notifier = methodInfo->methodFunc;
530 methodMutex.Signal();
532 // create a request/response container to be passed to the notifier function
533 PSOAPServerRequestResponse p( request );
535 // call the notifier
536 notifier( p, 0 );
538 // get the reply
540 reply = p.response.AsString();
542 return p.response.GetFaultCode() == PSOAPMessage::NoFault;
546 PSOAPMessage PSOAPServerResource::FormatFault( PINDEX code, const PString & str )
548 PTRACE(2, "PSOAPServerResource\trequest failed: " << str);
550 PSOAPMessage reply;
552 PString faultCodeStr = faultCodeToString( code );
554 reply.SetMethod( "Fault", "" );
556 reply.AddParameter( "faultcode", "", faultCodeStr );
557 reply.AddParameter( "faultstring", "", str );
559 return reply;
564 SOAP client classes
565 ####################
569 PSOAPClient::PSOAPClient( const PURL & _url )
570 : url(_url),
571 soapAction( " " )
573 timeout = 10000;
576 BOOL PSOAPClient::MakeRequest( const PString & method, const PString & nameSpace )
578 PSOAPMessage request( method, nameSpace );
579 PSOAPMessage response;
581 return MakeRequest( request, response );
584 BOOL PSOAPClient::MakeRequest( const PString & method, const PString & nameSpace, PSOAPMessage & response )
586 PSOAPMessage request( method, nameSpace );
588 return MakeRequest( request, response );
591 BOOL PSOAPClient::MakeRequest( PSOAPMessage & request, PSOAPMessage & response )
593 return PerformRequest( request, response );
596 BOOL PSOAPClient::PerformRequest( PSOAPMessage & request, PSOAPMessage & response )
598 // create SOAP request
599 PString soapRequest;
601 PStringStream txt;
603 if ( !request.Save( soapRequest ) )
606 txt << "Error creating request XML ("
607 << request.GetErrorLine()
608 << ") :"
609 << request.GetErrorString();
610 return FALSE;
613 // End with a newline
614 soapRequest += "\n";
616 PTRACE( 5, "SOAPClient\tOutgoing SOAP is " << soapRequest );
618 // do the request
619 PHTTPClient client;
620 PMIMEInfo sendMIME, replyMIME;
621 sendMIME.SetAt( "Server", url.GetHostName() );
622 sendMIME.SetAt( PHTTP::ContentTypeTag, "text/xml" );
623 sendMIME.SetAt( "SOAPAction", soapAction );
625 if(url.GetUserName() != "") {
626 PStringStream SoapAuthToken;
627 SoapAuthToken << url.GetUserName() << ":" << url.GetPassword();
628 sendMIME.SetAt( "Authorization", PBase64::Encode(SoapAuthToken) );
631 // Set thetimeout
632 client.SetReadTimeout( timeout );
634 // Send the POST request to the server
635 BOOL ok = client.PostData( url, sendMIME, soapRequest, replyMIME );
637 // Find the length of the response
638 PINDEX contentLength;
639 if ( replyMIME.Contains( PHTTP::ContentLengthTag ) )
640 contentLength = ( PINDEX ) replyMIME[ PHTTP::ContentLengthTag ].AsUnsigned();
641 else if ( ok)
642 contentLength = P_MAX_INDEX;
643 else
644 contentLength = 0;
646 // Retrieve the response
647 PString replyBody = client.ReadString( contentLength );
649 PTRACE( 5, "PSOAP\tIncoming SOAP is " << replyBody );
651 // Check if the server really gave us something
652 if ( !ok || replyBody.IsEmpty() )
654 txt << "HTTP POST failed: "
655 << client.GetLastResponseCode() << ' '
656 << client.GetLastResponseInfo();
659 // Parse the response only if the response code from the server
660 // is either 500 (Internal server error) or 200 (RequestOK)
662 if ( ( client.GetLastResponseCode() == PHTTP::RequestOK ) ||
663 ( client.GetLastResponseCode() == PHTTP::InternalServerError ) )
665 if (!response.Load(replyBody))
667 txt << "Error parsing response XML ("
668 << response.GetErrorLine()
669 << ") :"
670 << response.GetErrorString();
672 PStringArray lines = replyBody.Lines();
673 for ( int offset = -2; offset <= 2; offset++ ) {
674 int line = response.GetErrorLine() + offset;
676 if ( line >= 0 && line < lines.GetSize() )
677 txt << lines[ ( PINDEX ) line ];
683 if ( ( client.GetLastResponseCode() != PHTTP::RequestOK ) &&
684 ( client.GetLastResponseCode() != PHTTP::InternalServerError ) &&
685 ( !ok ) )
687 response.SetFault( PSOAPMessage::Server, txt );
688 return FALSE;
692 return TRUE;
696 #endif // P_EXPAT
699 // End of File ////////////////////////////////////////////////////////////////