Code drop of new target..
[shibboleth/sp.git] / shib-target / shib-ini.cpp
1 /*
2  * shib-ini.h -- INI file handling
3  *
4  * Created By:  Derek Atkins <derek@ihtfp.com>
5  *
6  * $Id$
7  */
8
9 #include "shib-target.h"
10 #include <strstream>
11 #include <iostream>
12 #include <fstream>
13 #include <ctype.h>
14
15 #include <log4cpp/Category.hh>
16
17 using namespace std;
18 using namespace shibtarget;
19
20 class HeaderIterator : public shibtarget::ShibINI::Iterator {
21 public:
22   HeaderIterator (ShibINIPriv* ini);
23   ~HeaderIterator () { }
24
25   const string* begin();
26   const string* next();
27 private:
28   ShibINIPriv* ini;
29   map<string, map<string,string> >::const_iterator iter;
30   bool valid;
31 };
32
33 class TagIterator : public ShibINI::Iterator {
34 public:
35   TagIterator (ShibINIPriv* ini, const string& header);
36   ~TagIterator () { }
37
38   const string* begin();
39   const string* next();
40 private:
41   ShibINIPriv* ini;
42   const string& header;
43   map<string,string>::const_iterator iter;
44   bool valid;
45 };
46
47 class shibtarget::ShibINIPriv {
48 public:
49   ShibINIPriv();
50   ~ShibINIPriv() { }
51   log4cpp::Category *log;
52
53   map<string, map<string, string> > table;
54   string file;
55   bool cs;
56 };
57
58 ShibINIPriv::ShibINIPriv()
59 {
60   string ctx = "shibtarget.ShibINI";
61   log = &(log4cpp::Category::getInstance(ctx));
62 }
63
64 static void trimline (string& s)
65 {
66   int end = s.size() - 1, start = 0;
67
68   // Trim stuff on right.
69   while (end > 0 && !isgraph(s[end])) end--;
70
71   // Trim stuff on left.
72   while (start < end && !isgraph(s[start])) start++;
73
74   // Modify the string.
75   s = s.substr(start, end - start + 1);
76 }
77
78 static void to_lowercase (string& s)
79 {
80   for (int i = 0, sz = s.size(); i < sz; i++)
81     s[i] = tolower(s[i]);
82 }
83
84 ShibINI::~ShibINI() {
85   delete m_priv;
86 }
87
88 void ShibINI::init (string& f, bool case_sensitive)
89 {
90   m_priv = new ShibINIPriv();
91   m_priv->file = f;
92   m_priv->cs = case_sensitive;
93   m_priv->log->info ("initializing INI file: %s (sensitive=%s)", f.c_str(),
94                      (case_sensitive ? "true" : "false"));
95   refresh();
96 }
97
98 void ShibINI::refresh(void)
99 {
100   // clear the existing maps
101   m_priv->table.clear();
102
103   m_priv->log->info("reading %s", m_priv->file.c_str());
104   
105   // read the file
106   try
107   {
108     ifstream infile (m_priv->file.c_str());
109     if (!infile) {
110       m_priv->log->warn("cannot open file: %s", m_priv->file.c_str());
111       return;
112     }
113
114     const int MAXLEN = 1024;
115     char linebuffer[MAXLEN];
116     string current_header;
117     bool have_header = false;
118
119     while (infile) {
120       infile.getline (linebuffer, MAXLEN);
121       string line (linebuffer);
122
123       if (line[0] == '#') continue;
124
125       trimline (line);
126       if (line.size() <= 1) continue;
127
128       if (line[0] == '[') {
129         // this is a header
130
131         m_priv->log->debug("Found what appears to be a header line");
132
133         have_header = false;
134
135         // find the end of the header
136         int endpos = line.find (']');
137         if (endpos == line.npos) {
138           m_priv->log->debug("Weird: no end found.. punting");
139           continue; // HUH?  No end?
140         }
141
142         // found it
143         current_header = line.substr (1, endpos-1);
144         trimline (current_header);
145
146         if (!m_priv->cs) to_lowercase (current_header);
147
148         m_priv->table[current_header] = map<string,string>();
149         have_header = true;
150         m_priv->log->debug("current header: \"%s\"", current_header.c_str());
151
152       } else if (have_header) {
153         // this is a tag
154
155         m_priv->log->debug("Found what appears to be a tag line");
156
157         string tag, setting;
158         int mid = line.find ('=');
159
160         if (mid == line.npos) {
161           m_priv->log->debug("Weird: no '=' found.. punting");
162           continue; // Can't find the value's setting
163         }
164
165         tag = line.substr (0,mid);
166         setting = line.substr (mid+1, line.size()-mid);
167
168         trimline (tag);
169         trimline (setting);
170
171         if (!m_priv->cs) to_lowercase (tag);
172
173         // If it already exists, log an error and do not save it
174         if (exists (current_header, tag))
175           m_priv->log->error("Duplicate tag found in section %s: \"%s\"",
176                              current_header.c_str(), tag.c_str());
177         else
178           (m_priv->table[current_header])[tag] = setting;
179
180         m_priv->log->debug("new tag: \"%s\" = \"%s\"",
181                           tag.c_str(), setting.c_str());
182
183       }
184
185     } // until the file ends
186
187   } catch (...) {
188     // In case there are exceptions.
189   }
190 }
191
192 const std::string& ShibINI::get (const string& header, const string& tag) const
193 {
194   static string empty = "";
195
196   string h = header;
197   string t = tag;
198
199   if (!m_priv->cs) {
200     to_lowercase (h);
201     to_lowercase (t);
202   }
203
204   if (!exists(h)) return empty;
205
206   map<string,string>::const_iterator i = m_priv->table[h].find(t);
207   if (i == m_priv->table[h].end())
208     return empty;
209   return i->second;
210 }
211
212 bool ShibINI::exists(const std::string& header) const
213 {
214   string h = header;
215   if (!m_priv->cs) to_lowercase (h);
216
217   return (m_priv->table.find(h) != m_priv->table.end());
218 }
219
220 bool ShibINI::exists(const std::string& header, const std::string& tag) const
221 {
222   string h = header;
223   string t = tag;
224
225   if (!m_priv->cs) {
226     to_lowercase (h);
227     to_lowercase (t);
228   }
229
230   if (!exists(h)) return false;
231   return (m_priv->table[h].find(t) != m_priv->table[h].end());
232 }
233
234 void ShibINI::dump (ostream& os) const
235 {
236   os << "File: " << m_priv->file << "\n";
237   os << "Case-Sensitive: " << ( m_priv->cs ? "Yes\n" : "No\n" );
238   os << "File Entries:\n";
239
240   for (map<string, map<string, string> >::const_iterator i = m_priv->table.begin();
241        i != m_priv->table.end(); i++) {
242
243     os << "[" << i->first << "]\n";
244
245     for (map<string,string>::const_iterator j=i->second.begin();
246          j != i->second.end(); j++) {
247
248       os << "  " << j->first << " = " << j->second << "\n";
249     }
250   }
251
252   os << "END\n";
253 }
254
255 ShibINI::Iterator* ShibINI::header_iterator() const
256 {
257   HeaderIterator* iter = new HeaderIterator(m_priv);
258   return (ShibINI::Iterator*) iter;
259 }
260
261 ShibINI::Iterator* ShibINI::tag_iterator(const std::string& header) const
262 {
263   TagIterator* iter = new TagIterator(m_priv, header);
264   return (ShibINI::Iterator*) iter;
265 }
266
267 HeaderIterator::HeaderIterator (ShibINIPriv* inip)
268 {
269   ini = inip;
270   valid = false;
271 }
272
273 const string* HeaderIterator::begin ()
274 {
275   iter = ini->table.begin();
276   if (iter == ini->table.end()) {
277     valid = false;
278     return 0;
279   }
280   valid = true;
281   return &iter->first;
282 }
283
284 const string* HeaderIterator::next ()
285 {
286   if (!valid)
287     return 0;
288   iter++;
289   if (iter == ini->table.end()) {
290     valid = false;
291     return 0;
292   }
293   return &iter->first;
294 }
295
296 TagIterator::TagIterator (ShibINIPriv* inip, const string& headerp)
297   : header(headerp)
298 {
299   ini = inip;
300   valid = false;
301 }
302
303 const string* TagIterator::begin ()
304 {
305   iter = ini->table[header].begin();
306   if (iter == ini->table[header].end()) {
307     valid = false;
308     return 0;
309   }
310   valid = true;
311   return &iter->first;
312 }
313
314 const string* TagIterator::next ()
315 {
316   if (!valid)
317     return 0;
318   iter++;
319   if (iter == ini->table[header].end()) {
320     valid = false;
321     return 0;
322   }
323   return &iter->first;
324 }