/* * Copyright (C) 1996-2023 The Squid Software Foundation and contributors * * Squid software is distributed under GPLv2+ license and includes * contributions from numerous individuals and organizations. * Please see the COPYING and CONTRIBUTORS files for details. */ #include "squid.h" #include "acl/Options.h" #include "ConfigParser.h" #include "Debug.h" #include "sbuf/Stream.h" #include #include namespace Acl { /// low-level parser that extracts but does not interpret ACL options class OptionExtractor { public: /// parses the next option and fills public members with its details /// \returns whether option extraction was successful bool extractOne(); /* extracted option details (after successful extraction */ SBuf name; ///< extracted option name, including dash(es) bool hasValue = false; ///< whether the option has a value (-x=value) const SBuf &value() const; ///< extracted option value (requires hasValue) protected: bool advance(); void extractWhole(); void extractShort(); private: SBuf prefix_; ///< option name(s), including leading dash(es) SBuf value_; ///< the last seen value of some option SBuf::size_type letterPos_ = 0; ///< letter position inside an -xyz sequence bool sawValue_ = false; ///< the current option sequence had a value }; /// parses/validates/stores ACL options; skips/preserves parameter flags class OptionsParser { public: OptionsParser(const Options &options, const ParameterFlags &flags); // fill previously supplied options container, throwing on errors void parse(); private: const Option *findOption(/* const */ SBuf &rawName); /// ACL parameter flags in parsing order typedef std::vector Names; /// parsed ACL parameter flags that must be preserved for ACLData::parse() static Names flagsToSkip; const Options &options_; ///< caller-supported, linked options const ParameterFlags ¶meterFlags_; ///< caller-supported parameter flags }; } // namespace Acl /* Acl::OptionNameCmp */ bool Acl::OptionNameCmp::operator()(const OptionName a, const OptionName b) const { return strcmp(a, b) < 0; } /* Acl::OptionExtractor */ const SBuf & Acl::OptionExtractor::value() const { Must(hasValue); return value_; } bool Acl::OptionExtractor::extractOne() { if (!prefix_.isEmpty()) { extractShort(); // continue with the previously extracted flags return true; } if (!advance()) return false; // end of options (and, possibly, the whole "acl" directive) if (prefix_.length() < 2) throw TexcHere(ToSBuf("truncated(?) ACL flag: ", prefix_)); // single - or + if (prefix_[0] == '-' && prefix_[1] == '-') { if (prefix_.length() == 2) return false; // skipped "--", an explicit end-of-options marker extractWhole(); return true; } if (prefix_.length() == 2) { // common trivial case: -x or +y extractWhole(); return true; } // -xyz or +xyz letterPos_ = 1; extractShort(); return true; } /// extracts a token with the next option/flag(s) or returns false bool Acl::OptionExtractor::advance() { const char *next = ConfigParser::PeekAtToken(); if (!next) return false; // end of the "acl" line const char nextChar = *next; if (!(nextChar == '-' || nextChar == '+')) return false; // start of ACL parameters sawValue_ = strchr(next, '='); // TODO: Make ConfigParser reject '^=.*' tokens if (sawValue_) { char *rawPrefix = nullptr; char *rawValue = nullptr; if (!ConfigParser::NextKvPair(rawPrefix, rawValue)) throw TexcHere(ToSBuf("Malformed acl option=value: ", next)); prefix_.assign(rawPrefix); value_.assign(rawValue); } else { prefix_.assign(next); ConfigParser::NextToken(); // consume what we have peeked at } return true; } /// handles -x[=option] or --foo[=option] void Acl::OptionExtractor::extractWhole() { debugs(28, 8, "from " << prefix_ << " value: " << sawValue_); hasValue = sawValue_; name = prefix_; prefix_.clear(); } /// handles one flag letter inside an -xyx[=option] or +xyz[=option] sequence void Acl::OptionExtractor::extractShort() { debugs(28, 8, "from " << prefix_ << " at " << letterPos_ << " value: " << sawValue_); name.assign(prefix_.rawContent(), 1); // leading - or + name.append(prefix_.at(letterPos_++)); if (letterPos_ >= prefix_.length()) { // got last flag in the sequence hasValue = sawValue_; prefix_.clear(); } else { hasValue = false; } } /* Acl::OptionsParser */ // being "static" is an optimization to avoid paying for vector creation/growth Acl::OptionsParser::Names Acl::OptionsParser::flagsToSkip; Acl::OptionsParser::OptionsParser(const Options &options, const ParameterFlags &flags): options_(options), parameterFlags_(flags) { } const Acl::Option * Acl::OptionsParser::findOption(/* const */ SBuf &rawNameBuf) { // TODO: new std::map::find() in C++14 does not require this conversion const auto rawName = rawNameBuf.c_str(); const auto optionPos = options_.find(rawName); if (optionPos != options_.end()) return optionPos->second; const auto flagPos = parameterFlags_.find(rawName); if (flagPos != parameterFlags_.end()) { flagsToSkip.push_back(*flagPos); // *flagPos is permanent unlike rawName return nullptr; } throw TexcHere(ToSBuf("unsupported ACL option: ", rawNameBuf)); } void Acl::OptionsParser::parse() { flagsToSkip.clear(); OptionExtractor oex; while (oex.extractOne()) { /* const */ auto rawName = oex.name; if (const Option *optionPtr = findOption(rawName)) { const Option &option = *optionPtr; if (option.configured()) debugs(28, 7, "acl uses multiple " << rawName << " options"); switch (option.valueExpectation) { case Option::valueNone: if (oex.hasValue) throw TexcHere(ToSBuf("unexpected value for an ACL option: ", rawName, '=', oex.value())); option.configureDefault(); break; case Option::valueRequired: if (!oex.hasValue) throw TexcHere(ToSBuf("missing required value for ACL option ", rawName)); option.configureWith(oex.value()); break; case Option::valueOptional: if (oex.hasValue) option.configureWith(oex.value()); else option.configureDefault(); break; } } // else skip supported parameter flag } /* hack: regex code wants to parse all -i and +i flags itself */ for (const auto name: flagsToSkip) ConfigParser::TokenPutBack(name); } void Acl::ParseFlags(const Options &options, const ParameterFlags &flags) { OptionsParser parser(options, flags); parser.parse(); } const Acl::Options & Acl::NoOptions() { static const Options none; return none; } const Acl::ParameterFlags & Acl::NoFlags() { static const ParameterFlags none; return none; } std::ostream & operator <<(std::ostream &os, const Acl::Option &option) { if (option.valued()) { os << '='; option.print(os); } return os; } std::ostream & operator <<(std::ostream &os, const Acl::Options &options) { for (const auto pos: options) { assert(pos.second); const auto &option = *pos.second; if (option.configured()) os << pos.first << option; } // TODO: Remember "--" presence and print that delimiter when present. // Detecting its need is difficult because parameter flags start with "-". return os; }