https://issues.shibboleth.net/jira/browse/CPPXT-47
[shibboleth/cpp-xmltooling.git] / xmltooling / soap / impl / SOAPClient.cpp
1 /*
2  *  Copyright 2001-2009 Internet2
3  * 
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 /**
18  * SOAPClient.cpp
19  * 
20  * Implements SOAP 1.1 messaging over a transport.
21  */
22
23 #include "internal.h"
24 #include "exceptions.h"
25 #include "logging.h"
26 #include "soap/HTTPSOAPTransport.h"
27 #include "soap/SOAP.h"
28 #include "soap/SOAPClient.h"
29 #include "util/XMLHelper.h"
30 #include "validation/ValidatorSuite.h"
31
32 #include <sstream>
33
34 using namespace soap11;
35 using namespace xmltooling::logging;
36 using namespace xmltooling;
37 using namespace xercesc;
38 using namespace std;
39
40 SOAPTransport::SOAPTransport()
41 {
42 }
43
44 SOAPTransport::~SOAPTransport()
45 {
46 }
47
48 bool SOAPTransport::setProviderOption(const char* provider, const char* option, const char* value)
49 {
50     return false;
51 }
52
53 HTTPSOAPTransport::HTTPSOAPTransport()
54 {
55 }
56
57 HTTPSOAPTransport::~HTTPSOAPTransport()
58 {
59 }
60
61 SOAPClient::SOAPClient(bool validate) : m_validate(validate), m_transport(NULL)
62 {
63 }
64
65 SOAPClient::~SOAPClient()
66 {
67     delete m_transport;
68 }
69
70 void SOAPClient::setValidating(bool validate)
71 {
72     m_validate = validate;
73 }
74
75 void SOAPClient::reset()
76 {
77     delete m_transport;
78     m_transport=NULL;
79 }
80
81 void SOAPTransport::send(istream* in)
82 {
83     if (!in)
84         throw IOException("SOAP transport does not support an empty request body.");
85     return send(*in);
86 }
87
88 void SOAPClient::send(const Envelope& env, const SOAPTransport::Address& addr)
89 {
90     // Prepare a transport object.
91     const char* pch = addr.m_endpoint ? strchr(addr.m_endpoint,':') : NULL;
92     if (!pch)
93         throw IOException("SOAP endpoint was not a URL.");
94     string scheme(addr.m_endpoint, pch-addr.m_endpoint);
95     m_transport = XMLToolingConfig::getConfig().SOAPTransportManager.newPlugin(scheme.c_str(), addr);
96     prepareTransport(*m_transport);
97     
98     Category& log = Category::getInstance(XMLTOOLING_LOGCAT".SOAPClient");
99     if (log.isDebugEnabled())
100         log.debugStream() << "marshalled envelope:\n" << env << logging::eol;
101     
102     // Serialize envelope.
103     stringstream s;
104     s << env;
105     
106     // Send to peer.
107     m_transport->send(s);
108 }
109
110 Envelope* SOAPClient::receive()
111 {
112     if (!m_transport)
113         throw IOException("No call is active.");
114     
115     // If we can get the stream, then the call is still active.
116     istream& out = m_transport->receive();
117     if (!out)
118         return NULL;    // nothing yet
119     
120     // Check content type.
121     string s = m_transport->getContentType();
122     if (s.find("text/xml") == string::npos)
123         throw IOException("Incorrect content type ($1) for SOAP response.", params(1,s.c_str() ? s.c_str() : "none"));
124     
125     // Parse and bind the document into an XMLObject.
126     DOMDocument* doc = (m_validate ? XMLToolingConfig::getConfig().getValidatingParser()
127         : XMLToolingConfig::getConfig().getParser()).parse(out); 
128     XercesJanitor<DOMDocument> janitor(doc);
129
130     Category& log = Category::getInstance(XMLTOOLING_LOGCAT".SOAPClient");
131     if (log.isDebugEnabled()) {
132 #ifdef XMLTOOLING_LOG4SHIB
133         log.debugStream() << "received XML:\n" << *(doc->getDocumentElement()) << logging::eol;
134 #else
135         string buf;
136         XMLHelper::serialize(doc->getDocumentElement(), buf);
137         log.debugStream() << "received XML:\n" << buf << logging::eol;
138 #endif
139     }
140     
141     auto_ptr<XMLObject> xmlObject(XMLObjectBuilder::buildOneFromElement(doc->getDocumentElement(), true));
142     janitor.release();
143
144     SchemaValidators.validate(xmlObject.get());
145
146     Envelope* env = dynamic_cast<Envelope*>(xmlObject.get());
147     if (!env)
148         throw IOException("Response was not a SOAP 1.1 Envelope.");
149
150     Body* body = env->getBody();
151     if (body && body->hasChildren()) {
152         //Check for a Fault.
153         const Fault* fault = dynamic_cast<Fault*>(body->getUnknownXMLObjects().front());
154         if (fault && handleFault(*fault))
155             throw IOException("SOAP client detected a Fault.");
156     }
157
158     xmlObject.release();
159     return env;
160 }
161
162 void SOAPClient::prepareTransport(SOAPTransport& transport)
163 {
164 }
165
166 bool SOAPClient::handleFault(const Fault& fault)
167 {
168     const xmltooling::QName* code = (fault.getFaultcode() ? fault.getFaultcode()->getCode() : NULL);
169     auto_ptr_char str((fault.getFaultstring() ? fault.getFaultstring()->getString() : NULL));
170     Category::getInstance(XMLTOOLING_LOGCAT".SOAPClient").error(
171         "SOAP client detected a Fault: (%s) (%s)",
172         (code ? code->toString().c_str() : "no code"),
173         (str.get() ? str.get() : "no message")
174         );
175     return true;
176 }