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