Port up URI sanitizer from branch.
[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()
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 static char _x2c(const char *what)\r
117 {\r
118     register char digit;\r
119 \r
120     digit = (what[0] >= 'A' ? ((what[0] & 0xdf) - 'A')+10 : (what[0] - '0'));\r
121     digit *= 16;\r
122     digit += (what[1] >= 'A' ? ((what[1] & 0xdf) - 'A')+10 : (what[1] - '0'));\r
123     return(digit);\r
124 }\r
125
126 void AbstractSPRequest::setRequestURI(const char* uri)
127 {
128     // Fix for bug 574, secadv 20061002\r
129     // Unescape URI up to query string delimiter by looking for %XX escapes.\r
130     // Adapted from Apache's util.c, ap_unescape_url function.\r
131     if (uri) {\r
132         while (*uri) {\r
133             if (*uri == '?') {\r
134                 m_uri += uri;\r
135                 break;\r
136             }\r
137             else if (*uri == ';') {\r
138                 // If this is Java being stupid, skip everything up to the query string, if any.\r
139                 if (!strncmp(uri, ";jsessionid=", 12)) {\r
140                     if (uri = strchr(uri, '?'))\r
141                         m_uri += uri;\r
142                     break;\r
143                 }\r
144                 else {\r
145                     m_uri += *uri;\r
146                 }\r
147             }\r
148             else if (*uri != '%') {\r
149                 m_uri += *uri;\r
150             }\r
151             else {\r
152                 ++uri;\r
153                 if (!isxdigit(*uri) || !isxdigit(*(uri+1)))\r
154                     throw ConfigurationException("Bad request, contained unsupported encoded characters.");\r
155                 m_uri += _x2c(uri);\r
156                 ++uri;\r
157             }\r
158             ++uri;\r
159         }\r
160     }\r
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::getCookie(const char* name) const
203 {
204     if (m_cookieMap.empty()) {
205         string cookies=getHeader("Cookie");
206
207         string::size_type pos=0,cname,namelen,val,vallen;
208         while (pos !=string::npos && pos < cookies.length()) {
209             while (isspace(cookies[pos])) pos++;
210             cname=pos;
211             pos=cookies.find_first_of("=",pos);
212             if (pos == string::npos)
213                 break;
214             namelen=pos-cname;
215             pos++;
216             if (pos==cookies.length())
217                 break;
218             val=pos;
219             pos=cookies.find_first_of(";",pos);
220             if (pos != string::npos) {
221                 vallen=pos-val;
222                 pos++;
223                 m_cookieMap.insert(make_pair(cookies.substr(cname,namelen),cookies.substr(val,vallen)));
224             }
225             else
226                 m_cookieMap.insert(make_pair(cookies.substr(cname,namelen),cookies.substr(val)));
227         }
228     }
229     map<string,string>::const_iterator lookup=m_cookieMap.find(name);
230     return (lookup==m_cookieMap.end()) ? NULL : lookup->second.c_str();
231 }
232
233 const char* AbstractSPRequest::getHandlerURL(const char* resource) const
234 {
235     if (!resource)
236         resource = getRequestURL();
237
238     if (!m_handlerURL.empty() && resource && !strcmp(getRequestURL(),resource))
239         return m_handlerURL.c_str();
240         
241 #ifdef HAVE_STRCASECMP
242     if (!resource || (strncasecmp(resource,"http://",7) && strncasecmp(resource,"https://",8)))
243 #else
244     if (!resource || (strnicmp(resource,"http://",7) && strnicmp(resource,"https://",8)))
245 #endif
246         throw ConfigurationException("Target resource was not an absolute URL.");
247
248     bool ssl_only=false;
249     const char* handler=NULL;
250     const PropertySet* props=m_app->getPropertySet("Sessions");
251     if (props) {
252         pair<bool,bool> p=props->getBool("handlerSSL");
253         if (p.first)
254             ssl_only=p.second;
255         pair<bool,const char*> p2=props->getString("handlerURL");
256         if (p2.first)
257             handler=p2.second;
258     }
259     
260     // Should never happen...
261     if (!handler || (*handler!='/' && strncmp(handler,"http:",5) && strncmp(handler,"https:",6)))
262         throw ConfigurationException(
263             "Invalid handlerURL property ($1) in Application ($2)",
264             params(2, handler ? handler : "null", m_app->getId())
265             );
266
267     // The "handlerURL" property can be in one of three formats:
268     //
269     // 1) a full URI:       http://host/foo/bar
270     // 2) a hostless URI:   http:///foo/bar
271     // 3) a relative path:  /foo/bar
272     //
273     // #  Protocol  Host        Path
274     // 1  handler   handler     handler
275     // 2  handler   resource    handler
276     // 3  resource  resource    handler
277     //
278     // note: if ssl_only is true, make sure the protocol is https
279
280     const char* path = NULL;
281
282     // Decide whether to use the handler or the resource for the "protocol"
283     const char* prot;
284     if (*handler != '/') {
285         prot = handler;
286     }
287     else {
288         prot = resource;
289         path = handler;
290     }
291
292     // break apart the "protocol" string into protocol, host, and "the rest"
293     const char* colon=strchr(prot,':');
294     colon += 3;
295     const char* slash=strchr(colon,'/');
296     if (!path)
297         path = slash;
298
299     // Compute the actual protocol and store in member.
300     if (ssl_only)
301         m_handlerURL.assign("https://");
302     else
303         m_handlerURL.assign(prot, colon-prot);
304
305     // create the "host" from either the colon/slash or from the target string
306     // If prot == handler then we're in either #1 or #2, else #3.
307     // If slash == colon then we're in #2.
308     if (prot != handler || slash == colon) {
309         colon = strchr(resource, ':');
310         colon += 3;      // Get past the ://
311         slash = strchr(colon, '/');
312     }
313     string host(colon, (slash ? slash-colon : strlen(colon)));
314
315     // Build the handler URL
316     m_handlerURL += host + path;
317     return m_handlerURL.c_str();
318 }
319
320 void AbstractSPRequest::log(SPLogLevel level, const std::string& msg) const
321 {
322     reinterpret_cast<Category*>(m_log)->log(
323         (level == SPDebug ? Priority::DEBUG :
324         (level == SPInfo ? Priority::INFO :
325         (level == SPWarn ? Priority::WARN :
326         (level == SPError ? Priority::ERROR : Priority::CRIT)))),
327         msg
328         );
329 }
330
331 bool AbstractSPRequest::isPriorityEnabled(SPLogLevel level) const
332 {
333     return reinterpret_cast<Category*>(m_log)->isPriorityEnabled(
334         (level == SPDebug ? Priority::DEBUG :
335         (level == SPInfo ? Priority::INFO :
336         (level == SPWarn ? Priority::WARN :
337         (level == SPError ? Priority::ERROR : Priority::CRIT))))
338         );
339 }