Non-threadsafe call to ctime()
[shibboleth/cpp-sp.git] / shib-target / shib-mlp.cpp
1 /*
2  *  Copyright 2001-2005 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  * shib-mlp.cpp -- The ShibTarget Markup Language processor
19  *
20  * Created by:  Derek Atkins <derek@ihtfp.com>
21  *
22  * $Id$
23  */
24
25 #include "internal.h"
26
27 #include <sstream>
28 #include <ctype.h>
29 #include <xercesc/util/XercesDefs.hpp>
30 #include <log4cpp/Category.hh>
31
32 using namespace std;
33 using namespace log4cpp;
34 using namespace saml;
35 using namespace shibboleth;
36 using namespace shibtarget;
37
38 class shibtarget::ShibMLPPriv {
39 public:
40   ShibMLPPriv();
41   ~ShibMLPPriv() {}
42   log4cpp::Category *log;
43
44   static void html_encode(string& os, const char* start);
45 };  
46
47
48 void ShibMLPPriv::html_encode(string& os, const char* start)
49 {
50     while (start && *start) {
51         switch (*start) {
52             case '<':   os += "&lt;";       break;
53             case '>':   os += "&gt;";       break;
54             case '"':   os += "&quot;";     break;
55             case '#':   os += "&#35;";      break;
56             case '%':   os += "&#37;";      break;
57             case '&':   os += "&#38;";      break;
58             case '\'':  os += "&#39;";      break;
59             case '(':   os += "&#40;";      break;
60             case ')':   os += "&#41;";      break;
61             case ':':   os += "&#58;";      break;
62             case '[':   os += "&#91;";      break;
63             case '\\':  os += "&#92;";      break;
64             case ']':   os += "&#93;";      break;
65             case '`':   os += "&#96;";      break;
66             case '{':   os += "&#123;";     break;
67             case '}':   os += "&#125;";     break;
68             default:    os += *start;
69         }
70         start++;
71     }
72 }
73
74 ShibMLPPriv::ShibMLPPriv() : log(&(log4cpp::Category::getInstance("shibtarget.ShibMLP"))) {}
75
76 static void trimspace (string& s)
77 {
78   int end = s.size() - 1, start = 0;
79
80   // Trim stuff on right.
81   while (end > 0 && !isgraph(s[end])) end--;
82
83   // Trim stuff on left.
84   while (start < end && !isgraph(s[start])) start++;
85
86   // Modify the string.
87   s = s.substr(start, end - start + 1);
88 }
89
90 ShibMLP::ShibMLP()
91 {
92   m_priv = new ShibMLPPriv ();
93 }
94
95 ShibMLP::~ShibMLP ()
96 {
97   delete m_priv;
98 }
99
100 const char* ShibMLP::run(const string& is, const IPropertySet* props, std::string* output)
101 {
102   // Create a timestamp
103   time_t now = time(NULL);
104 #ifdef HAVE_CTIME_R
105   char timebuf[32];
106   insert("now", ctime_r(&now,timebuf));
107 #else
108   insert("now", ctime(&now));
109 #endif
110
111   if (!output)
112     output=&m_generated;
113   const char* line = is.c_str();
114   const char* lastpos = line;
115   const char* thispos;
116
117   m_priv->log->debug("Processing string");
118
119   //
120   // Search for SHIBMLP tags.  These are of the form:
121   //    <shibmlp key/>
122   //    <shibmlpif key> stuff </shibmlpif>
123   //    <shibmlpifnot key> stuff </shibmlpifnot>
124   // Note that there MUST be white-space after "<shibmlp" but
125   // there does not need to be white space between the key and
126   // the close-tag.
127   //
128   while ((thispos = strchr(lastpos, '<')) != NULL) {
129     // save the string up to this token
130     *output += is.substr(lastpos-line, thispos-lastpos);
131
132     // Make sure this token matches our tokens.
133 #ifdef HAVE_STRCASECMP
134     if (!strncasecmp(thispos, "<shibmlp ", 9))
135 #else
136     if (!strnicmp(thispos, "<shibmlp ", 9))
137 #endif
138     {
139         // Save this position off.
140         lastpos = thispos + 9;  // strlen("<shibmlp ")
141     
142         // search for the end-tag
143         if ((thispos = strstr(lastpos, "/>")) != NULL) {
144             string key = is.substr(lastpos-line, thispos-lastpos);
145             trimspace(key);
146     
147             map<string,string>::const_iterator i=m_map.find(key);
148             if (i != m_map.end()) {
149                 m_priv->html_encode(*output,i->second.c_str());
150             }
151             else {
152                 pair<bool,const char*> p=props ? props->getString(key.c_str()) : pair<bool,const char*>(false,NULL);
153                 if (p.first) {
154                     m_priv->html_encode(*output,p.second);
155                 }
156                 else {
157                     static const char* s1 = "<!-- Unknown SHIBMLP key: ";
158                     static const char* s2 = "/>";
159                     *output += s1;
160                     *output += key + s2;
161                 }
162             }
163             lastpos = thispos + 2; // strlen("/>")
164         }
165     }
166 #ifdef HAVE_STRCASECMP
167     else if (!strncasecmp(thispos, "<shibmlpif ", 11))
168 #else
169     else if (!strnicmp(thispos, "<shibmlpif ", 11))
170 #endif
171     {
172         // Save this position off.
173         lastpos = thispos + 11;  // strlen("<shibmlpif ")
174
175         // search for the end of this tag
176         if ((thispos = strchr(lastpos, '>')) != NULL) {
177             string key = is.substr(lastpos-line, thispos-lastpos);
178             trimspace(key);
179             bool eval=false;
180             map<string,string>::const_iterator i=m_map.find(key);
181             if (i != m_map.end() && !i->second.empty()) {
182                 eval=true;
183             }
184             else {
185                 pair<bool,const char*> p=props ? props->getString(key.c_str()) : pair<bool,const char*>(false,NULL);
186                 if (p.first) {
187                     eval=true;
188                 }
189             }
190             lastpos = thispos + 1; // strlen(">")
191             
192             // Search for the closing tag.
193             const char* frontpos=lastpos;
194             while ((thispos = strstr(lastpos, "</")) != NULL) {
195 #ifdef HAVE_STRCASECMP
196                 if (!strncasecmp(thispos, "</shibmlpif>", 12))
197 #else
198                 if (!strnicmp(thispos, "</shibmlpif>", 12))
199 #endif
200                 {
201                     // We found our terminator. Process the string in between.
202                     string segment;
203                     run(is.substr(frontpos-line, thispos-frontpos),props,&segment);
204                     if (eval)
205                         *output += segment;
206                     lastpos = thispos + 12; // strlen("</shibmlpif>")
207                     break;
208                 }
209                 else {
210                     // Skip it.
211                     lastpos = thispos + 2;
212                 }
213             }
214         }
215     }
216 #ifdef HAVE_STRCASECMP
217     else if (!strncasecmp(thispos, "<shibmlpifnot ", 14))
218 #else
219     else if (!strnicmp(thispos, "<shibmlpifnot ", 14))
220 #endif
221     {
222         // Save this position off.
223         lastpos = thispos + 14;  // strlen("<shibmlpifnot ")
224
225         // search for the end of this tag
226         if ((thispos = strchr(lastpos, '>')) != NULL) {
227             string key = is.substr(lastpos-line, thispos-lastpos);
228             trimspace(key);
229             bool eval=false;
230             map<string,string>::const_iterator i=m_map.find(key);
231             if (i != m_map.end() && !i->second.empty()) {
232                 eval=true;
233             }
234             else {
235                 pair<bool,const char*> p=props ? props->getString(key.c_str()) : pair<bool,const char*>(false,NULL);
236                 if (p.first) {
237                     eval=true;
238                 }
239             }
240             lastpos = thispos + 1; // strlen(">")
241             
242             // Search for the closing tag.
243             const char* frontpos=lastpos;
244             while ((thispos = strstr(lastpos, "</")) != NULL) {
245 #ifdef HAVE_STRCASECMP
246                 if (!strncasecmp(thispos, "</shibmlpifnot>", 15))
247 #else
248                 if (!strnicmp(thispos, "</shibmlpifnot>", 15))
249 #endif
250                 {
251                     // We found our terminator. Process the string in between.
252                     string segment;
253                     run(is.substr(frontpos-line, thispos-frontpos),props,&segment);
254                     if (!eval)
255                         *output += segment;
256                     lastpos = thispos + 15; // strlen("</shibmlpifnot>")
257                     break;
258                 }
259                 else {
260                     // Skip it.
261                     lastpos = thispos + 2;
262                 }
263             }
264         }
265     }
266     else {
267       // Skip it.
268       *output += "<";
269       lastpos = thispos + 1;
270     }
271   }
272   *output += is.substr(lastpos-line);
273
274   return output->c_str();
275 }
276
277 const char* ShibMLP::run(istream& is, const IPropertySet* props, std::string* output)
278 {
279   static string eol = "\r\n";
280   string str, line;
281
282   m_priv->log->debug("processing stream");
283
284   while (getline(is, line))
285     str += line + eol;
286
287   return run(str,props,output);
288 }
289
290 void ShibMLP::insert(SAMLException& e)
291 {
292     insert("errorType", e.classname());
293     if (typeid(e)==typeid(ContentTypeException))
294         insert("errorText", "A problem was detected with your identity provider's software configuration.");
295     else
296         insert("errorText", e.getMessage() ? e.getMessage() : "No Message");
297     if (e.getProperty("errorURL"))
298         insert("originErrorURL", e.getProperty("errorURL"));
299     if (e.getProperty("contactName"))
300         insert("originContactName", e.getProperty("contactName"));
301     const char* email=e.getProperty("contactEmail");
302     if (email) {
303         if (!strncmp(email,"mailto:",7) && strlen(email)>7)
304             insert("originContactEmail", email+7);
305         else
306             insert("originContactEmail", email);
307     }
308 }
309
310 void ShibMLP::insert (const std::string& key, const std::string& value)
311 {
312   m_priv->log->debug("inserting %s -> %s", key.c_str(), value.c_str());
313   m_map[key] = value;
314 }