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