/* ** configfile.cpp ** Implements the basic .ini parsing class ** **--------------------------------------------------------------------------- ** Copyright 1998-2008 Randy Heit ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions ** are met: ** ** 1. Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** 2. Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in the ** documentation and/or other materials provided with the distribution. ** 3. The name of the author may not be used to endorse or promote products ** derived from this software without specific prior written permission. ** ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. **--------------------------------------------------------------------------- ** */ #include #include #include #include "configfile.h" #include "files.h" #define READBUFFERSIZE 256 //==================================================================== // // FConfigFile Constructor // //==================================================================== FConfigFile::FConfigFile () { Sections = CurrentSection = NULL; LastSectionPtr = &Sections; CurrentEntry = NULL; PathName = ""; OkayToWrite = true; FileExisted = true; } //==================================================================== // // FConfigFile Constructor // //==================================================================== FConfigFile::FConfigFile (const char *pathname) { Sections = CurrentSection = NULL; LastSectionPtr = &Sections; CurrentEntry = NULL; ChangePathName (pathname); LoadConfigFile (); OkayToWrite = true; FileExisted = true; } //==================================================================== // // FConfigFile Copy Constructor // //==================================================================== FConfigFile::FConfigFile (const FConfigFile &other) { Sections = CurrentSection = NULL; LastSectionPtr = &Sections; CurrentEntry = NULL; ChangePathName (other.PathName); *this = other; OkayToWrite = other.OkayToWrite; FileExisted = other.FileExisted; } //==================================================================== // // FConfigFile Destructor // //==================================================================== FConfigFile::~FConfigFile () { FConfigSection *section = Sections; while (section != NULL) { FConfigSection *nextsection = section->Next; FConfigEntry *entry = section->RootEntry; while (entry != NULL) { FConfigEntry *nextentry = entry->Next; delete[] entry->Value; delete[] (char *)entry; entry = nextentry; } delete section; section = nextsection; } } //==================================================================== // // FConfigFile Copy Operator // //==================================================================== FConfigFile &FConfigFile::operator = (const FConfigFile &other) { FConfigSection *fromsection, *tosection; FConfigEntry *fromentry; ClearConfig (); fromsection = other.Sections; while (fromsection != NULL) { fromentry = fromsection->RootEntry; tosection = NewConfigSection (fromsection->SectionName); while (fromentry != NULL) { NewConfigEntry (tosection, fromentry->Key, fromentry->Value); fromentry = fromentry->Next; } fromsection = fromsection->Next; } return *this; } //==================================================================== // // FConfigFile :: ClearConfig // // Removes all sections and entries from the config file. // //==================================================================== void FConfigFile::ClearConfig () { CurrentSection = Sections; while (CurrentSection != NULL) { FConfigSection *next = CurrentSection->Next; ClearCurrentSection (); delete CurrentSection; CurrentSection = next; } Sections = NULL; LastSectionPtr = &Sections; } //==================================================================== // // FConfigFile :: ChangePathName // //==================================================================== void FConfigFile::ChangePathName (const char *pathname) { PathName = pathname; } //==================================================================== // // FConfigFile :: CreateSectionAtStart // // Creates the section at the start of the file if it does not exist. // Otherwise, simply moves the section to the start of the file. // //==================================================================== void FConfigFile::CreateSectionAtStart (const char *name) { NewConfigSection (name); MoveSectionToStart (name); } //==================================================================== // // FConfigFile :: MoveSectionToStart // // Moves the named section to the start of the file if it exists. // Otherwise, does nothing. // //==================================================================== void FConfigFile::MoveSectionToStart (const char *name) { FConfigSection *section = FindSection (name); if (section != NULL) { FConfigSection **prevsec = &Sections; while (*prevsec != NULL && *prevsec != section) { prevsec = &((*prevsec)->Next); } *prevsec = section->Next; section->Next = Sections; Sections = section; if (LastSectionPtr == §ion->Next) { LastSectionPtr = prevsec; } } } //==================================================================== // // FConfigFile :: SetSection // // Sets the current section to the named one, optionally creating it // if it does not exist. Returns true if the section exists (even if // it was newly created), false otherwise. // //==================================================================== bool FConfigFile::SetSection (const char *name, bool allowCreate) { FConfigSection *section = FindSection (name); if (section == NULL && allowCreate) { section = NewConfigSection (name); } if (section != NULL) { CurrentSection = section; CurrentEntry = section->RootEntry; return true; } return false; } //==================================================================== // // FConfigFile :: SetFirstSection // // Sets the current section to the first one in the file. Returns // false if there are no sections. // //==================================================================== bool FConfigFile::SetFirstSection () { CurrentSection = Sections; if (CurrentSection != NULL) { CurrentEntry = CurrentSection->RootEntry; return true; } return false; } //==================================================================== // // FConfigFile :: SetNextSection // // Advances the current section to the next one in the file. Returns // false if there are no more sections. // //==================================================================== bool FConfigFile::SetNextSection () { if (CurrentSection != NULL) { CurrentSection = CurrentSection->Next; if (CurrentSection != NULL) { CurrentEntry = CurrentSection->RootEntry; return true; } } return false; } //==================================================================== // // FConfigFile :: GetCurrentSection // // Returns the name of the current section. // //==================================================================== const char *FConfigFile::GetCurrentSection () const { if (CurrentSection != NULL) { return CurrentSection->SectionName.GetChars(); } return NULL; } //==================================================================== // // FConfigFile :: ClearCurrentSection // // Removes all entries from the current section. // //==================================================================== void FConfigFile::ClearCurrentSection () { if (CurrentSection != NULL) { FConfigEntry *entry, *next; entry = CurrentSection->RootEntry; while (entry != NULL) { next = entry->Next; delete[] entry->Value; delete[] (char *)entry; entry = next; } CurrentSection->RootEntry = NULL; CurrentSection->LastEntryPtr = &CurrentSection->RootEntry; } } //==================================================================== // // FConfigFile :: DeleteCurrentSection // // Completely removes the current section. The current section is // advanced to the next section. Returns true if there is still a // current section. // //==================================================================== bool FConfigFile::DeleteCurrentSection() { if (CurrentSection != NULL) { FConfigSection *sec; ClearCurrentSection(); // Find the preceding section. for (sec = Sections; sec != NULL && sec->Next != CurrentSection; sec = sec->Next) { } sec->Next = CurrentSection->Next; if (LastSectionPtr == &CurrentSection->Next) { LastSectionPtr = &sec->Next; } delete CurrentSection; CurrentSection = sec->Next; return CurrentSection != NULL; } return false; } //==================================================================== // // FConfigFile :: ClearKey // // Removes a key from the current section, if found. If there are // duplicates, only the first is removed. // //==================================================================== void FConfigFile::ClearKey(const char *key) { if (CurrentSection->RootEntry == NULL) { return; } FConfigEntry **prober = &CurrentSection->RootEntry, *probe = *prober; while (probe != NULL && stricmp(probe->Key, key) != 0) { prober = &probe->Next; probe = *prober; } if (probe != NULL) { *prober = probe->Next; if (CurrentSection->LastEntryPtr == &probe->Next) { CurrentSection->LastEntryPtr = prober; } delete[] probe->Value; delete[] (char *)probe; } } //==================================================================== // // FConfigFile :: SectionIsEmpty // // Returns true if the current section has no entries. If there is // no current section, it is also considered empty. // //==================================================================== bool FConfigFile::SectionIsEmpty() { return (CurrentSection == NULL) || (CurrentSection->RootEntry == NULL); } //==================================================================== // // FConfigFile :: NextInSection // // Provides the next key/value pair in the current section. Returns // true if there was another, false otherwise. // //==================================================================== bool FConfigFile::NextInSection (const char *&key, const char *&value) { FConfigEntry *entry = CurrentEntry; if (entry == NULL) return false; CurrentEntry = entry->Next; key = entry->Key; value = entry->Value; return true; } //==================================================================== // // FConfigFile :: GetValueForKey // // Returns the value for the specified key in the current section, // returning NULL if the key does not exist. // //==================================================================== const char *FConfigFile::GetValueForKey (const char *key) const { FConfigEntry *entry = FindEntry (CurrentSection, key); if (entry != NULL) { return entry->Value; } return NULL; } //==================================================================== // // FConfigFile :: SetValueForKey // // Sets they key/value pair as specified in the current section. If // duplicates are allowed, it always creates a new pair. Otherwise, it // will overwrite the value of an existing key with the same name. // //==================================================================== void FConfigFile::SetValueForKey (const char *key, const char *value, bool duplicates) { if (CurrentSection != NULL) { FConfigEntry *entry; if (duplicates || (entry = FindEntry (CurrentSection, key)) == NULL) { NewConfigEntry (CurrentSection, key, value); } else { entry->SetValue (value); } } } //==================================================================== // // FConfigFile :: FindSection // //==================================================================== FConfigFile::FConfigSection *FConfigFile::FindSection (const char *name) const { FConfigSection *section = Sections; while (section != NULL && section->SectionName.CompareNoCase(name) != 0) { section = section->Next; } return section; } //==================================================================== // // FConfigFile :: RenameSection // //==================================================================== void FConfigFile::RenameSection (const char *oldname, const char *newname) const { FConfigSection *section = FindSection(oldname); if (section != NULL) { section->SectionName = newname; } } //==================================================================== // // FConfigFile :: FindEntry // //==================================================================== FConfigFile::FConfigEntry *FConfigFile::FindEntry ( FConfigFile::FConfigSection *section, const char *key) const { FConfigEntry *probe = section->RootEntry; while (probe != NULL && stricmp (probe->Key, key) != 0) { probe = probe->Next; } return probe; } //==================================================================== // // FConfigFile :: NewConfigSection // //==================================================================== FConfigFile::FConfigSection *FConfigFile::NewConfigSection (const char *name) { FConfigSection *section; section = FindSection (name); if (section == NULL) { section = new FConfigSection; section->RootEntry = NULL; section->LastEntryPtr = §ion->RootEntry; section->Next = NULL; section->SectionName = name; *LastSectionPtr = section; LastSectionPtr = §ion->Next; } return section; } //==================================================================== // // FConfigFile :: NewConfigEntry // //==================================================================== FConfigFile::FConfigEntry *FConfigFile::NewConfigEntry ( FConfigFile::FConfigSection *section, const char *key, const char *value) { FConfigEntry *entry; size_t keylen; keylen = strlen (key); entry = (FConfigEntry *)new char[sizeof(*section)+keylen]; entry->Value = NULL; entry->Next = NULL; memcpy (entry->Key, key, keylen); entry->Key[keylen] = 0; *(section->LastEntryPtr) = entry; section->LastEntryPtr = &entry->Next; entry->SetValue (value); return entry; } //==================================================================== // // FConfigFile :: LoadConfigFile // //==================================================================== void FConfigFile::LoadConfigFile () { FileReader file; bool succ; FileExisted = false; if (!file.OpenFile (PathName)) { return; } succ = ReadConfig (&file); FileExisted = succ; } //==================================================================== // // FConfigFile :: ReadConfig // //==================================================================== bool FConfigFile::ReadConfig (FileReader *file) { TArray readbuf; FConfigSection *section = NULL; ClearConfig (); while (ReadLine (readbuf, file) != NULL) { uint8_t *start = readbuf.Data(); uint8_t *equalpt; uint8_t *endpt; // Remove white space at start of line while (*start && *start <= ' ') { start++; } // Remove comment lines if (*start == '#' || (start[0] == '/' && start[1] == '/')) { continue; } // Remove white space at end of line endpt = start + strlen ((char*)start) - 1; while (endpt > start && *endpt <= ' ') { endpt--; } // Remove line feed '\n' character endpt[1] = 0; if (endpt <= start) continue; // Nothing here if (*start == '[') { // Section header if (*endpt == ']') *endpt = 0; section = NewConfigSection ((char*)start+1); } else if (section == NULL) { return false; } else { // Should be key=value equalpt = (uint8_t*)strchr ((char*)start, '='); if (equalpt != NULL && equalpt > start) { // Remove white space in front of = uint8_t *whiteprobe = equalpt - 1; while (whiteprobe > start && isspace(*whiteprobe)) { whiteprobe--; } whiteprobe[1] = 0; // Remove white space after = whiteprobe = equalpt + 1; while (*whiteprobe && isspace(*whiteprobe)) { whiteprobe++; } *(whiteprobe - 1) = 0; // Check for multi-line value if (whiteprobe[0] == '<' && whiteprobe[1] == '<' && whiteprobe[2] == '<' && whiteprobe[3] != '\0') { ReadMultiLineValue (file, section, (char*)start, (char*)whiteprobe + 3); } else { NewConfigEntry (section, (char*)start, (char*)whiteprobe); } } } } return true; } //==================================================================== // // FConfigFile :: ReadMultiLineValue // // Reads a multi-line value, with format as follows: // // key=<<>>ENDTAG // // The final ENDTAG must be on a line all by itself. // //==================================================================== FConfigFile::FConfigEntry *FConfigFile::ReadMultiLineValue(FileReader *file, FConfigSection *section, const char *key, const char *endtag) { TArray readbuf; FString value; size_t endlen = strlen(endtag); // Keep on reading lines until we reach a line that matches >>>endtag while (ReadLine(readbuf, file) != NULL) { // Does the start of this line match the endtag? if (readbuf[0] == '>' && readbuf[1] == '>' && readbuf[2] == '>' && strncmp((char*)readbuf.Data() + 3, endtag, endlen) == 0) { // Is there nothing but line break characters after the match? size_t i; for (i = endlen + 3; readbuf[i] != '\0'; ++i) { if (readbuf[i] != '\n' && readbuf[i] != '\r') { // Not a line break character break; } } if (readbuf[i] == '\0') { // We're done; strip the previous line's line breaks, since it's not part of the value. value.StripRight("\n\r"); } break; } // Append this line to the value. value << readbuf; } return NewConfigEntry(section, key, value); } //==================================================================== // // FConfigFile :: ReadLine // //==================================================================== uint8_t *FConfigFile::ReadLine(TArray& string, FileReader* file) const { uint8_t byte; string.Clear(); while (file->Read(&byte, 1)) { if (byte == 0) { break; } if (byte != '\r') { string.Push(byte); if (byte == '\n') { break; } } } if (string.Size() == 0) return nullptr; string.Push(0); return string.Data(); } //==================================================================== // // FConfigFile :: WriteConfigFile // //==================================================================== bool FConfigFile::WriteConfigFile () const { if (!OkayToWrite && FileExisted) { // Pretend it was written anyway so that the user doesn't get // any "config not written" notifications, but only if the file // already existed. Otherwise, let it write out a default one. return true; } FileWriter *file = FileWriter::Open (PathName); FConfigSection *section; FConfigEntry *entry; if (file == NULL) return false; WriteCommentHeader (file); section = Sections; while (section != NULL) { entry = section->RootEntry; if (section->Note.IsNotEmpty()) { file->Write (section->Note.GetChars(), section->Note.Len()); } file->Printf ("[%s]\n", section->SectionName.GetChars()); while (entry != NULL) { if (strpbrk(entry->Value, "\r\n") == NULL) { // Single-line value file->Printf ("%s=%s\n", entry->Key, entry->Value); } else { // Multi-line value const char *endtag = GenerateEndTag(entry->Value); file->Printf ("%s=<<<%s\n%s\n>>>%s\n", entry->Key, endtag, entry->Value, endtag); } entry = entry->Next; } section = section->Next; file->Write ("\n", 1); } delete file; return true; } //==================================================================== // // FConfigFile :: GenerateEndTag // // Generates a terminator sequence for multi-line values that does // not appear anywhere in the value. // //==================================================================== const char *FConfigFile::GenerateEndTag(const char *value) { static const char Base64Table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._"; static char EndTag[25] = "EOV-"; // Try different 20-character sequences until we find one that // isn't in the value. We create the sequences by generating two // 64-bit random numbers and Base64 encoding the first 15 bytes // from them. union { uint16_t rand_num[8]; uint8_t rand_bytes[16]; }; do { for (auto &r : rand_num) r = (uint16_t)rand(); for (int i = 0; i < 5; ++i) { //uint32_t three_bytes = (rand_bytes[i*3] << 16) | (rand_bytes[i*3+1] << 8) | (rand_bytes[i*3+2]); // ??? EndTag[4+i*4 ] = Base64Table[rand_bytes[i*3] >> 2]; EndTag[4+i*4+1] = Base64Table[((rand_bytes[i*3] & 3) << 4) | (rand_bytes[i*3+1] >> 4)]; EndTag[4+i*4+2] = Base64Table[((rand_bytes[i*3+1] & 15) << 2) | (rand_bytes[i*3+2] >> 6)]; EndTag[4+i*4+3] = Base64Table[rand_bytes[i*3+2] & 63]; } } while (strstr(value, EndTag) != NULL); return EndTag; } //==================================================================== // // FConfigFile :: WriteCommentHeader // // Override in a subclass to write a header to the config file. // //==================================================================== void FConfigFile::WriteCommentHeader (FileWriter *file) const { } //==================================================================== // // FConfigFile :: FConfigEntry :: SetValue // //==================================================================== void FConfigFile::FConfigEntry::SetValue (const char *value) { if (Value != NULL) { delete[] Value; } Value = new char[strlen (value)+1]; strcpy (Value, value); } //==================================================================== // // FConfigFile :: GetPosition // // Populates a struct with the current position of the parse cursor. // //==================================================================== void FConfigFile::GetPosition (FConfigFile::Position &pos) const { pos.Section = CurrentSection; pos.Entry = CurrentEntry; } //==================================================================== // // FConfigFile :: SetPosition // // Sets the parse cursor to a previously retrieved position. // //==================================================================== void FConfigFile::SetPosition (const FConfigFile::Position &pos) { CurrentSection = pos.Section; CurrentEntry = pos.Entry; } //==================================================================== // // FConfigFile :: SetSectionNote // // Sets a comment note to be inserted into the INI verbatim directly // ahead of the section. Notes are lost when the INI is read so must // be explicitly set to be maintained. // //==================================================================== void FConfigFile::SetSectionNote(const char *section, const char *note) { SetSectionNote(FindSection(section), note); } void FConfigFile::SetSectionNote(const char *note) { SetSectionNote(CurrentSection, note); } void FConfigFile::SetSectionNote(FConfigSection *section, const char *note) { if (section != NULL) { if (note == NULL) { note = ""; } section->Note = note; } }