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