103c2b3d88e687caa389653ff1c2e65617a037c4
[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()
35     : m_sp(NULL), m_mapper(NULL), m_app(NULL), m_sessionTried(false), m_session(NULL),
36         m_log(&Category::getInstance(SHIBSP_LOGCAT".SPRequest")), 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 const char* AbstractSPRequest::getRequestURL() const {
117     if (m_url.empty()) {
118         // Compute the full target URL
119         int port = getPort();
120         const char* scheme = getScheme();
121         m_url = string(scheme) + "://" + getHostname();
122         if ((!strcmp(scheme,"http") && port!=80) || (!strcmp(scheme,"https") && port!=443)) { 
123             ostringstream portstr;
124             portstr << port;
125             m_url += ":" + portstr.str();
126         }
127         scheme = getRequestURI();
128         if (scheme)
129             m_url += scheme;
130     }
131     return m_url.c_str();
132 }
133
134 const char* AbstractSPRequest::getParameter(const char* name) const
135 {
136     if (!m_parser)
137         m_parser=new CGIParser(*this);
138     
139     pair<CGIParser::walker,CGIParser::walker> bounds=m_parser->getParameters(name);
140     return (bounds.first==bounds.second) ? NULL : bounds.first->second;
141 }
142
143 vector<const char*>::size_type AbstractSPRequest::getParameters(const char* name, vector<const char*>& values) const
144 {
145     if (!m_parser)
146         m_parser=new CGIParser(*this);
147
148     pair<CGIParser::walker,CGIParser::walker> bounds=m_parser->getParameters(name);
149     while (bounds.first!=bounds.second) {
150         values.push_back(bounds.first->second);
151         ++bounds.first;
152     }
153     return values.size();
154 }
155
156 const char* AbstractSPRequest::getCookie(const char* name) const
157 {
158     if (m_cookieMap.empty()) {
159         string cookies=getHeader("Cookie");
160
161         string::size_type pos=0,cname,namelen,val,vallen;
162         while (pos !=string::npos && pos < cookies.length()) {
163             while (isspace(cookies[pos])) pos++;
164             cname=pos;
165             pos=cookies.find_first_of("=",pos);
166             if (pos == string::npos)
167                 break;
168             namelen=pos-cname;
169             pos++;
170             if (pos==cookies.length())
171                 break;
172             val=pos;
173             pos=cookies.find_first_of(";",pos);
174             if (pos != string::npos) {
175                 vallen=pos-val;
176                 pos++;
177                 m_cookieMap.insert(make_pair(cookies.substr(cname,namelen),cookies.substr(val,vallen)));
178             }
179             else
180                 m_cookieMap.insert(make_pair(cookies.substr(cname,namelen),cookies.substr(val)));
181         }
182     }
183     map<string,string>::const_iterator lookup=m_cookieMap.find(name);
184     return (lookup==m_cookieMap.end()) ? NULL : lookup->second.c_str();
185 }
186
187 const char* AbstractSPRequest::getHandlerURL(const char* resource) const
188 {
189     if (!resource)
190         resource = getRequestURL();
191
192     if (!m_handlerURL.empty() && resource && !strcmp(getRequestURL(),resource))
193         return m_handlerURL.c_str();
194         
195 #ifdef HAVE_STRCASECMP
196     if (!resource || (strncasecmp(resource,"http://",7) && strncasecmp(resource,"https://",8)))
197 #else
198     if (!resource || (strnicmp(resource,"http://",7) && strnicmp(resource,"https://",8)))
199 #endif
200         throw ConfigurationException("Target resource was not an absolute URL.");
201
202     bool ssl_only=false;
203     const char* handler=NULL;
204     const PropertySet* props=m_app->getPropertySet("Sessions");
205     if (props) {
206         pair<bool,bool> p=props->getBool("handlerSSL");
207         if (p.first)
208             ssl_only=p.second;
209         pair<bool,const char*> p2=props->getString("handlerURL");
210         if (p2.first)
211             handler=p2.second;
212     }
213     
214     // Should never happen...
215     if (!handler || (*handler!='/' && strncmp(handler,"http:",5) && strncmp(handler,"https:",6)))
216         throw ConfigurationException(
217             "Invalid handlerURL property ($1) in Application ($2)",
218             params(2, handler ? handler : "null", m_app->getId())
219             );
220
221     // The "handlerURL" property can be in one of three formats:
222     //
223     // 1) a full URI:       http://host/foo/bar
224     // 2) a hostless URI:   http:///foo/bar
225     // 3) a relative path:  /foo/bar
226     //
227     // #  Protocol  Host        Path
228     // 1  handler   handler     handler
229     // 2  handler   resource    handler
230     // 3  resource  resource    handler
231     //
232     // note: if ssl_only is true, make sure the protocol is https
233
234     const char* path = NULL;
235
236     // Decide whether to use the handler or the resource for the "protocol"
237     const char* prot;
238     if (*handler != '/') {
239         prot = handler;
240     }
241     else {
242         prot = resource;
243         path = handler;
244     }
245
246     // break apart the "protocol" string into protocol, host, and "the rest"
247     const char* colon=strchr(prot,':');
248     colon += 3;
249     const char* slash=strchr(colon,'/');
250     if (!path)
251         path = slash;
252
253     // Compute the actual protocol and store in member.
254     if (ssl_only)
255         m_handlerURL.assign("https://");
256     else
257         m_handlerURL.assign(prot, colon-prot);
258
259     // create the "host" from either the colon/slash or from the target string
260     // If prot == handler then we're in either #1 or #2, else #3.
261     // If slash == colon then we're in #2.
262     if (prot != handler || slash == colon) {
263         colon = strchr(resource, ':');
264         colon += 3;      // Get past the ://
265         slash = strchr(colon, '/');
266     }
267     string host(colon, (slash ? slash-colon : strlen(colon)));
268
269     // Build the handler URL
270     m_handlerURL += host + path;
271     return m_handlerURL.c_str();
272 }
273
274 void AbstractSPRequest::log(SPLogLevel level, const std::string& msg) const
275 {
276     reinterpret_cast<Category*>(m_log)->log(
277         (level == SPDebug ? Priority::DEBUG :
278         (level == SPInfo ? Priority::INFO :
279         (level == SPWarn ? Priority::WARN :
280         (level == SPError ? Priority::ERROR : Priority::CRIT)))),
281         msg
282         );
283 }
284
285 bool AbstractSPRequest::isPriorityEnabled(SPLogLevel level) const
286 {
287     return reinterpret_cast<Category*>(m_log)->isPriorityEnabled(
288         (level == SPDebug ? Priority::DEBUG :
289         (level == SPInfo ? Priority::INFO :
290         (level == SPWarn ? Priority::WARN :
291         (level == SPError ? Priority::ERROR : Priority::CRIT))))
292         );
293 }