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