Moved CGI parsing to OS, add handler base for remoting HTTP req/resp data.
[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 #include <log4cpp/Category.hh>
30 #include <saml/util/CGIParser.h>
31
32 using namespace shibsp;
33 using namespace opensaml;
34 using namespace xmltooling;
35 using namespace log4cpp;
36 using namespace std;
37
38 AbstractSPRequest::AbstractSPRequest()
39     : m_sp(NULL), m_mapper(NULL), m_app(NULL), m_sessionTried(false), m_session(NULL),
40         m_log(&Category::getInstance(SHIBSP_LOGCAT".SPRequest")), m_parser(NULL)
41 {
42     m_sp=SPConfig::getConfig().getServiceProvider();
43     m_sp->lock();
44 }
45
46 AbstractSPRequest::~AbstractSPRequest()
47 {
48     if (m_session)
49         m_session->unlock();
50     if (m_mapper)
51         m_mapper->unlock();
52     if (m_sp)
53         m_sp->unlock();
54     delete m_parser;
55 }
56
57 RequestMapper::Settings AbstractSPRequest::getRequestSettings() const
58 {
59     if (m_mapper)
60         return m_settings;
61
62     // Map request to application and content settings.
63     m_mapper=m_sp->getRequestMapper();
64     m_mapper->lock();
65     return m_settings = m_mapper->getSettings(*this);
66
67 }
68
69 const Application& AbstractSPRequest::getApplication() const
70 {
71     if (!m_app) {
72         // Now find the application from the URL settings
73         m_app=m_sp->getApplication(getRequestSettings().first->getString("applicationId").second);
74         if (!m_app)
75             throw ConfigurationException("Unable to map request to application settings, check configuration.");
76     }    
77     return *m_app;
78 }
79
80 Session* AbstractSPRequest::getSession() const
81 {
82     // Only attempt this once.
83     if (m_sessionTried)
84         return m_session;
85     m_sessionTried = true;
86
87     // Get session ID from cookie.
88     const Application& app = getApplication();
89     pair<string,const char*> shib_cookie = app.getCookieNameProps("_shibsession_");
90     const char* session_id = getCookie(shib_cookie.first.c_str());
91     if (!session_id || !*session_id)
92         return NULL;
93
94     // Need address checking and timeout settings.
95     int timeout=0;
96     bool consistent=true;
97     const PropertySet* props=app.getPropertySet("Sessions");
98     if (props) {
99         pair<bool,unsigned int> p=props->getUnsignedInt("timeout");
100         if (p.first)
101             timeout = p.second;
102         pair<bool,bool> pcheck=props->getBool("consistentAddress");
103         if (pcheck.first)
104             consistent = pcheck.second;
105     }
106
107     // The cache will either silently pass a session or NULL back, or throw an exception out.
108     return m_session = getServiceProvider().getSessionCache()->find(
109         session_id, app, consistent ? getRemoteAddr().c_str() : NULL, timeout
110         );
111 }
112
113 const char* AbstractSPRequest::getRequestURL() const {
114     if (m_url.empty()) {
115         // Compute the full target URL
116         int port = getPort();
117         const char* scheme = getScheme();
118         m_url = string(scheme) + "://" + getHostname();
119         if ((!strcmp(scheme,"http") && port!=80) || (!strcmp(scheme,"https") && port!=443)) { 
120             ostringstream portstr;
121             portstr << port;
122             m_url += ":" + portstr.str();
123         }
124         scheme = getRequestURI();
125         if (scheme)
126             m_url += scheme;
127     }
128     return m_url.c_str();
129 }
130
131 const char* AbstractSPRequest::getParameter(const char* name) const
132 {
133     if (!m_parser)
134         m_parser=new CGIParser(*this);
135     
136     pair<CGIParser::walker,CGIParser::walker> bounds=m_parser->getParameters(name);
137     return (bounds.first==bounds.second) ? NULL : bounds.first->second;
138 }
139
140 vector<const char*>::size_type AbstractSPRequest::getParameters(const char* name, vector<const char*>& values) const
141 {
142     if (!m_parser)
143         m_parser=new CGIParser(*this);
144
145     pair<CGIParser::walker,CGIParser::walker> bounds=m_parser->getParameters(name);
146     while (bounds.first!=bounds.second) {
147         values.push_back(bounds.first->second);
148         ++bounds.first;
149     }
150     return values.size();
151 }
152
153 const char* AbstractSPRequest::getCookie(const char* name) const
154 {
155     if (m_cookieMap.empty()) {
156         string cookies=getHeader("Cookie");
157
158         string::size_type pos=0,cname,namelen,val,vallen;
159         while (pos !=string::npos && pos < cookies.length()) {
160             while (isspace(cookies[pos])) pos++;
161             cname=pos;
162             pos=cookies.find_first_of("=",pos);
163             if (pos == string::npos)
164                 break;
165             namelen=pos-cname;
166             pos++;
167             if (pos==cookies.length())
168                 break;
169             val=pos;
170             pos=cookies.find_first_of(";",pos);
171             if (pos != string::npos) {
172                 vallen=pos-val;
173                 pos++;
174                 m_cookieMap.insert(make_pair(cookies.substr(cname,namelen),cookies.substr(val,vallen)));
175             }
176             else
177                 m_cookieMap.insert(make_pair(cookies.substr(cname,namelen),cookies.substr(val)));
178         }
179     }
180     map<string,string>::const_iterator lookup=m_cookieMap.find(name);
181     return (lookup==m_cookieMap.end()) ? NULL : lookup->second.c_str();
182 }
183
184 const char* AbstractSPRequest::getHandlerURL(const char* resource) const
185 {
186     if (!m_handlerURL.empty() && resource && !strcmp(getRequestURL(),resource))
187         return m_handlerURL.c_str();
188         
189 #ifdef HAVE_STRCASECMP
190     if (!resource || (strncasecmp(resource,"http://",7) && strncasecmp(resource,"https://",8)))
191 #else
192     if (!resource || (strnicmp(resource,"http://",7) && strnicmp(resource,"https://",8)))
193 #endif
194         throw ConfigurationException("Target resource was not an absolute URL.");
195
196     bool ssl_only=false;
197     const char* handler=NULL;
198     const PropertySet* props=m_app->getPropertySet("Sessions");
199     if (props) {
200         pair<bool,bool> p=props->getBool("handlerSSL");
201         if (p.first)
202             ssl_only=p.second;
203         pair<bool,const char*> p2=props->getString("handlerURL");
204         if (p2.first)
205             handler=p2.second;
206     }
207     
208     // Should never happen...
209     if (!handler || (*handler!='/' && strncmp(handler,"http:",5) && strncmp(handler,"https:",6)))
210         throw ConfigurationException(
211             "Invalid handlerURL property ($1) in Application ($2)",
212             params(2, handler ? handler : "null", m_app->getId())
213             );
214
215     // The "handlerURL" property can be in one of three formats:
216     //
217     // 1) a full URI:       http://host/foo/bar
218     // 2) a hostless URI:   http:///foo/bar
219     // 3) a relative path:  /foo/bar
220     //
221     // #  Protocol  Host        Path
222     // 1  handler   handler     handler
223     // 2  handler   resource    handler
224     // 3  resource  resource    handler
225     //
226     // note: if ssl_only is true, make sure the protocol is https
227
228     const char* path = NULL;
229
230     // Decide whether to use the handler or the resource for the "protocol"
231     const char* prot;
232     if (*handler != '/') {
233         prot = handler;
234     }
235     else {
236         prot = resource;
237         path = handler;
238     }
239
240     // break apart the "protocol" string into protocol, host, and "the rest"
241     const char* colon=strchr(prot,':');
242     colon += 3;
243     const char* slash=strchr(colon,'/');
244     if (!path)
245         path = slash;
246
247     // Compute the actual protocol and store in member.
248     if (ssl_only)
249         m_handlerURL.assign("https://");
250     else
251         m_handlerURL.assign(prot, colon-prot);
252
253     // create the "host" from either the colon/slash or from the target string
254     // If prot == handler then we're in either #1 or #2, else #3.
255     // If slash == colon then we're in #2.
256     if (prot != handler || slash == colon) {
257         colon = strchr(resource, ':');
258         colon += 3;      // Get past the ://
259         slash = strchr(colon, '/');
260     }
261     string host(colon, (slash ? slash-colon : strlen(colon)));
262
263     // Build the handler URL
264     m_handlerURL += host + path;
265     return m_handlerURL.c_str();
266 }
267
268 void AbstractSPRequest::log(SPLogLevel level, const std::string& msg) const
269 {
270     reinterpret_cast<Category*>(m_log)->log(
271         (level == SPDebug ? log4cpp::Priority::DEBUG :
272         (level == SPInfo ? log4cpp::Priority::INFO :
273         (level == SPWarn ? log4cpp::Priority::WARN :
274         (level == SPError ? log4cpp::Priority::ERROR : log4cpp::Priority::CRIT)))),
275         msg
276         );
277 }
278
279 bool AbstractSPRequest::isPriorityEnabled(SPLogLevel level) const
280 {
281     return reinterpret_cast<Category*>(m_log)->isPriorityEnabled(
282         (level == SPDebug ? log4cpp::Priority::DEBUG :
283         (level == SPInfo ? log4cpp::Priority::INFO :
284         (level == SPWarn ? log4cpp::Priority::WARN :
285         (level == SPError ? log4cpp::Priority::ERROR : log4cpp::Priority::CRIT))))
286         );
287 }