#include "conf.h" #include <library/cpp/logger/all.h> #include <library/cpp/charset/ci_string.h> #include <util/generic/algorithm.h> #include <util/stream/file.h> #include <util/string/split.h> #include <util/string/type.h> #include <cstdlib> void TYandexConfig::Clear() { delete[] FileData; FileData = nullptr; CurrentMemoryPtr = nullptr; Len = 0; while (!CurSections.empty()) CurSections.pop(); for (size_t i = 0; i < AllSections.size(); i++) { if (AllSections[i]->Owner) delete AllSections[i]->Cookie; delete AllSections[i]; } AllSections.clear(); Errors.clear(); EndLines.clear(); ConfigPath.remove(); } void TYandexConfig::PrintErrors(TLog* Log) { size_t sz = Errors.size(); if (sz) { Log->AddLog("Processing of \'%s\':\n", ConfigPath.data()); for (size_t i = 0; i < sz; i++) *Log << Errors[i]; Errors.clear(); } } void TYandexConfig::PrintErrors(TString& Err) { size_t sz = Errors.size(); if (sz) { char buf[512]; snprintf(buf, 512, "Processing of \'%s\':\n", ConfigPath.data()); Err += buf; for (size_t i = 0; i < sz; i++) Err += Errors[i]; Errors.clear(); } } void TYandexConfig::ReportError(const char* ptr, const char* err, bool warning) { if (ptr) { char buf[1024]; int line = 0, col = 0; if (!EndLines.empty()) { TVector<const char*>::iterator I = UpperBound(EndLines.begin(), EndLines.end(), ptr); if (I == EndLines.end()) I = EndLines.end() - 1; line = int(I - EndLines.begin()); if (line) I--; col = int(ptr - (*I)); } if (warning) snprintf(buf, 1024, "Warning at line %d, col %d: %s.\n", line, col, err); else snprintf(buf, 1024, "Error at line %d, col %d: %s.\n", line, col, err); Errors.push_back(buf); } else Errors.push_back(err); } void TYandexConfig::ReportError(const char* ptr, bool warning, const char* format, ...) { va_list args; va_start(args, format); char buf[512]; vsnprintf(buf, 512, format, args); ReportError(ptr, buf, warning); } bool TYandexConfig::Read(const TString& path) { assert(FileData == nullptr); ConfigPath = path; //read the file to memory TFile doc(path, OpenExisting | RdOnly); if (!doc.IsOpen()) { Errors.push_back(TString("can't open file ") + path + "\n"); return false; } Len = (ui32)doc.GetLength(); FileData = new char[Len + 1]; doc.Load(FileData, Len); FileData[Len] = 0; doc.Close(); return PrepareLines(); } bool TYandexConfig::ReadMemory(const TStringBuf& buffer, const char* configPath) { assert(FileData == nullptr); if (configPath != nullptr) { ConfigPath = configPath; } Len = (ui32)buffer.size(); FileData = new char[Len + 1]; memcpy(FileData, buffer.data(), Len); FileData[Len] = 0; return PrepareLines(); } bool TYandexConfig::ReadMemory(const char* buffer, const char* configPath) { Y_ASSERT(buffer); return ReadMemory(TStringBuf(buffer), configPath); } bool TYandexConfig::PrepareLines() { //scan line breaks EndLines.push_back(FileData - 1); CurrentMemoryPtr = FileData; while (*CurrentMemoryPtr) { // Are you in a great hurry? I am not... :-) if (iscntrl((unsigned char)*CurrentMemoryPtr) && !isspace((unsigned char)*CurrentMemoryPtr)) { ReportError(CurrentMemoryPtr, "it's a binary file"); return false; } if (*CurrentMemoryPtr++ == '\n') EndLines.push_back(CurrentMemoryPtr - 1); } EndLines.push_back(CurrentMemoryPtr); // convert simple comments inceptive with '#' or '!' or ';' to blanks ProcessComments(); //convert the XML comments to blanks char* endptr = nullptr; CurrentMemoryPtr = strstr(FileData, "<!--"); while (CurrentMemoryPtr != nullptr) { endptr = strstr(CurrentMemoryPtr, "-->"); if (endptr) { endptr += 3; while (CurrentMemoryPtr != endptr) *CurrentMemoryPtr++ = ' '; CurrentMemoryPtr = strstr(endptr, "<!--"); } else { ReportError(CurrentMemoryPtr, "unclosed comment"); return false; } } return true; } bool TYandexConfig::ParseMemory(const TStringBuf& buffer, bool processDirectives, const char* configPath) { if (!ReadMemory(buffer, configPath)) return false; return ProcessRoot(processDirectives); } bool TYandexConfig::ParseMemory(const char* buffer, bool process_directives, const char* configPath) { if (!ReadMemory(buffer, configPath)) return false; return ProcessRoot(process_directives); } bool TYandexConfig::Parse(const TString& path, bool process_directives) { if (!Read(path)) return false; return ProcessRoot(process_directives); } bool TYandexConfig::ProcessRoot(bool process_directives) { CurrentMemoryPtr = FileData; // Add the unnamed root section assert(AllSections.empty()); AllSections.push_back(new Section); AllSections.back()->Parent = AllSections.back(); if (!OnBeginSection(*AllSections.back())) return false; CurSections.push(AllSections.back()); bool ret = ProcessAll(process_directives) && OnEndSection(*CurSections.top()); CurSections.pop(); while (!CurSections.empty()) { // There are some not closed main sections. OnEndSection(*CurSections.top()); CurSections.pop(); } return ret; } bool TYandexConfig::FindEndOfSection(const char* SecName, const char* begin, char*& endsec, char*& endptr) { // find "</SecName" and set '<' and '>' to '\0' char* p = (char*)begin; char* EndName = (char*)alloca(strlen(SecName) + 3); *EndName = '<'; *(EndName + 1) = '/'; strcpy(EndName + 2, SecName); while (p && p < FileData + Len) { p = strstr(p, EndName); if (p == nullptr) { ReportError(SecName, "mismatched section"); return false; } endsec = p; p += strlen(SecName) + 2; if (*p != '>' && !isspace((unsigned char)*p)) continue; // it's a prefix but not the required section-end endptr = strchr(p, '>'); if (endptr == nullptr) { ReportError(p, "mismatched \'<\'"); return false; } *endptr = 0; *endsec++ = 0; *endsec++ = 0; p = endptr - 1; while (p > endsec && isspace((unsigned char)*p)) *p-- = 0; return true; } ReportError(SecName, "mismatched section"); return false; } bool TYandexConfig::ParseSection(const char* SecName, const char* idname, const char* idvalue) { assert(FileData); // Call Read() firstly size_t slen = strlen(SecName); CurrentMemoryPtr = FileData; assert(AllSections.empty()); AllSections.push_back(new Section); AllSections.back()->Parent = AllSections.back(); if (!OnBeginSection(*AllSections.back())) return false; CurSections.push(AllSections.back()); bool ret = false; while (CurrentMemoryPtr < FileData + Len) { // May be *CurrentMemoryPtr == 0 if FileData has been parsed. while (*CurrentMemoryPtr++ != '<' && CurrentMemoryPtr < FileData + Len) { } if (strnicmp(CurrentMemoryPtr, SecName, slen) == 0) { char* p = CurrentMemoryPtr + slen; char* endptr = strchr(p, '>'); if (endptr == nullptr) continue; // a section may be parsed if (*p != '>' && *p != '/' && !isspace((unsigned char)*p)) continue; // required section must match the name and may not be parsed //parse now *endptr = 0; bool endnow = false; p = endptr - 1; if (*p == '/') { *p-- = 0; endnow = true; } while (isspace((unsigned char)*p)) *p-- = 0; char *body = endptr + 1, *endsec = nullptr; if (!ProcessBeginSection()) break; // false if (!endnow) { if (!FindEndOfSection(CurSections.top()->Name, body, endsec, endptr)) break; // false } if (idname && idvalue) { SectionAttrs::iterator I = CurSections.top()->Attrs.find(idname); if (I != CurSections.top()->Attrs.end()) { if (stricmp((*I).second, idvalue) != 0) { CurrentMemoryPtr = endptr + 1; CurSections.pop(); assert(AllSections.size() == 2); Section* Last = AllSections.back(); assert(Last->Parent->Next == nullptr); assert(Last->Parent->Child == Last); assert(Last->Next == nullptr); Last->Parent->Child = nullptr; delete Last; AllSections.pop_back(); continue; } } else { if (*idvalue != 0) { CurrentMemoryPtr = endptr + 1; CurSections.pop(); assert(AllSections.size() == 2); Section* Last = AllSections.back(); assert(Last->Parent->Next == nullptr); assert(Last->Parent->Child == Last); assert(Last->Next == nullptr); Last->Parent->Child = nullptr; delete Last; AllSections.pop_back(); continue; } } } if (!OnBeginSection(*CurSections.top())) break; // false if (!endnow) { CurrentMemoryPtr = body; if (!ProcessAll(true)) break; // false CurrentMemoryPtr = endsec; } if (!OnEndSection(*CurSections.top())) break; // false if (!ProcessEndSection()) break; // false CurrentMemoryPtr = endptr + 1; ret = true; break; // section found and processed } } CurSections.pop(); while (!CurSections.empty()) { OnEndSection(*CurSections.top()); CurSections.pop(); } return ret; } // Parse some chunk of memory ended by \0 bool TYandexConfig::ProcessAll(bool process_directives) { char* endptr; while (CurrentMemoryPtr && *CurrentMemoryPtr) { switch (*CurrentMemoryPtr) { case ' ': case '\t': case '\r': case '\n': CurrentMemoryPtr++; break; case '>': ReportError(CurrentMemoryPtr, "mismatched \'>\'"); return false; break; //It is not XML. We need (\n[\n\r\t ]*<) for the section started. case '<': { endptr = strchr(CurrentMemoryPtr, '>'); if (endptr == nullptr) { ReportError(CurrentMemoryPtr, "mismatched \'<\'"); return false; } *endptr = 0; char* p = CurrentMemoryPtr + 1; if (*p != '/' && !isalpha(*p)) { ReportError(p, "invalid character"); return false; } bool endnow = false; p = endptr - 1; if (*p == '/') { *p-- = 0; endnow = true; } while (isspace((unsigned char)*p)) *p-- = 0; *CurrentMemoryPtr++ = 0; if (*CurrentMemoryPtr == '/') { *CurrentMemoryPtr++ = 0; if (!OnEndSection(*CurSections.top())) return false; if (!ProcessEndSection()) return false; } else { if (!ProcessBeginSection()) return false; if (!OnBeginSection(*CurSections.top())) return false; } if (endnow) { if (!OnEndSection(*CurSections.top())) return false; if (!ProcessEndSection()) return false; } CurrentMemoryPtr = endptr + 1; } break; default: if (process_directives && CurSections.top()->Cookie) { if (!ProcessDirective()) return false; } else { CurrentMemoryPtr = strchr(CurrentMemoryPtr, '\n'); if (!CurrentMemoryPtr) return true; // the end of file } break; } } return true; } void TYandexConfig::ProcessLineBreak(char*& LineBreak, char toChange) { assert(*LineBreak == '\n'); assert(toChange == ' ' || toChange == 0); if (toChange == 0) { char* p = LineBreak - 1; while ((*p == ' ' || *p == '\r' || *p == '\t') && p >= FileData) *p-- = 0; } *LineBreak++ = toChange; } // convert simple comments inceptive with '#' or '!' or ';' to blanks void TYandexConfig::ProcessComments() { assert(FileData); // Call Read() firstly char* endptr = FileData; while (true) { //process the leading blanks for the next endptr += strspn(endptr, " \t\r"); //process the comment-line if (*endptr == '!' || *endptr == '#' || *endptr == ';') { while (*endptr != 0 && *endptr != '\n') *endptr++ = ' '; if (*endptr == '\n') { endptr++; continue; } else // may be the last line in file break; } //process the regular line endptr = strchr(endptr, '\n'); if (endptr) endptr++; else // may be the last line in file break; } } bool TYandexConfig::ProcessDirective() { char* endptr = CurrentMemoryPtr; //find the end of the directive while (true) { //process the leading blanks for the next endptr += strspn(endptr, " \t\r"); //process the blank line if (*endptr == '\n') { ProcessLineBreak(endptr, ' '); continue; } //process the regular line endptr = strchr(endptr, '\n'); if (!endptr) // may be the last line in file break; //may be continue at the next line char* p = endptr - 1; while ((*p == ' ' || *p == '\r' || *p == '\t') && p > FileData) p--; if (*p == '\\') { *p = ' '; ProcessLineBreak(endptr, ' '); } else { ProcessLineBreak(endptr, 0); break; } } assert(endptr == nullptr || *endptr == 0 || *(endptr - 1) == 0); //split the directive into key and value char* args = CurrentMemoryPtr; args += strcspn(CurrentMemoryPtr, " \t\r=:"); if (*args) { bool olddelimiter = (*args == ':' || *args == '='); *args++ = 0; args += strspn(args, " \t\r"); if ((*args == ':' || *args == '=') && !olddelimiter) { args++; args += strspn(args, " \t\r"); } } //add the directive at last assert(!CurSections.empty()); Section* sec = CurSections.top(); if (!AddKeyValue(*sec, CurrentMemoryPtr, args)) return false; CurrentMemoryPtr = endptr; return true; } void TYandexConfig::AddSection(Section* sec) { assert(sec && sec->Parent); if (sec->Parent->Child == nullptr) sec->Parent->Child = sec; else { Section** pNext = &sec->Parent->Child->Next; while (*pNext) pNext = &(*pNext)->Next; *pNext = sec; } AllSections.push_back(sec); } bool TYandexConfig::ProcessBeginSection() { assert(!CurSections.empty()); Section* sec = new Section; sec->Parent = CurSections.top(); AddSection(sec); char* endptr = CurrentMemoryPtr; endptr += strcspn(endptr, " \t\r\n"); if (endptr && *endptr) *endptr++ = 0; AllSections.back()->Name = CurrentMemoryPtr; //find the attributes const char* AttrName = nullptr; bool EqPassed = false; while (endptr && *endptr) { switch (*endptr) { case ' ': case '\t': case '\r': case '\n': endptr++; break; case '=': if (!AttrName || EqPassed) { ReportError(endptr, "invalid character"); return false; } else { EqPassed = true; *endptr++ = 0; } break; case '\"': case '\'': case '`': if (!AttrName || !EqPassed) { ReportError(endptr, "invalid character"); return false; } { char* endattr = strchr(endptr + 1, *endptr); if (!endattr) { ReportError(endptr, "mismatched character"); return false; } *endattr++ = 0; AllSections.back()->Attrs[AttrName] = endptr + 1; AttrName = nullptr; EqPassed = false; endptr = endattr; } if (!(*endptr == 0 || isspace((unsigned char)*endptr))) { ReportError(endptr, "invalid character"); return false; } break; default: if (AttrName || EqPassed) { ReportError(endptr, "invalid character"); return false; } AttrName = endptr; endptr += strcspn(endptr, " \t\r\n="); if (*endptr == 0) { ReportError(AttrName, "invalid characters"); return false; } if (*endptr == '=') EqPassed = true; *endptr++ = 0; break; } } CurSections.push(AllSections.back()); return true; } bool TYandexConfig::ProcessEndSection() { assert(!CurSections.empty()); Section* sec = CurSections.top(); if (sec->Name && CurrentMemoryPtr && strcmp(sec->Name, CurrentMemoryPtr) != 0) { ReportError(CurrentMemoryPtr, "mismatched open element"); return false; } CurSections.pop(); return true; } bool TYandexConfig::AddKeyValue(Section& sec, const char* key, const char* value) { assert(sec.Cookie); if (!sec.Cookie->AddKeyValue(key, value)) { if (*sec.Name) ReportError(key, true, "section \'%s\' does not allow directive \'%s\'. The directive will be ignored", sec.Name, key); else ReportError(key, true, "directive \'%s\' not allowed here. It will be ignored", key); } return true; } bool TYandexConfig::OnBeginSection(Section& secnew) { if (*secnew.Name) { ReportError(secnew.Name, false, "section \'%s\' not allowed here", secnew.Name); return false; } return true; } bool TYandexConfig::OnEndSection(Section& sec) { if (sec.Cookie) { if (!sec.Cookie->CheckOnEnd(*this, sec)) { if (sec.Owner) { delete sec.Cookie; sec.Cookie = nullptr; } return false; } } return true; } TYandexConfig::Section* TYandexConfig::GetFirstChild(const char* Name, TYandexConfig::Section* CurSection /*= NULL*/) { if (CurSection == nullptr) CurSection = GetRootSection(); CurSection = CurSection->Child; while (CurSection) { if (CurSection->Parsed()) { if (stricmp(CurSection->Name, Name) == 0) break; } CurSection = CurSection->Next; } return CurSection; } void TYandexConfig::PrintSectionConfig(const TYandexConfig::Section* section, IOutputStream& os, bool printNextSection) { if (section == nullptr || !section->Parsed()) return; bool hasName = section->Name && *section->Name; if (hasName) { os << "<" << section->Name; for (const auto& attr : section->Attrs) { os << " " << attr.first << "=\"" << attr.second << "\""; } os << ">\n"; } for (const auto& iter : section->GetDirectives()) { if (iter.second != nullptr && *iter.second && !IsSpace(iter.second)) { os << iter.first; os << " " << iter.second << "\n"; } } if (section->Child) { PrintSectionConfig(section->Child, os); } if (hasName) os << "</" << section->Name << ">\n"; if (printNextSection && section->Next) { PrintSectionConfig(section->Next, os); } } void TYandexConfig::PrintConfig(IOutputStream& os) const { const Section* sec = GetRootSection(); return PrintSectionConfig(sec, os); } //********************************************************************************************* bool TYandexConfig::Directives::CheckOnEnd(TYandexConfig&, TYandexConfig::Section&) { return true; } bool TYandexConfig::Directives::AddKeyValue(const TString& key, const char* value) { iterator I = find(key); if (I == end()) { if (strict) return false; else I = insert(value_type(key, nullptr)).first; } (*I).second = value; return true; } bool TYandexConfig::Directives::GetValue(TStringBuf key, TString& value) const { const_iterator I = find(key); if (I == end()) { if (strict) { ythrow yexception() << "key " << key << " not declared"; } return false; } if ((*I).second == nullptr) { return false; } value = (*I).second; return true; } bool TYandexConfig::Directives::GetNonEmptyValue(TStringBuf key, TString& value) const { TString tempValue; GetValue(key, tempValue); if (tempValue) { value = tempValue; return true; } return false; } bool TYandexConfig::Directives::GetValue(TStringBuf key, bool& value) const { TString tmp; if (GetValue(key, tmp)) { // IsTrue won't return true on empty strings anymore value = !tmp || IsTrue(tmp); return true; } return false; } bool TYandexConfig::Directives::FillArray(TStringBuf key, TVector<TString>& values) const { const_iterator I = find(key); if (I == end()) { return false; } if ((*I).second != nullptr) { Split((*I).second, " ,\t\r", values); return true; } return false; } void TYandexConfig::Directives::Clear() { if (strict) { clear(); } else { for (auto& I : *this) I.second = nullptr; } } TYandexConfig::TSectionsMap TYandexConfig::Section::GetAllChildren() const { TSectionsMap result; if (Child == nullptr) return result; Section* curSection = Child; while (curSection) { result.insert(TMultiMap<TCiString, Section*>::value_type(curSection->Name, curSection)); curSection = curSection->Next; } return result; }