Merge branch '1.x' of ssh://authdev.it.ohio-state.edu/~scantor/git/cpp-xmltooling...
[shibboleth/cpp-xmltooling.git] / xmltooling / util / TemplateEngine.cpp
1 /**
2  * Licensed to the University Corporation for Advanced Internet
3  * Development, Inc. (UCAID) under one or more contributor license
4  * agreements. See the NOTICE file distributed with this work for
5  * additional information regarding copyright ownership.
6  *
7  * UCAID licenses this file to you under the Apache License,
8  * Version 2.0 (the "License"); you may not use this file except
9  * in compliance with the License. You may obtain a copy of the
10  * License at
11  *
12  * http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing,
15  * software distributed under the License is distributed on an
16  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
17  * either express or implied. See the License for the specific
18  * language governing permissions and limitations under the License.
19  */
20
21 /**
22  * TemplateEngine.cpp
23  *
24  * Simple template replacement engine.
25  */
26
27 #include "internal.h"
28 #include "io/GenericRequest.h"
29 #include "util/TemplateEngine.h"
30
31 using namespace xmltooling;
32 using namespace std;
33
34 namespace {
35     static const pair<const string,string> emptyPair;
36 }
37
38 TemplateEngine::TemplateEngine()
39 {
40     setTagPrefix("mlp");
41 }
42
43 TemplateEngine::~TemplateEngine()
44 {
45 }
46
47 TemplateEngine::TemplateParameters::TemplateParameters() : m_request(nullptr)
48 {
49 }
50
51 TemplateEngine::TemplateParameters::~TemplateParameters()
52 {
53 }
54
55 const char* TemplateEngine::TemplateParameters::getParameter(const char* name) const
56 {
57     map<string,string>::const_iterator i=m_map.find(name);
58     return (i!=m_map.end() ? i->second.c_str() : (m_request ? m_request->getParameter(name) : nullptr));
59 }
60
61 const multimap<string,string>* TemplateEngine::TemplateParameters::getLoopCollection(const char* name) const
62 {
63     map< string,multimap<string,string> >::const_iterator i=m_collectionMap.find(name);
64     return (i!=m_collectionMap.end() ? &(i->second) : nullptr);
65 }
66
67 void TemplateEngine::setTagPrefix(const char* tagPrefix)
68 {
69     keytag = string("<") + tagPrefix + " ";
70     iftag = string("<") + tagPrefix + "if ";
71     ifnottag = string("<") + tagPrefix + "ifnot ";
72     ifendtag = string("</") + tagPrefix + "if>";
73     ifnotendtag = string("</") + tagPrefix + "ifnot>";
74     fortag = string("<") + tagPrefix + "for ";
75     forendtag = string("</") + tagPrefix + "for>";
76 }
77
78 string TemplateEngine::unsafe_chars = "#%&():[]\\`{}";
79
80 void TemplateEngine::html_encode(ostream& os, const char* start) const
81 {
82     while (start && *start) {
83         switch (*start) {
84             case '<':   os << "&lt;";       break;
85             case '>':   os << "&gt;";       break;
86             case '"':   os << "&quot;";     break;
87             case '&':   os << "&#38;";      break;
88             case '\'':  os << "&#39;";      break;
89
90             default:
91                 if (unsafe_chars.find_first_of(*start) != string::npos)
92                     os << "&#" << static_cast<short>(*start) << ';';
93                 else
94                     os << *start;
95
96             /*
97             case '#':   os << "&#35;";      break;
98             case '%':   os << "&#37;";      break;
99             case '(':   os << "&#40;";      break;
100             case ')':   os << "&#41;";      break;
101             case ':':   os << "&#58;";      break;
102             case '[':   os << "&#91;";      break;
103             case '\\':  os << "&#92;";      break;
104             case ']':   os << "&#93;";      break;
105             case '`':   os << "&#96;";      break;
106             case '{':   os << "&#123;";     break;
107             case '}':   os << "&#125;";     break;
108             default:    os << *start;
109             */
110         }
111         start++;
112     }
113 }
114
115 void TemplateEngine::trimspace(string& s) const
116 {
117   string::size_type end = s.size() - 1, start = 0;
118
119   // Trim stuff on right.
120   while (end > 0 && !isgraph(s[end])) end--;
121
122   // Trim stuff on left.
123   while (start < end && !isgraph(s[start])) start++;
124
125   // Modify the string.
126   s = s.substr(start, end - start + 1);
127 }
128
129 void TemplateEngine::process(
130     bool visible,
131     const string& buf,
132     const char*& lastpos,
133     ostream& os,
134     const TemplateParameters& parameters,
135     const std::pair<const std::string,std::string>& loopentry,
136     const XMLToolingException* e
137     ) const
138 {
139     const char* line = buf.c_str();
140     const char* thispos;
141
142     while ((thispos = strchr(lastpos, '<')) != nullptr) {
143         // Output the string up to this token.
144         if (visible)
145             os << buf.substr(lastpos-line, thispos-lastpos);
146
147         // Make sure this token matches our tokens.
148 #ifdef HAVE_STRCASECMP
149         if (visible && !strncasecmp(thispos, keytag.c_str(), keytag.length()))
150 #else
151         if (visible && !_strnicmp(thispos, keytag.c_str(), keytag.length()))
152 #endif
153         {
154             // Save this position off.
155             lastpos = thispos + keytag.length();
156
157             // search for the end-tag
158             if ((thispos = strstr(lastpos, "/>")) != nullptr) {
159                 string key = buf.substr(lastpos-line, thispos-lastpos);
160                 trimspace(key);
161
162                 if (key == "$name" && !loopentry.first.empty())
163                     html_encode(os,loopentry.first.c_str());
164                 else if (key == "$value" && !loopentry.second.empty())
165                     html_encode(os,loopentry.second.c_str());
166                 else {
167                     const char* p = parameters.getParameter(key.c_str());
168                     if (!p && e)
169                         p = e->getProperty(key.c_str());
170                     if (p)
171                         html_encode(os,p);
172                 }
173                 lastpos = thispos + 2; // strlen("/>")
174             }
175         }
176 #ifdef HAVE_STRCASECMP
177         else if (!strncasecmp(thispos, iftag.c_str(), iftag.length()))
178 #else
179         else if (!_strnicmp(thispos, iftag.c_str(), iftag.length()))
180 #endif
181         {
182             // Save this position off.
183             lastpos = thispos + iftag.length();
184
185             // search for the end of this tag
186             if ((thispos = strchr(lastpos, '>')) != nullptr) {
187                 string key = buf.substr(lastpos-line, thispos-lastpos);
188                 trimspace(key);
189                 bool cond=false;
190                 if (visible)
191                     cond = parameters.getParameter(key.c_str()) || (e && e->getProperty(key.c_str()));
192                 lastpos = thispos + 1; // strlen(">")
193                 process(cond, buf, lastpos, os, parameters, loopentry, e);
194             }
195         }
196 #ifdef HAVE_STRCASECMP
197         else if (!strncasecmp(thispos, ifendtag.c_str(), ifendtag.length()))
198 #else
199         else if (!_strnicmp(thispos, ifendtag.c_str(), ifendtag.length()))
200 #endif
201         {
202             // Save this position off and pop the stack.
203             lastpos = thispos + ifendtag.length();
204             return;
205         }
206 #ifdef HAVE_STRCASECMP
207         else if (!strncasecmp(thispos, ifnottag.c_str(), ifnottag.length()))
208 #else
209         else if (!_strnicmp(thispos, ifnottag.c_str(), ifnottag.length()))
210 #endif
211         {
212             // Save this position off.
213             lastpos = thispos + ifnottag.length();
214
215             // search for the end of this tag
216             if ((thispos = strchr(lastpos, '>')) != nullptr) {
217                 string key = buf.substr(lastpos-line, thispos-lastpos);
218                 trimspace(key);
219                 bool cond=visible;
220                 if (visible)
221                     cond = !(parameters.getParameter(key.c_str()) || (e && e->getProperty(key.c_str())));
222                 lastpos = thispos + 1; // strlen(">")
223                 process(cond, buf, lastpos, os, parameters, loopentry, e);
224             }
225         }
226 #ifdef HAVE_STRCASECMP
227         else if (!strncasecmp(thispos, ifnotendtag.c_str(), ifnotendtag.length()))
228 #else
229         else if (!_strnicmp(thispos, ifnotendtag.c_str(), ifnotendtag.length()))
230 #endif
231         {
232             // Save this position off and pop the stack.
233             lastpos = thispos + ifnotendtag.length();
234             return;
235         }
236
237 #ifdef HAVE_STRCASECMP
238         else if (!strncasecmp(thispos, fortag.c_str(), fortag.length()))
239 #else
240         else if (!_strnicmp(thispos, fortag.c_str(), fortag.length()))
241 #endif
242         {
243             // Save this position off.
244             lastpos = thispos + iftag.length();
245             string key;
246             bool cond = visible;
247
248             // search for the end of this tag
249             if ((thispos = strchr(lastpos, '>')) != nullptr) {
250                 key = buf.substr(lastpos-line, thispos-lastpos);
251                 trimspace(key);
252                 lastpos = thispos + 1; // strlen(">")
253             }
254
255             const multimap<string,string>* forParams = parameters.getLoopCollection(key.c_str());
256             if (!forParams || forParams->size() == 0) {
257                 process(false, buf, lastpos, os, parameters, emptyPair, e);
258             }
259             else {
260                 const char* savlastpos = lastpos;
261                 for (multimap<string,string>::const_iterator i=forParams->begin(); i!=forParams->end(); ++i) {
262                     lastpos = savlastpos;
263                     process(cond, buf, lastpos, os, parameters, *i, e);
264                 }
265             }
266         }
267
268 #ifdef HAVE_STRCASECMP
269         else if (!strncasecmp(thispos, forendtag.c_str(), forendtag.length()))
270 #else
271         else if (!_strnicmp(thispos, forendtag.c_str(), forendtag.length()))
272 #endif
273         {
274             // Save this position off and pop the stack.
275             lastpos = thispos + forendtag.length();
276             return;
277         }
278
279         else {
280             // Skip it.
281             if (visible)
282                 os << '<';
283             lastpos = thispos + 1;
284         }
285     }
286     if (visible)
287         os << buf.substr(lastpos-line);
288 }
289
290 void TemplateEngine::run(istream& is, ostream& os, const TemplateParameters& parameters, const XMLToolingException* e) const
291 {
292     string buf,line;
293     while (getline(is, line))
294         buf += line + '\n';
295
296     const char* pos=buf.c_str();
297     process(true, buf, pos, os, parameters, emptyPair, e);
298 }