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