1
/* $Id: ConfigFile.cpp,v 1.12 2004/08/27 17:53:44 terpstra Exp $
3
* ConfigFile.cpp - Knows how to load the config file
5
* Copyright (C) 2002 - Wesley W. Terpstra
9
* Authors: 'Wesley W. Terpstra' <wesley@terpstra.ca>
11
* This program is free software; you can redistribute it and/or modify
12
* it under the terms of the GNU General Public License as published by
13
* the Free Software Foundation; version 2.
15
* This program is distributed in the hope that it will be useful,
16
* but WITHOUT ANY WARRANTY; without even the implied warranty of
17
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18
* GNU General Public License for more details.
20
* You should have received a copy of the GNU General Public License
21
* along with this program; if not, write to the Free Software
22
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
25
#define _XOPEN_SOURCE 500
26
#define _FILE_OFFSET_BITS 64
28
#include "ConfigFile.h"
29
#include "XmlEscape.h"
33
#include <sys/types.h>
39
map<string, string>* lstring::c = 0;
41
lstring::lstring(const string& fallback)
46
void lstring::prep_c()
48
c = new map<string, string>();
50
// source: http://www.w3.org/WAI/ER/IG/ert/iso639.htm
51
static const char* code[][2] =
210
// I think these are the same?
219
for (unsigned int i = 0; code[i][0] != ""; ++i)
220
(*c)[code[i][0]] = code[i][1];
223
bool lstring::lang_normalize(string& lang)
227
if (lang.length() != 2 && lang.length() != 3)
232
for (string::size_type i = 0; i < iso.length(); ++i)
234
if (iso[i] >= 'A' && iso[i] <= 'Z')
235
iso[i] = iso[i] - 'A' + 'a';
236
if (iso[i] < 'a' || iso[i] > 'z')
237
return false; // not an ISO 639 code!
240
// Resolve different language spellings to one spelling
241
if (c->find(iso) != c->end())
248
bool lstring::locale_normalize(string& locale)
250
string::size_type i, e;
252
if ((e = locale.find('-')) == string::npos &&
253
(e = locale.find('_')) == string::npos)
256
string lang(locale, 0, e);
257
if (!lang_normalize(lang)) return false;
260
if (e != locale.length())
262
region.assign(locale, e+1, string::npos);
263
if (region.length() != 2) return false; // not an ISO 3166 code
265
for (i = 0; i < 2; ++i)
267
if (region[i] >= 'a' && region[i] <= 'z')
268
region[i] = region[i] - 'a' + 'A';
269
if (region[i] < 'A' || region[i] > 'Z')
270
return false; // not an ISO 3166 code!
273
locale = lang + "-" + region;
283
bool lstring::translate(const string& language_, const string& translation)
285
string language(language_);
286
if (language != "" && !locale_normalize(language)) return false;
290
// this overrides whatever we have got so far
291
s[language] = translation;
293
// maybe a localized string for which we lack a language setting?
294
if ((i = language.find('-')) != string::npos)
296
string iso(language, 0, i);
297
if (s.find(iso) == s.end()) translate(iso, translation);
300
// maybe we lack a fallback language completely
301
if (s.find("") == s.end()) translate("", translation);
306
string lstring::localize(const string& language_) const
308
string language(language_);
309
if (language != "" && !locale_normalize(language)) return "(bug) bad locale";
311
map<string, string>::const_iterator o;
314
// correct locale? use it
315
if ((o = s.find(language)) != s.end()) return o->second;
317
// correct language? use it
318
if ((i = language.find('-')) != string::npos)
320
string iso(language, 0, i);
321
if ((o = s.find(iso)) != s.end()) return o->second;
325
if ((o = s.find("")) != s.end()) return o->second;
327
return ""; // not set!
330
bool lstring::is_set() const
336
: list(0), group("lists"), error(), lists(), groups(),
343
archive("Unconfigured Archivew"),
344
admin_name("Unset admin name"),
353
void prune_back(string& line)
355
// Trim off eol and whitespace
356
string::size_type whitespace = line.length();
357
while (whitespace > 0 &&
358
(line[whitespace-1] == ' ' ||
359
line[whitespace-1] == '\r' ||
360
line[whitespace-1] == '\n' ||
361
line[whitespace-1] == '\t'))
364
line.resize(whitespace);
367
string::size_type skip_front(const string& line, string::size_type x = 0)
369
// Trim off eol and whitespace
370
for (; x < line.length(); ++x)
371
if (line[x] != ' ' &&
380
int Config::load(const string& file, bool toplevel)
382
ifstream f(file.c_str());
385
error << file << ":open: could not open!" << endl;
390
if (stat(file.c_str(), &sbuf) < 0)
392
error << file << ":stat: could not stat!" << endl;
396
// deal with included file's timestamps
397
if (sbuf.st_mtime > modified)
398
modified = sbuf.st_mtime;
401
string::size_type x = file.rfind('/');
402
if (x != string::npos) dir.assign(file, 0, x+1);
410
while (getline(f, line))
412
// Increment line number
415
// Trim off the comments
416
string::size_type comment = line.find('#');
417
if (comment != string::npos) line.resize(comment);
419
// Clear off trailing whitespace
423
if (line.length() == 0) continue;
425
string::size_type eq = line.find('=');
426
if (eq == string::npos)
427
{ // this line continues the previous one.
430
error << "No key for value '" << line << "'!" << endl;
435
string::size_type fe = skip_front(line);
437
val.append(line, fe, string::npos);
442
if (key != "" && process_command(key, val, dir) != 0) ok = false;
444
string::size_type leadin = skip_front(line);
445
key.assign(line, leadin, eq-leadin);
446
val.assign(line, skip_front(line, eq+1), string::npos);
451
if (toplevel && key == "")
453
error << "No values set by config file '" << file << "'!" << endl;
457
if (key != "" && process_command(key, val, dir) != 0) ok = false;
463
bool isSimple(const string& s)
466
for (x = 0; x < s.length(); ++x)
469
if (y >= 'a' && y <= 'z') continue;
470
if (y >= '0' && y <= '9') continue;
471
if (y == '.' || y == '-' || y == '_') continue;
478
int Config::process_command(const string& keys, const string& val, const string& dir)
480
// cout << key << "-" << val << endl;
482
string lc; // locale code
485
string::size_type o, c;
486
if ((o = key.find('[')) != string::npos &&
487
(c = key.find(']')) != string::npos &&
490
// localization option
491
lc.assign(key, o+1, (c-o) - 1);
492
key.erase(o, (c-o) + 1);
494
if (!lstring::locale_normalize(lc))
496
error << "Localization code '" << lc << "' is not valid." << endl;
501
string::size_type len = string::npos;
506
if (!isSimple(val) || val.length() == 0)
508
error << "Group id '" << val << "' is not a simple lowercase string!" << endl;
514
error << "group id cannot be localized" << endl;
520
else if (key == "heading")
523
groups[group].heading.translate(lc, val);
525
else if (key == "list")
528
if (!isSimple(val) || val.length() == 0)
530
error << "List id '" << val << "' is not a simple lowercase string!" << endl;
535
error << "list id cannot be localized" << endl;
539
if (lists.find(val) == lists.end())
541
groups[group].members.insert(val);
545
list->language = "en";
546
list->offline = false;
550
// re-enter list scope
554
else if (key == "title")
560
error << "No list has been defined for title '" << val << "'!" << endl;
564
list->title.translate(lc, val);
566
else if (key == "address")
570
error << "No list has been defined for address '" << val << "'!" << endl;
575
error << "list address cannot be localized" << endl;
581
else if (key == "link")
585
error << "No list has been defined for address '" << val << "'!" << endl;
589
list->link.translate(lc, val);
591
else if (key == "language")
595
error << "No list has been defined for language '" << val << "'!" << endl;
600
error << "list language cannot be localized" << endl;
605
if (!lstring::lang_normalize(lval))
607
error << "Language '" << val << "' is not an ISO 639 language code!" << endl;
608
error << "Regional variants are not relevant for searches." << endl;
612
list->language = lval;
614
else if (key == "offline")
618
error << "No list has been defined for offline setting '" << val << "'!" << endl;
623
error << "list offline cannot be localized" << endl;
627
if (val == "off" || val == "false")
628
list->offline = false;
629
else if (val == "on" || val == "true")
630
list->offline = true;
633
error << "offline must be set to on/off or true/false!" << endl;
637
else if (key == "description")
641
error << "No list has been defined for address '" << val << "'!" << endl;
645
list->description.translate(lc, val);
647
else if (key == "dbdir")
651
error << "dbdir cannot be localized" << endl;
656
else if (key == "admin_name")
658
admin_name.translate(lc, val);
660
else if (key == "admin_address")
664
error << "admin_address cannot be localized" << endl;
669
else if (key == "archive")
671
archive.translate(lc, val);
673
else if (key == "xslt")
677
error << "xslt command cannot be localized" << endl;
682
else if (key == "pgp_verify_mime")
686
error << "pgp_verify_mime command cannot be localized" << endl;
691
else if (key == "pgp_verify_inline")
695
error << "pgp_verify_inline command cannot be localized" << endl;
700
else if (key == "web_cache")
704
error << "web_cache cannot be localized" << endl;
707
if (val == "off" || val == "false")
709
else if (val == "on" || val == "true")
713
error << "web_cache must be set to on/off or true/false!" << endl;
717
else if (key == "hide_email")
721
error << "hide_email cannot be localized" << endl;
724
if (val == "off" || val == "false")
726
else if (val == "on" || val == "true")
730
error << "hide_email must be set to on/off or true/false!" << endl;
734
else if (key == "regroupable")
738
error << "regroupable cannot be localized" << endl;
741
if (val == "off" || val == "false")
743
else if (val == "on" || val == "true")
747
error << "regroupable must be set to on/off or true/false!" << endl;
751
else if (key == "raw_email")
755
error << "raw_email cannot be localized" << endl;
758
if (val == "off" || val == "false")
760
else if (val == "on" || val == "true")
764
error << "raw_email must be set to on/off or true/false!" << endl;
768
else if (key == "include")
772
error << "include cannot be localized" << endl;
779
else file = dir + val;
781
if (load(file, false) != 0)
786
error << "Unknown configuration directive '" << key << "'!" << endl;
790
if (val.length() > len)
792
error << "Value '" << val << "' is too long for directive '" << key << "'!" << endl;
799
ostream& operator << (ostream& o, const List::SerializeMagic& lm)
801
const List& m = lm.m;
802
const string& l = lm.l;
805
<< "<id>" << m.mbox << "</id>"
806
<< "<group>" << m.group << "</group>"
807
<< "<language>" << m.language << "</language>";
813
o << "<link>" << xmlEscape << m.link(l) << "</link>";
815
if (m.description.is_set())
816
o << "<description>" << xmlEscape << m.description(l) << "</description>";
819
if (m.address.length() > 0)
820
o << " address=\"" << xmlEscape << m.address << "\"";
821
if (m.title.is_set())
822
o << " name=\"" << xmlEscape << m.title(l) << "\"";
824
o << " name=\"" << m.mbox << "\"";
831
ostream& operator << (ostream& o, const Config::SerializeMagic& cm)
833
const Config& c = cm.c;
834
const string& l = cm.l;
836
// expire = time(0) + 60*5; // 5 minute cache
838
// tm = gmtime(&expire);
839
// strftime(&timebuf[0], sizeof(timebuf),
840
// "%a, %d %b %Y %H:%M:%S GMT", tm);
843
time_t end_of_archive = time(0) + 365*24*60*60;
844
strftime(&year[0], sizeof(year), "%Y", gmtime(&end_of_archive));
847
<< "<version>" << VERSION << "</version>"
848
<< "<eoa-year>" << year << "</eoa-year>"
849
<< "<doc-url>" << c.docUrl << "</doc-url>"
850
<< "<cgi-url>" << c.cgiUrl << "</cgi-url>"
851
<< "<command>" << c.command << "</command>"
852
<< "<options>" << c.options << "</options>";
854
if (c.raw_email) o << "<raw-email/>";
858
if (c.archive.is_set())
859
o << xmlEscape << c.archive(l);
860
else o << "Some Mailing List Archive";
862
o << "</archive><email";
863
if (c.admin_address.length() > 0)
864
o << " address=\"" << xmlEscape << c.admin_address << "\"";
865
if (c.admin_name.is_set())
866
o << " name=\"" << xmlEscape << c.admin_name(l) << "\"";