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