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