Imported Upstream version 2.4+dfsg
[shibboleth/sp.git] / shibsp / AbstractSPRequest.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  * AbstractSPRequest.cpp
19  *
20  * Abstract base for SPRequest implementations.
21  */
22
23 #include "internal.h"
24 #include "exceptions.h"
25 #include "AbstractSPRequest.h"
26 #include "Application.h"
27 #include "GSSRequest.h"
28 #include "ServiceProvider.h"
29 #include "SessionCache.h"
30 #include "util/CGIParser.h"
31
32 using namespace shibsp;
33 using namespace opensaml;
34 using namespace xmltooling;
35 using namespace std;
36
37 SPRequest::SPRequest()
38 {
39 }
40
41 SPRequest::~SPRequest()
42 {
43 }
44
45 string SPRequest::getSecureHeader(const char* name) const
46 {
47     return getHeader(name);
48 }
49
50 void SPRequest::setAuthType(const char* authtype)
51 {
52 }
53
54 #ifdef HAVE_GSSAPI
55 GSSRequest::GSSRequest()
56 {
57 }
58
59 GSSRequest::~GSSRequest()
60 {
61 }
62 #endif
63
64 AbstractSPRequest::AbstractSPRequest(const char* category)
65     : m_sp(nullptr), m_mapper(nullptr), m_app(nullptr), m_sessionTried(false), m_session(nullptr),
66         m_log(&Category::getInstance(category)), m_parser(nullptr)
67 {
68     m_sp=SPConfig::getConfig().getServiceProvider();
69     m_sp->lock();
70 }
71
72 AbstractSPRequest::~AbstractSPRequest()
73 {
74     if (m_session)
75         m_session->unlock();
76     if (m_mapper)
77         m_mapper->unlock();
78     if (m_sp)
79         m_sp->unlock();
80     delete m_parser;
81 }
82
83 const ServiceProvider& AbstractSPRequest::getServiceProvider() const
84 {
85     return *m_sp;
86 }
87
88 RequestMapper::Settings AbstractSPRequest::getRequestSettings() const
89 {
90     if (!m_mapper) {
91         // Map request to application and content settings.
92         m_mapper=m_sp->getRequestMapper();
93         m_mapper->lock();
94         m_settings = m_mapper->getSettings(*this);
95
96         if (reinterpret_cast<Category*>(m_log)->isDebugEnabled()) {
97             reinterpret_cast<Category*>(m_log)->debug(
98                 "mapped %s to %s", getRequestURL(), m_settings.first->getString("applicationId").second
99                 );
100         }
101     }
102     return m_settings;
103 }
104
105 const Application& AbstractSPRequest::getApplication() const
106 {
107     if (!m_app) {
108         // Now find the application from the URL settings
109         m_app=m_sp->getApplication(getRequestSettings().first->getString("applicationId").second);
110         if (!m_app)
111             throw ConfigurationException("Unable to map non-default applicationId to an ApplicationOverride, check configuration.");
112     }
113     return *m_app;
114 }
115
116 Session* AbstractSPRequest::getSession(bool checkTimeout, bool ignoreAddress, bool cache)
117 {
118     // Only attempt this once.
119     if (cache && m_sessionTried)
120         return m_session;
121     else if (cache)
122         m_sessionTried = true;
123
124     // Need address checking and timeout settings.
125     time_t timeout=3600;
126     if (checkTimeout || !ignoreAddress) {
127         const PropertySet* props=getApplication().getPropertySet("Sessions");
128         if (props) {
129             if (checkTimeout) {
130                 pair<bool,unsigned int> p=props->getUnsignedInt("timeout");
131                 if (p.first)
132                     timeout = p.second;
133             }
134             pair<bool,bool> pcheck=props->getBool("consistentAddress");
135             if (pcheck.first)
136                 ignoreAddress = !pcheck.second;
137         }
138     }
139
140     // The cache will either silently pass a session or nullptr back, or throw an exception out.
141     Session* session = getServiceProvider().getSessionCache()->find(
142         getApplication(), *this, ignoreAddress ? nullptr : getRemoteAddr().c_str(), checkTimeout ? &timeout : nullptr
143         );
144     if (cache)
145         m_session = session;
146     return session;
147 }
148
149 static char _x2c(const char *what)
150 {
151     register char digit;
152
153     digit = (what[0] >= 'A' ? ((what[0] & 0xdf) - 'A')+10 : (what[0] - '0'));
154     digit *= 16;
155     digit += (what[1] >= 'A' ? ((what[1] & 0xdf) - 'A')+10 : (what[1] - '0'));
156     return(digit);
157 }
158
159 void AbstractSPRequest::setRequestURI(const char* uri)
160 {
161     // Fix for bug 574, secadv 20061002
162     // Unescape URI up to query string delimiter by looking for %XX escapes.
163     // Adapted from Apache's util.c, ap_unescape_url function.
164     if (uri) {
165         while (*uri) {
166             if (*uri == '?') {
167                 m_uri += uri;
168                 break;
169             }
170             else if (*uri != '%') {
171                 m_uri += *uri;
172             }
173             else {
174                 ++uri;
175                 if (!isxdigit(*uri) || !isxdigit(*(uri+1)))
176                     throw ConfigurationException("Bad request, contained unsupported encoded characters.");
177                 m_uri += _x2c(uri);
178                 ++uri;
179             }
180             ++uri;
181         }
182     }
183 }
184
185 const char* AbstractSPRequest::getRequestURI() const
186 {
187     return m_uri.c_str();
188 }
189
190 const char* AbstractSPRequest::getRequestURL() const
191 {
192     if (m_url.empty()) {
193         // Compute the full target URL
194         int port = getPort();
195         const char* scheme = getScheme();
196         m_url = string(scheme) + "://" + getHostname();
197         if ((!strcmp(scheme,"http") && port!=80) || (!strcmp(scheme,"https") && port!=443)) {
198             ostringstream portstr;
199             portstr << port;
200             m_url += ":" + portstr.str();
201         }
202         m_url += m_uri;
203     }
204     return m_url.c_str();
205 }
206
207 string AbstractSPRequest::getRemoteAddr() const
208 {
209     pair<bool,const char*> addr = getRequestSettings().first->getString("REMOTE_ADDR");
210     return addr.first ? getHeader(addr.second) : "";
211 }
212
213 const char* AbstractSPRequest::getParameter(const char* name) const
214 {
215     if (!m_parser)
216         m_parser=new CGIParser(*this);
217
218     pair<CGIParser::walker,CGIParser::walker> bounds=m_parser->getParameters(name);
219     return (bounds.first==bounds.second) ? nullptr : bounds.first->second;
220 }
221
222 vector<const char*>::size_type AbstractSPRequest::getParameters(const char* name, vector<const char*>& values) const
223 {
224     if (!m_parser)
225         m_parser=new CGIParser(*this);
226
227     pair<CGIParser::walker,CGIParser::walker> bounds=m_parser->getParameters(name);
228     while (bounds.first!=bounds.second) {
229         values.push_back(bounds.first->second);
230         ++bounds.first;
231     }
232     return values.size();
233 }
234
235 const char* AbstractSPRequest::getHandlerURL(const char* resource) const
236 {
237     if (!resource)
238         resource = getRequestURL();
239
240     if (!m_handlerURL.empty() && resource && !strcmp(getRequestURL(),resource))
241         return m_handlerURL.c_str();
242
243     string stackresource;
244     if (resource && *resource == '/') {
245         // Compute a URL to the root of the site and point resource at constructed string.
246         int port = getPort();
247         const char* scheme = getScheme();
248         stackresource = string(scheme) + "://" + getHostname();
249         if ((!strcmp(scheme,"http") && port!=80) || (!strcmp(scheme,"https") && port!=443)) {
250             ostringstream portstr;
251             portstr << port;
252             stackresource += ":" + portstr.str();
253         }
254         stackresource += resource;
255         resource = stackresource.c_str();
256     }
257
258 #ifdef HAVE_STRCASECMP
259     if (!resource || (strncasecmp(resource,"http://",7) && strncasecmp(resource,"https://",8)))
260 #else
261     if (!resource || (strnicmp(resource,"http://",7) && strnicmp(resource,"https://",8)))
262 #endif
263         throw ConfigurationException("Target resource was not an absolute URL.");
264
265     bool ssl_only=true;
266     const char* handler=nullptr;
267     const PropertySet* props=getApplication().getPropertySet("Sessions");
268     if (props) {
269         pair<bool,bool> p=props->getBool("handlerSSL");
270         if (p.first)
271             ssl_only=p.second;
272         pair<bool,const char*> p2=props->getString("handlerURL");
273         if (p2.first)
274             handler=p2.second;
275     }
276
277     if (!handler) {
278         handler = "/Shibboleth.sso";
279     }
280     else if (*handler!='/' && strncmp(handler,"http:",5) && strncmp(handler,"https:",6)) {
281         throw ConfigurationException(
282             "Invalid handlerURL property ($1) in <Sessions> element for Application ($2)",
283             params(2, handler ? handler : "null", m_app->getId())
284             );
285     }
286
287     // The "handlerURL" property can be in one of three formats:
288     //
289     // 1) a full URI:       http://host/foo/bar
290     // 2) a hostless URI:   http:///foo/bar
291     // 3) a relative path:  /foo/bar
292     //
293     // #  Protocol  Host        Path
294     // 1  handler   handler     handler
295     // 2  handler   resource    handler
296     // 3  resource  resource    handler
297     //
298     // note: if ssl_only is true, make sure the protocol is https
299
300     const char* path = nullptr;
301
302     // Decide whether to use the handler or the resource for the "protocol"
303     const char* prot;
304     if (*handler != '/') {
305         prot = handler;
306     }
307     else {
308         prot = resource;
309         path = handler;
310     }
311
312     // break apart the "protocol" string into protocol, host, and "the rest"
313     const char* colon=strchr(prot,':');
314     colon += 3;
315     const char* slash=strchr(colon,'/');
316     if (!path)
317         path = slash;
318
319     // Compute the actual protocol and store in member.
320     if (ssl_only)
321         m_handlerURL.assign("https://");
322     else
323         m_handlerURL.assign(prot, colon-prot);
324
325     // create the "host" from either the colon/slash or from the target string
326     // If prot == handler then we're in either #1 or #2, else #3.
327     // If slash == colon then we're in #2.
328     if (prot != handler || slash == colon) {
329         colon = strchr(resource, ':');
330         colon += 3;      // Get past the ://
331         slash = strchr(colon, '/');
332     }
333     string host(colon, (slash ? slash-colon : strlen(colon)));
334
335     // Build the handler URL
336     m_handlerURL += host + path;
337     return m_handlerURL.c_str();
338 }
339
340 void AbstractSPRequest::log(SPLogLevel level, const std::string& msg) const
341 {
342     reinterpret_cast<Category*>(m_log)->log(
343         (level == SPDebug ? Priority::DEBUG :
344         (level == SPInfo ? Priority::INFO :
345         (level == SPWarn ? Priority::WARN :
346         (level == SPError ? Priority::ERROR : Priority::CRIT)))),
347         msg
348         );
349 }
350
351 bool AbstractSPRequest::isPriorityEnabled(SPLogLevel level) const
352 {
353     return reinterpret_cast<Category*>(m_log)->isPriorityEnabled(
354         (level == SPDebug ? Priority::DEBUG :
355         (level == SPInfo ? Priority::INFO :
356         (level == SPWarn ? Priority::WARN :
357         (level == SPError ? Priority::ERROR : Priority::CRIT))))
358         );
359 }