SSPCPP-616 - clean up concatenated string literals
[shibboleth/cpp-xmltooling.git] / xmltooling / soap / impl / SOAPClient.cpp
1 /**
2  * Licensed to the University Corporation for Advanced Internet
3  * Development, Inc. (UCAID) under one or more contributor license
4  * agreements. See the NOTICE file distributed with this work for
5  * additional information regarding copyright ownership.
6  *
7  * UCAID licenses this file to you under the Apache License,
8  * Version 2.0 (the "License"); you may not use this file except
9  * in compliance with the License. You may obtain a copy of the
10  * License at
11  *
12  * http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing,
15  * software distributed under the License is distributed on an
16  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
17  * either express or implied. See the License for the specific
18  * language governing permissions and limitations under the License.
19  */
20
21 /**
22  * SOAPClient.cpp
23  * 
24  * Implements SOAP 1.1 messaging over a transport.
25  */
26
27 #include "internal.h"
28 #include "exceptions.h"
29 #include "logging.h"
30 #include "soap/HTTPSOAPTransport.h"
31 #include "soap/OpenSSLSOAPTransport.h"
32 #include "soap/SOAP.h"
33 #include "soap/SOAPClient.h"
34 #include "util/XMLHelper.h"
35 #include "validation/ValidatorSuite.h"
36
37 #include <sstream>
38
39 using namespace soap11;
40 using namespace xmltooling::logging;
41 using namespace xmltooling;
42 using namespace xercesc;
43 using namespace std;
44
45 #if !defined(XMLTOOLING_NO_XMLSEC) && !defined(XMLTOOLING_LITE)
46 namespace xmltooling {
47     PluginManager<SOAPTransport,string,SOAPTransport::Address>::Factory CURLSOAPTransportFactory;
48 };
49 #endif
50
51 void xmltooling::registerSOAPTransports()
52 {
53 #if !defined(XMLTOOLING_NO_XMLSEC) && !defined(XMLTOOLING_LITE)
54     XMLToolingConfig& conf=XMLToolingConfig::getConfig();
55     conf.SOAPTransportManager.registerFactory("http", CURLSOAPTransportFactory);
56     conf.SOAPTransportManager.registerFactory("https", CURLSOAPTransportFactory);
57 #endif
58 }
59
60
61 #ifdef XMLTOOLING_NO_XMLSEC
62 void xmltooling::initSOAPTransports()
63 {
64 }
65
66 void xmltooling::termSOAPTransports()
67 {
68 }
69 #endif
70
71 SOAPTransport::SOAPTransport()
72 {
73 }
74
75 SOAPTransport::~SOAPTransport()
76 {
77 }
78
79 bool SOAPTransport::setProviderOption(const char* provider, const char* option, const char* value)
80 {
81     return false;
82 }
83
84 bool SOAPTransport::setCacheTag(string* cacheTag)
85 {
86     return false;
87 }
88
89 void SOAPTransport::send(istream* in)
90 {
91     if (!in)
92         throw IOException("SOAP transport does not support an empty request body.");
93     return send(*in);
94 }
95
96 long SOAPTransport::getStatusCode() const
97 {
98     return 0;
99 }
100
101 HTTPSOAPTransport::HTTPSOAPTransport()
102 {
103 }
104
105 HTTPSOAPTransport::~HTTPSOAPTransport()
106 {
107 }
108
109 bool HTTPSOAPTransport::followRedirects(bool follow, unsigned int maxRedirs)
110 {
111     return false;
112 }
113
114 #ifndef XMLTOOLING_NO_XMLSEC
115 OpenSSLSOAPTransport::OpenSSLSOAPTransport()
116 {
117 }
118
119 OpenSSLSOAPTransport::~OpenSSLSOAPTransport()
120 {
121 }
122 #endif
123
124 SOAPClient::SOAPClient(bool validate) : m_validate(validate), m_transport(nullptr)
125 {
126 }
127
128 SOAPClient::~SOAPClient()
129 {
130     delete m_transport;
131 }
132
133 void SOAPClient::setValidating(bool validate)
134 {
135     m_validate = validate;
136 }
137
138 void SOAPClient::reset()
139 {
140     delete m_transport;
141     m_transport=nullptr;
142 }
143
144 void SOAPClient::send(const Envelope& env, const SOAPTransport::Address& addr)
145 {
146     // Prepare a transport object.
147     const char* pch = addr.m_endpoint ? strchr(addr.m_endpoint,':') : nullptr;
148     if (!pch)
149         throw IOException("SOAP endpoint was not a URL.");
150     string scheme(addr.m_endpoint, pch-addr.m_endpoint);
151     m_transport = XMLToolingConfig::getConfig().SOAPTransportManager.newPlugin(scheme.c_str(), addr);
152     prepareTransport(*m_transport);
153     
154     Category& log = Category::getInstance(XMLTOOLING_LOGCAT ".SOAPClient");
155     if (log.isDebugEnabled())
156         log.debugStream() << "marshalled envelope:\n" << env << logging::eol;
157     
158     // Serialize envelope.
159     stringstream s;
160     s << env;
161     
162     // Send to peer.
163     m_transport->send(s);
164 }
165
166 Envelope* SOAPClient::receive()
167 {
168     if (!m_transport)
169         throw IOException("No call is active.");
170     
171     // If we can get the stream, then the call is still active.
172     istream& out = m_transport->receive();
173     if (!out)
174         return nullptr;    // nothing yet
175     
176     // Check content type.
177     string s = m_transport->getContentType();
178     if (s.find("text/xml") == string::npos)
179         throw IOException("Incorrect content type ($1) for SOAP response.", params(1,s.c_str() ? s.c_str() : "none"));
180     
181     // Parse and bind the document into an XMLObject.
182     DOMDocument* doc = (m_validate ? XMLToolingConfig::getConfig().getValidatingParser()
183         : XMLToolingConfig::getConfig().getParser()).parse(out); 
184     XercesJanitor<DOMDocument> janitor(doc);
185
186     Category& log = Category::getInstance(XMLTOOLING_LOGCAT ".SOAPClient");
187     if (log.isDebugEnabled()) {
188         string buf;
189         XMLHelper::serialize(doc->getDocumentElement(), buf);
190         log.debugStream() << "received XML:\n" << buf << logging::eol;
191     }
192     
193     auto_ptr<XMLObject> xmlObject(XMLObjectBuilder::buildOneFromElement(doc->getDocumentElement(), true));
194     janitor.release();
195
196     SchemaValidators.validate(xmlObject.get());
197
198     Envelope* env = dynamic_cast<Envelope*>(xmlObject.get());
199     if (!env)
200         throw IOException("Response was not a SOAP 1.1 Envelope.");
201
202     Body* body = env->getBody();
203     if (body && body->hasChildren()) {
204         //Check for a Fault.
205         const Fault* fault = dynamic_cast<Fault*>(body->getUnknownXMLObjects().front());
206         if (fault && handleFault(*fault))
207             throw IOException("SOAP client detected a Fault.");
208     }
209
210     xmlObject.release();
211     return env;
212 }
213
214 void SOAPClient::prepareTransport(SOAPTransport& transport)
215 {
216 }
217
218 bool SOAPClient::handleFault(const Fault& fault)
219 {
220     const xmltooling::QName* code = (fault.getFaultcode() ? fault.getFaultcode()->getCode() : nullptr);
221     auto_ptr_char str((fault.getFaultstring() ? fault.getFaultstring()->getString() : nullptr));
222     Category::getInstance(XMLTOOLING_LOGCAT ".SOAPClient").error(
223         "SOAP client detected a Fault: (%s) (%s)",
224         (code ? code->toString().c_str() : "no code"),
225         (str.get() ? str.get() : "no message")
226         );
227     return true;
228 }