1e17c2d0bee578b11a5834b2014682562741bc7c
[shibboleth/cpp-xmltooling.git] / xmltooling / soap / impl / SOAPClient.cpp
1 /*
2  *  Copyright 2001-2010 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 bool SOAPTransport::setCacheTag(string* cacheTag)
54 {
55     return false;
56 }
57
58 void SOAPTransport::send(istream* in)
59 {
60     if (!in)
61         throw IOException("SOAP transport does not support an empty request body.");
62     return send(*in);
63 }
64
65 long SOAPTransport::getStatusCode() const
66 {
67     return 0;
68 }
69
70 HTTPSOAPTransport::HTTPSOAPTransport()
71 {
72 }
73
74 HTTPSOAPTransport::~HTTPSOAPTransport()
75 {
76 }
77
78 bool HTTPSOAPTransport::followRedirects(bool follow, unsigned int maxRedirs)
79 {
80     return false;
81 }
82
83 SOAPClient::SOAPClient(bool validate) : m_validate(validate), m_transport(nullptr)
84 {
85 }
86
87 SOAPClient::~SOAPClient()
88 {
89     delete m_transport;
90 }
91
92 void SOAPClient::setValidating(bool validate)
93 {
94     m_validate = validate;
95 }
96
97 void SOAPClient::reset()
98 {
99     delete m_transport;
100     m_transport=nullptr;
101 }
102
103 void SOAPClient::send(const Envelope& env, const SOAPTransport::Address& addr)
104 {
105     // Prepare a transport object.
106     const char* pch = addr.m_endpoint ? strchr(addr.m_endpoint,':') : nullptr;
107     if (!pch)
108         throw IOException("SOAP endpoint was not a URL.");
109     string scheme(addr.m_endpoint, pch-addr.m_endpoint);
110     m_transport = XMLToolingConfig::getConfig().SOAPTransportManager.newPlugin(scheme.c_str(), addr);
111     prepareTransport(*m_transport);
112     
113     Category& log = Category::getInstance(XMLTOOLING_LOGCAT".SOAPClient");
114     if (log.isDebugEnabled())
115         log.debugStream() << "marshalled envelope:\n" << env << logging::eol;
116     
117     // Serialize envelope.
118     stringstream s;
119     s << env;
120     
121     // Send to peer.
122     m_transport->send(s);
123 }
124
125 Envelope* SOAPClient::receive()
126 {
127     if (!m_transport)
128         throw IOException("No call is active.");
129     
130     // If we can get the stream, then the call is still active.
131     istream& out = m_transport->receive();
132     if (!out)
133         return nullptr;    // nothing yet
134     
135     // Check content type.
136     string s = m_transport->getContentType();
137     if (s.find("text/xml") == string::npos)
138         throw IOException("Incorrect content type ($1) for SOAP response.", params(1,s.c_str() ? s.c_str() : "none"));
139     
140     // Parse and bind the document into an XMLObject.
141     DOMDocument* doc = (m_validate ? XMLToolingConfig::getConfig().getValidatingParser()
142         : XMLToolingConfig::getConfig().getParser()).parse(out); 
143     XercesJanitor<DOMDocument> janitor(doc);
144
145     Category& log = Category::getInstance(XMLTOOLING_LOGCAT".SOAPClient");
146     if (log.isDebugEnabled()) {
147 #ifdef XMLTOOLING_LOG4SHIB
148         log.debugStream() << "received XML:\n" << *(doc->getDocumentElement()) << logging::eol;
149 #else
150         string buf;
151         XMLHelper::serialize(doc->getDocumentElement(), buf);
152         log.debugStream() << "received XML:\n" << buf << logging::eol;
153 #endif
154     }
155     
156     auto_ptr<XMLObject> xmlObject(XMLObjectBuilder::buildOneFromElement(doc->getDocumentElement(), true));
157     janitor.release();
158
159     SchemaValidators.validate(xmlObject.get());
160
161     Envelope* env = dynamic_cast<Envelope*>(xmlObject.get());
162     if (!env)
163         throw IOException("Response was not a SOAP 1.1 Envelope.");
164
165     Body* body = env->getBody();
166     if (body && body->hasChildren()) {
167         //Check for a Fault.
168         const Fault* fault = dynamic_cast<Fault*>(body->getUnknownXMLObjects().front());
169         if (fault && handleFault(*fault))
170             throw IOException("SOAP client detected a Fault.");
171     }
172
173     xmlObject.release();
174     return env;
175 }
176
177 void SOAPClient::prepareTransport(SOAPTransport& transport)
178 {
179 }
180
181 bool SOAPClient::handleFault(const Fault& fault)
182 {
183     const xmltooling::QName* code = (fault.getFaultcode() ? fault.getFaultcode()->getCode() : nullptr);
184     auto_ptr_char str((fault.getFaultstring() ? fault.getFaultstring()->getString() : nullptr));
185     Category::getInstance(XMLTOOLING_LOGCAT".SOAPClient").error(
186         "SOAP client detected a Fault: (%s) (%s)",
187         (code ? code->toString().c_str() : "no code"),
188         (str.get() ? str.get() : "no message")
189         );
190     return true;
191 }