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.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
55 #pragma implementation "psoap.h"
64 #include <ptclib/psoap.h>
74 PSOAPMessage::PSOAPMessage( int options
) :
78 faultCode( PSOAPMessage::NoFault
)
82 PSOAPMessage::PSOAPMessage( const PString
& method
, const PString
& nameSpace
) :
83 PXML( PXMLParser::Indent
+ PXMLParser::NewLineAfterElement
),
86 faultCode( PSOAPMessage::NoFault
)
88 SetMethod( method
, nameSpace
);
93 void PSOAPMessage::SetMethod( const PString
& name
, const PString
& nameSpace
)
95 PXMLElement
* rtElement
= 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
)
143 PXMLElement
* rtElement
= GetRootElement();
145 PXMLElement
* pParameter
= new PXMLElement( rtElement
, name
);
146 PXMLData
* pParameterData
= new PXMLData( pParameter
, value
);
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
)
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
;
182 strm
<< "<?xml version=\"" << ver
<< "\" encoding=\"" << enc
<< "\"";
185 strm
<< " standalone=\"no\"";
188 strm
<< " standalone=\"yes\"";
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
;
214 PString
faultCodeToString( PINDEX faultCode
)
216 PString faultCodeStr
;
219 case PSOAPMessage::VersionMismatch
:
220 faultCodeStr
= "VersionMisMatch";
222 case PSOAPMessage::MustUnderstand
:
223 faultCodeStr
= "MustUnderstand";
225 case PSOAPMessage::Client
:
226 faultCodeStr
= "Client";
228 case PSOAPMessage::Server
:
229 faultCodeStr
= "Server";
232 // Default it's the server's fault. Can't blame it on the customer, because he/she is king ;-)
233 faultCodeStr
= "Server";
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
);
264 if ( pElement
->GetAttribute( "xsi:type") == "xsd:string" )
266 value
= pElement
->GetData();
274 BOOL
PSOAPMessage::GetParameter( const PString
& name
, int & value
)
276 PXMLElement
* pElement
= GetParameter( name
);
280 if ( pElement
->GetAttribute( "xsi:type") == "xsd:int" )
282 value
= pElement
->GetData().AsInteger();
290 PXMLElement
* PSOAPMessage::GetParameter( const PString
& name
)
294 return pSOAPMethod
->GetElement( name
, 0 );
302 BOOL
PSOAPMessage::Load( const PString
& str
)
304 if ( !PXML::Load( str
) )
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() ;
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
];
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();
350 void PSOAPMessage::SetFault( PINDEX code
, const PString
& text
)
355 PString faultCodeStr
= faultCodeToString( code
);
357 SetMethod( "Fault", "" );
359 AddParameter( "faultcode", "", faultCodeStr
);
360 AddParameter( "faultstring", "", text
);
373 PSOAPServerResource::PSOAPServerResource()
374 : PHTTPResource( DEFAULT_SOAP_URL
),
379 PSOAPServerResource::PSOAPServerResource(
380 const PHTTPAuthority
& auth
) // Authorisation for the resource.
381 : PHTTPResource( DEFAULT_SOAP_URL
, auth
),
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
);
414 methodInfo
= new PSOAPServerMethod( methodName
);
415 methodList
.Append( methodInfo
);
419 methodInfo
->methodFunc
= func
;
424 BOOL
PSOAPServerResource::LoadHeaders( PHTTPRequest
& /* request */ ) // Information on this request.
429 BOOL
PSOAPServerResource::OnPOSTData( PHTTPRequest
& request
,
430 const PStringToString
& /*data*/)
432 PTRACE( 2, "PSOAPServerResource\tReceived post data, request: " << request
.entityBody
);
438 // Check for the SOAPAction header
439 PString
* pSOAPAction
= request
.inMIME
.GetAt( "SOAPAction" );
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
);
450 // Check if the incoming header is the same as we expected
451 if ( *pSOAPAction
== soapAction
)
453 ok
= OnSOAPRequest( request
.entityBody
, reply
);
458 reply
= FormatFault( PSOAPMessage::Client
, "Incorrect SOAPAction in HTTP Header: " + *pSOAPAction
).AsString();
465 reply
= FormatFault( PSOAPMessage::Client
, "SOAPAction is missing in HTTP Header" ).AsString();
468 // If everything went OK, reply with ReturnCode 200 (OK)
470 request
.code
= PHTTP::RequestOK
;
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
496 reply
= FormatFault( PSOAPMessage::Client
, "XML error:" + request
.GetErrorString() ).AsString();
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
,
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();
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
);
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
);
552 PString faultCodeStr
= faultCodeToString( code
);
554 reply
.SetMethod( "Fault", "" );
556 reply
.AddParameter( "faultcode", "", faultCodeStr
);
557 reply
.AddParameter( "faultstring", "", str
);
569 PSOAPClient::PSOAPClient( const PURL
& _url
)
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
603 if ( !request
.Save( soapRequest
) )
606 txt
<< "Error creating request XML ("
607 << request
.GetErrorLine()
609 << request
.GetErrorString();
613 // End with a newline
616 PTRACE( 5, "SOAPClient\tOutgoing SOAP is " << soapRequest
);
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
) );
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();
642 contentLength
= P_MAX_INDEX
;
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()
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
) &&
687 response
.SetFault( PSOAPMessage::Server
, txt
);
699 // End of File ////////////////////////////////////////////////////////////////