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
20 * The Original Code is Portable Windows Library.
22 * The Initial Developer of the Original Code is Andreas Sikkema
24 * Contributor(s): ______________________________________.
27 * Revision 1.7 2004/01/17 17:45:59 csoutheren
28 * Changed to use PString::MakeEmpty
30 * Revision 1.6 2003/10/08 21:58:13 dereksmithies
31 * Add client authentication support. many thanks to Ben Lear.
33 * Revision 1.5 2003/04/28 00:09:14 craigs
34 * Patches from Andreas Sikkema
36 * Revision 1.4 2003/03/31 06:20:56 craigs
37 * Split the expat wrapper from the XML file handling to allow reuse of the parser
39 * Revision 1.3 2003/02/09 23:31:54 robertj
40 * Added referention PString's for efficiency.
42 * Revision 1.2 2003/02/09 23:22:46 robertj
43 * Fixed spelling errors, and setting return values, thanks Andreas Sikkema
45 * Revision 1.1 2003/02/04 22:46:48 robertj
46 * Added basic SOAP support, thanks Andreas Sikkema
51 #pragma implementation "psoap.h"
60 #include <ptclib/psoap.h>
70 PSOAPMessage::PSOAPMessage( int options
) :
74 faultCode( PSOAPMessage::NoFault
)
78 PSOAPMessage::PSOAPMessage( const PString
& method
, const PString
& nameSpace
) :
79 PXML( PXMLParser::Indent
+ PXMLParser::NewLineAfterElement
),
82 faultCode( PSOAPMessage::NoFault
)
84 SetMethod( method
, nameSpace
);
89 void PSOAPMessage::SetMethod( const PString
& name
, const PString
& nameSpace
)
91 PXMLElement
* rtElement
= 0;
95 SetRootElement("SOAP-ENV:Envelope");
97 rtElement
= GetRootElement();
99 rtElement
->SetAttribute("xmlns:SOAP-ENV", "http://schemas.xmlsoap.org/soap/envelope/", TRUE
);
100 rtElement
->SetAttribute("xmlns:xsi", "http://www.w3.org/1999/XMLSchema-instance", TRUE
);
101 rtElement
->SetAttribute("xmlns:xsd", "http://www.w3.org/1999/XMLSchema", TRUE
);
102 rtElement
->SetAttribute("xmlns:SOAP-ENC", "http://schemas.xmlsoap.org/soap/encoding/", TRUE
);
104 pSOAPBody
= new PXMLElement( rtElement
, "SOAP-ENV:Body");
106 rtElement
->AddChild( pSOAPBody
, TRUE
);
109 if ( pSOAPMethod
== 0 )
111 rtElement
= GetRootElement();
113 pSOAPMethod
= new PXMLElement( rtElement
, PString( "m:") + name
);
114 if ( nameSpace
!= "" )
116 pSOAPMethod
->SetAttribute("xmlns:m", nameSpace
, TRUE
);
118 pSOAPBody
->AddChild( pSOAPMethod
, TRUE
);
123 void PSOAPMessage::GetMethod( PString
& name
, PString
& nameSpace
)
125 PString fullMethod
= pSOAPMethod
->GetName();
126 PINDEX sepLocation
= fullMethod
.Find(':');
127 PString methodID
= fullMethod
.Left( sepLocation
);
128 name
= fullMethod
.Right( fullMethod
.GetSize() - 2 - sepLocation
);
130 nameSpace
= pSOAPMethod
->GetAttribute( "xmlns:" + methodID
);
135 void PSOAPMessage::AddParameter( PString name
, PString type
, PString value
)
139 PXMLElement
* rtElement
= GetRootElement();
141 PXMLElement
* pParameter
= new PXMLElement( rtElement
, name
);
142 PXMLData
* pParameterData
= new PXMLData( pParameter
, value
);
146 pParameter
->SetAttribute( "xsi:type", PString( "xsd:" ) + type
);
149 pParameter
->AddChild( pParameterData
, TRUE
);
151 AddParameter( pParameter
, TRUE
);
155 void PSOAPMessage::AddParameter( PXMLElement
* parameter
, BOOL dirty
)
159 pSOAPMethod
->AddChild( parameter
, dirty
);
163 void PSOAPMessage::PrintOn(ostream
& strm
) const
165 BOOL newLine
= ( options
& PXMLParser::NewLineAfterElement
) != 0;
167 PString ver
= version
;
168 PString enc
= encoding
;
169 int salone
= standAlone
;
178 strm
<< "<?xml version=\"" << ver
<< "\" encoding=\"" << enc
<< "\"";
181 strm
<< " standalone=\"no\"";
184 strm
<< " standalone=\"yes\"";
194 if ( rootElement
!= NULL
) {
195 rootElement
->Output(strm
, *(this), 2 );
199 PString
PSOAPMessage::AsString( void )
201 PStringStream stringStream
;
202 PrintOn( stringStream
);
204 PString SOAPString
= stringStream
;
210 PString
faultCodeToString( PINDEX faultCode
)
212 PString faultCodeStr
;
215 case PSOAPMessage::VersionMismatch
:
216 faultCodeStr
= "VersionMisMatch";
218 case PSOAPMessage::MustUnderstand
:
219 faultCodeStr
= "MustUnderstand";
221 case PSOAPMessage::Client
:
222 faultCodeStr
= "Client";
224 case PSOAPMessage::Server
:
225 faultCodeStr
= "Server";
228 // Default it's the server's fault. Can't blame it on the customer, because he/she is king ;-)
229 faultCodeStr
= "Server";
236 PINDEX
stringToFaultCode( PString
& faultStr
)
238 if ( faultStr
== "VersionMisMatch" )
239 return PSOAPMessage::VersionMismatch
;
241 if ( faultStr
== "MustUnderstand" )
242 return PSOAPMessage::MustUnderstand
;
244 if ( faultStr
== "Client" )
245 return PSOAPMessage::Client
;
247 if ( faultStr
== "Server" )
248 return PSOAPMessage::Server
;
250 return PSOAPMessage::Server
;
253 BOOL
PSOAPMessage::GetParameter( const PString
& name
, PString
& value
)
255 PXMLElement
* pElement
= GetParameter( name
);
257 if ( pElement
->GetAttribute( "xsi:type") == "xsd:string" )
259 value
= pElement
->GetData();
267 BOOL
PSOAPMessage::GetParameter( const PString
& name
, int & value
)
269 PXMLElement
* pElement
= GetParameter( name
);
271 if ( pElement
->GetAttribute( "xsi:type") == "xsd:int" )
273 value
= pElement
->GetData().AsInteger();
281 PXMLElement
* PSOAPMessage::GetParameter( const PString
& name
)
285 return pSOAPMethod
->GetElement( name
, 0 );
293 BOOL
PSOAPMessage::Load( const PString
& str
)
295 if ( !PXML::Load( str
) )
298 if ( rootElement
!= NULL
)
300 PString soapEnvelopeName
= rootElement
->GetName();
301 PString soapEnvelopeID
= soapEnvelopeName
.Left( soapEnvelopeName
.Find(':') );
303 pSOAPBody
= rootElement
->GetElement( soapEnvelopeID
+ ":Body", 0 );
305 if ( pSOAPBody
!= NULL
)
307 PXMLObjectArray subObjects
= pSOAPBody
->GetSubObjects() ;
310 PINDEX size
= subObjects
.GetSize();
312 for ( idx
= 0; idx
< size
; idx
++ ) {
313 if ( subObjects
[ idx
].IsElement() ) {
314 // First subobject being an element is the method
315 pSOAPMethod
= ( PXMLElement
* ) &subObjects
[ idx
];
320 GetMethod( method
, nameSpace
);
322 // Check if method name is "Fault"
323 if ( method
== "Fault" )
325 // The SOAP server has signalled an error
326 PString faultCodeData
= GetParameter( "faultcode" )->GetData();
327 faultCode
= stringToFaultCode( faultCodeData
);
328 faultText
= GetParameter( "faultstring" )->GetData();
341 void PSOAPMessage::SetFault( PINDEX code
, const PString
& text
)
346 PString faultCodeStr
= faultCodeToString( code
);
348 SetMethod( "Fault", "" );
350 AddParameter( "faultcode", "", faultCodeStr
);
351 AddParameter( "faultstring", "", text
);
364 PSOAPServerResource::PSOAPServerResource()
365 : PHTTPResource( DEFAULT_SOAP_URL
),
370 PSOAPServerResource::PSOAPServerResource(
371 const PHTTPAuthority
& auth
) // Authorisation for the resource.
372 : PHTTPResource( DEFAULT_SOAP_URL
, auth
),
376 PSOAPServerResource::PSOAPServerResource(
377 const PURL
& url
) // Name of the resource in URL space.
378 : PHTTPResource(url
)
382 PSOAPServerResource::PSOAPServerResource(
383 const PURL
& url
, // Name of the resource in URL space.
384 const PHTTPAuthority
& auth
// Authorisation for the resource.
386 : PHTTPResource( url
, auth
)
390 BOOL
PSOAPServerResource::SetMethod(const PString
& methodName
, const PNotifier
& func
)
392 // Set the method for the notifier function and add it to the list
393 PWaitAndSignal
m( methodMutex
);
395 // Find the method, or create a new one
396 PSOAPServerMethod
* methodInfo
;
398 PINDEX pos
= methodList
.GetValuesIndex( methodName
);
399 if (pos
!= P_MAX_INDEX
)
401 methodInfo
= ( PSOAPServerMethod
*) methodList
.GetAt( pos
);
405 methodInfo
= new PSOAPServerMethod( methodName
);
406 methodList
.Append( methodInfo
);
410 methodInfo
->methodFunc
= func
;
415 BOOL
PSOAPServerResource::LoadHeaders( PHTTPRequest
& /* request */ ) // Information on this request.
420 BOOL
PSOAPServerResource::OnPOSTData( PHTTPRequest
& request
,
421 const PStringToString
& /*data*/)
423 PTRACE( 2, "PSOAPServerResource\tReceived post data, request: " << request
.entityBody
);
429 // Check for the SOAPAction header
430 PString
* pSOAPAction
= request
.inMIME
.GetAt( "SOAPAction" );
433 // If it's available check if we are expecting a special header value
434 if ( soapAction
== " " )
436 // A space means anything goes
437 ok
= OnSOAPRequest( request
.entityBody
, reply
);
441 // Check if the incoming header is the same as we expected
442 if ( *pSOAPAction
== soapAction
)
444 ok
= OnSOAPRequest( request
.entityBody
, reply
);
449 reply
= FormatFault( PSOAPMessage::Client
, "Incorrect SOAPAction in HTTP Header: " + *pSOAPAction
).AsString();
456 reply
= FormatFault( PSOAPMessage::Client
, "SOAPAction is missing in HTTP Header" ).AsString();
459 // If everything went OK, reply with ReturnCode 200 (OK)
461 request
.code
= PHTTP::RequestOK
;
463 // Reply with InternalServerError (500)
464 request
.code
= PHTTP::InternalServerError
;
466 // Set the correct content-type
467 request
.outMIME
.SetAt(PHTTP::ContentTypeTag
, "text/xml");
469 // Start constructing the response
470 PINDEX len
= reply
.GetLength();
471 request
.server
.StartResponse( request
.code
, request
.outMIME
, len
);
473 // Write the reply to the client
474 return request
.server
.Write( (const char* ) reply
, len
);
478 BOOL
PSOAPServerResource::OnSOAPRequest( const PString
& body
, PString
& reply
)
480 // Load the HTTP body into the SOAP (XML) parser
481 PSOAPMessage request
;
482 BOOL ok
= request
.Load( body
);
484 // If parsing the XML to SOAP failed reply with an error
487 reply
= FormatFault( PSOAPMessage::Client
, "XML error:" + request
.GetErrorString() ).AsString();
495 // Retrieve the method from the SOAP messsage
496 request
.GetMethod( method
, nameSpace
);
498 PTRACE( 3, "PSOAPServerResource\tReceived SOAP message for method " << method
);
500 return OnSOAPRequest( method
, request
, reply
);
503 BOOL
PSOAPServerResource::OnSOAPRequest( const PString
& methodName
,
504 PSOAPMessage
& request
,
509 // Find the method information
510 PINDEX pos
= methodList
.GetValuesIndex( methodName
);
512 if ( pos
== P_MAX_INDEX
)
514 reply
= FormatFault( PSOAPMessage::Client
, "Unknown method = " + methodName
).AsString();
518 PSOAPServerMethod
* methodInfo
= ( PSOAPServerMethod
* )methodList
.GetAt( pos
);
519 PNotifier notifier
= methodInfo
->methodFunc
;
521 methodMutex
.Signal();
523 // create a request/response container to be passed to the notifier function
524 PSOAPServerRequestResponse
p( request
);
531 reply
= p
.response
.AsString();
533 return p
.response
.GetFaultCode() == PSOAPMessage::NoFault
;
537 PSOAPMessage
PSOAPServerResource::FormatFault( PINDEX code
, const PString
& str
)
539 PTRACE(2, "PSOAPServerResource\trequest failed: " << str
);
543 PString faultCodeStr
= faultCodeToString( code
);
545 reply
.SetMethod( "Fault", "" );
547 reply
.AddParameter( "faultcode", "", faultCodeStr
);
548 reply
.AddParameter( "faultstring", "", str
);
560 PSOAPClient::PSOAPClient( const PURL
& _url
)
567 BOOL
PSOAPClient::MakeRequest( const PString
& method
, const PString
& nameSpace
)
569 PSOAPMessage
request( method
, nameSpace
);
570 PSOAPMessage response
;
572 return MakeRequest( request
, response
);
575 BOOL
PSOAPClient::MakeRequest( const PString
& method
, const PString
& nameSpace
, PSOAPMessage
& response
)
577 PSOAPMessage
request( method
, nameSpace
);
579 return MakeRequest( request
, response
);
582 BOOL
PSOAPClient::MakeRequest( PSOAPMessage
& request
, PSOAPMessage
& response
)
584 return PerformRequest( request
, response
);
587 BOOL
PSOAPClient::PerformRequest( PSOAPMessage
& request
, PSOAPMessage
& response
)
589 // create SOAP request
594 if ( !request
.Save( soapRequest
) )
597 txt
<< "Error creating request XML ("
598 << request
.GetErrorLine()
600 << request
.GetErrorString();
604 // End with a newline
607 PTRACE( 5, "SOAPClient\tOutgoing SOAP is " << soapRequest
);
611 PMIMEInfo sendMIME
, replyMIME
;
612 sendMIME
.SetAt( "Server", url
.GetHostName() );
613 sendMIME
.SetAt( PHTTP::ContentTypeTag
, "text/xml" );
614 sendMIME
.SetAt( "SOAPAction", soapAction
);
616 if(url
.GetUserName() != "") {
617 PStringStream SoapAuthToken
;
618 SoapAuthToken
<< url
.GetUserName() << ":" << url
.GetPassword();
619 sendMIME
.SetAt( "Authorization", PBase64::Encode(SoapAuthToken
) );
623 client
.SetReadTimeout( timeout
);
625 // Send the POST request to the server
626 BOOL ok
= client
.PostData( url
, sendMIME
, soapRequest
, replyMIME
);
628 // Find the length of the response
629 PINDEX contentLength
;
630 if ( replyMIME
.Contains( PHTTP::ContentLengthTag
) )
631 contentLength
= ( PINDEX
) replyMIME
[ PHTTP::ContentLengthTag
].AsUnsigned();
633 contentLength
= P_MAX_INDEX
;
637 // Retrieve the response
638 PString replyBody
= client
.ReadString( contentLength
);
640 PTRACE( 5, "PSOAP\tIncoming SOAP is " << replyBody
);
642 // Check if the server really gave us something
643 if ( !ok
|| replyBody
.IsEmpty() )
645 txt
<< "HTTP POST failed: "
646 << client
.GetLastResponseCode() << ' '
647 << client
.GetLastResponseInfo();
650 // Parse the response only if the response code from the server
651 // is either 500 (Internal server error) or 200 (RequestOK)
653 if ( ( client
.GetLastResponseCode() == PHTTP::RequestOK
) ||
654 ( client
.GetLastResponseCode() == PHTTP::InternalServerError
) )
656 if (!response
.Load(replyBody
))
658 txt
<< "Error parsing response XML ("
659 << response
.GetErrorLine()
661 << response
.GetErrorString();
663 PStringArray lines
= replyBody
.Lines();
664 for ( int offset
= -2; offset
<= 2; offset
++ ) {
665 int line
= response
.GetErrorLine() + offset
;
667 if ( line
>= 0 && line
< lines
.GetSize() )
668 txt
<< lines
[ ( PINDEX
) line
];
674 if ( ( client
.GetLastResponseCode() != PHTTP::RequestOK
) &&
675 ( client
.GetLastResponseCode() != PHTTP::InternalServerError
) &&
678 response
.SetFault( PSOAPMessage::Server
, txt
);
690 // End of File ////////////////////////////////////////////////////////////////