#include "config_parse.hpp" // config_parse.cpp - Framework for per-server config parsers. // // Copyright © 2014 Ben Longbons // // This file is part of The Mana World (Athena server) // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . #include #include "../strings/xstring.hpp" #include "../strings/zstring.hpp" #include "../compat/borrow.hpp" #include "../io/cxxstdio.hpp" #include "../io/extract.hpp" #include "../io/line.hpp" #include "version.hpp" #include "../poison.hpp" namespace tmwa { bool is_comment(XString line) { return not line or line.startswith("//"_s); } template static bool do_split(io::Spanned line, io::Spanned *key, io::Spanned *value) { typename ZS::iterator colon = std::find(line.data.begin(), line.data.end(), ':'); if (colon == line.data.end()) return false; key->data = line.data.xislice_h(colon); key->span = line.span; key->span.end.column = key->span.begin.column + key->data.size() - 1; ++colon; value->data = line.data.xislice_t(colon); value->span = line.span; value->span.begin.column = value->span.end.column - value->data.size() + 1; return true; } template static io::Spanned do_lstrip(io::Spanned value) { io::Spanned rv; rv.data = value.data.lstrip(); rv.span = value.span; rv.span.begin.column += (value.data.size() - rv.data.size()); return rv; } static io::Spanned do_rstrip(io::Spanned value) { io::Spanned rv; rv.data = value.data.rstrip(); rv.span = value.span; rv.span.end.column -= (value.data.size() - rv.data.size()); return rv; } static io::Spanned do_strip(io::Spanned value) { return do_lstrip(do_rstrip(value)); } template inline bool config_split_impl(io::Spanned line, io::Spanned *key, io::Spanned *value) { // unconditionally fail if line contains control characters if (std::find_if(line.data.begin(), line.data.end(), [](unsigned char c) { return c < ' '; } ) != line.data.end()) return false; if (!do_split(line, key, value)) return false; *key = do_strip(*key); *value = do_lstrip(*value); return true; } // eventually this should go away // currently the only real offenders are io::FD::open and *PRINTF bool config_split(io::Spanned line, io::Spanned *key, io::Spanned *value) { return config_split_impl(line, key, value); } static bool check_version(io::Spanned avers, Borrowed valid) { enum { GE, LE, GT, LT } cmp; if (avers.data.startswith(">="_s)) { cmp = GE; avers.data = avers.data.xslice_t(2); avers.span.begin.column += 2; } else if (avers.data.startswith('>')) { cmp = GT; avers.data = avers.data.xslice_t(1); avers.span.begin.column += 1; } else if (avers.data.startswith("<="_s)) { cmp = LE; avers.data = avers.data.xslice_t(2); avers.span.begin.column += 2; } else if (avers.data.startswith('<')) { cmp = LT; avers.data = avers.data.xslice_t(1); avers.span.begin.column += 1; } else { avers.span.error("Version check must begin with one of: '>=', '>', '<=', '<'"_s); *valid = false; return false; } Version vers; if (!extract(avers.data, &vers)) { avers.span.error("Bad value"_s); *valid = false; return false; } switch (cmp) { case GE: return CURRENT_VERSION >= vers; case GT: return CURRENT_VERSION > vers; case LE: return CURRENT_VERSION <= vers; case LT: return CURRENT_VERSION < vers; } abort(); } /// Master config parser. This handles 'import' and 'version-ge' etc. /// Then it defers to the inferior parser for a line it does not understand. /// /// Note: old-style 'version-ge: 1.2.3' etc apply to the rest of the file, but /// new-style 'version: >= 1.2.3' apply only up to the next 'version:' bool load_config_file(ZString filename, ConfigItemParser slave) { io::LineReader in(filename); if (!in.is_open()) { PRINTF("Unable to open file: %s\n"_fmt, filename); return false; } io::Line line_; bool rv = true; bool good_version = true; while (in.read_line(line_)) { if (is_comment(line_.text)) continue; auto line = io::respan(line_.to_span(), ZString(line_.text)); io::Spanned key; io::Spanned value; if (!config_split(line, &key, &value)) { line.span.error("Bad config line"_s); rv = false; continue; } if (key.data == "version"_s) { if (value.data == "all"_s) { good_version = true; } else { good_version = true; while (good_version && value.data) { ZString::iterator it = std::find(value.data.begin(), value.data.end(), ' '); io::Spanned value_head; value_head.data = value.data.xislice_h(it); value_head.span = value.span; value.data = value.data.xislice_t(it).lstrip(); value_head.span.end.column = value_head.span.begin.column + value_head.data.size() - 1; value.span.begin.column = value.span.end.column - value.data.size() + 1; good_version &= check_version(value_head, borrow(rv)); } } continue; } if (!good_version) { continue; } if (key.data == "import"_s) { if (!load_config_file(value.data, slave)) { value.span.error("Failed to include file"_s); rv = false; } continue; } else if (key.data == "version-lt"_s) { Version vers; if (!extract(value.data, &vers)) { value.span.error("Bad value"_s); rv = false; continue; } if (CURRENT_VERSION < vers) continue; break; } else if (key.data == "version-le"_s) { Version vers; if (!extract(value.data, &vers)) { rv = false; value.span.error("Bad value"_s); continue; } if (CURRENT_VERSION <= vers) continue; break; } else if (key.data == "version-gt"_s) { Version vers; if (!extract(value.data, &vers)) { rv = false; value.span.error("Bad value"_s); continue; } if (CURRENT_VERSION > vers) continue; break; } else if (key.data == "version-ge"_s) { Version vers; if (!extract(value.data, &vers)) { rv = false; value.span.error("Bad value"_s); continue; } if (CURRENT_VERSION >= vers) continue; break; } else { rv &= slave(key, value); } // nothing to see here, move along } return rv; } } // namespace tmwa