]> git.ipfire.org Git - thirdparty/squid.git/blame - src/acl/Options.cc
Source Format Enforcement (#532)
[thirdparty/squid.git] / src / acl / Options.cc
CommitLineData
4eac3407 1/*
77b1029d 2 * Copyright (C) 1996-2020 The Squid Software Foundation and contributors
4eac3407
CT
3 *
4 * Squid software is distributed under GPLv2+ license and includes
5 * contributions from numerous individuals and organizations.
6 * Please see the COPYING and CONTRIBUTORS files for details.
7 */
8
9#include "squid.h"
10#include "acl/Options.h"
11#include "ConfigParser.h"
12#include "Debug.h"
13#include "sbuf/Stream.h"
14
15#include <iostream>
16#include <vector>
17
18namespace Acl {
19
20/// low-level parser that extracts but does not interpret ACL options
21class OptionExtractor
22{
23public:
24 /// parses the next option and fills public members with its details
25 /// \returns whether option extraction was successful
26 bool extractOne();
27
28 /* extracted option details (after successful extraction */
29 SBuf name; ///< extracted option name, including dash(es)
30 bool hasValue = false; ///< whether the option has a value (-x=value)
31 const SBuf &value() const; ///< extracted option value (requires hasValue)
32
33protected:
34 bool advance();
35 void extractWhole();
36 void extractShort();
37
38private:
39 SBuf prefix_; ///< option name(s), including leading dash(es)
40 SBuf value_; ///< the last seen value of some option
41 SBuf::size_type letterPos_ = 0; ///< letter position inside an -xyz sequence
42 bool sawValue_ = false; ///< the current option sequence had a value
43};
44
45/// parses/validates/stores ACL options; skips/preserves parameter flags
46class OptionsParser
47{
48public:
49 OptionsParser(const Options &options, const ParameterFlags &flags);
50
51 // fill previously supplied options container, throwing on errors
52 void parse();
53
54private:
55 const Option *findOption(/* const */ SBuf &rawName);
56
57 /// ACL parameter flags in parsing order
58 typedef std::vector<OptionName> Names;
59 /// parsed ACL parameter flags that must be preserved for ACLData::parse()
60 static Names flagsToSkip;
61
62 const Options &options_; ///< caller-supported, linked options
63 const ParameterFlags &parameterFlags_; ///< caller-supported parameter flags
64};
65
66} // namespace Acl
67
68/* Acl::OptionNameCmp */
69
70bool
71Acl::OptionNameCmp::operator()(const OptionName a, const OptionName b) const
72{
73 return strcmp(a, b) < 0;
74}
75
76/* Acl::OptionExtractor */
77
78const SBuf &
79Acl::OptionExtractor::value() const
80{
81 Must(hasValue);
82 return value_;
83}
84
85bool
86Acl::OptionExtractor::extractOne()
87{
88 if (!prefix_.isEmpty()) {
89 extractShort(); // continue with the previously extracted flags
90 return true;
91 }
92
93 if (!advance())
94 return false; // end of options (and, possibly, the whole "acl" directive)
95
96 if (prefix_.length() < 2)
97 throw TexcHere(ToSBuf("truncated(?) ACL flag: ", prefix_)); // single - or +
98
99 if (prefix_[0] == '-' && prefix_[1] == '-') {
100 if (prefix_.length() == 2)
101 return false; // skipped "--", an explicit end-of-options marker
102 extractWhole();
103 return true;
104 }
105
106 if (prefix_.length() == 2) { // common trivial case: -x or +y
107 extractWhole();
108 return true;
109 }
110
111 // -xyz or +xyz
112 letterPos_ = 1;
113 extractShort();
114 return true;
115}
116
117/// extracts a token with the next option/flag(s) or returns false
118bool
119Acl::OptionExtractor::advance()
120{
121 const char *next = ConfigParser::PeekAtToken();
122 if (!next)
123 return false; // end of the "acl" line
124
125 const char nextChar = *next;
126 if (!(nextChar == '-' || nextChar == '+'))
127 return false; // start of ACL parameters
128
129 sawValue_ = strchr(next, '='); // TODO: Make ConfigParser reject '^=.*' tokens
130 if (sawValue_) {
131 char *rawPrefix = nullptr;
132 char *rawValue = nullptr;
133 if (!ConfigParser::NextKvPair(rawPrefix, rawValue))
134 throw TexcHere(ToSBuf("Malformed acl option=value: ", next));
135 prefix_.assign(rawPrefix);
136 value_.assign(rawValue);
137 } else {
138 prefix_.assign(next);
139 ConfigParser::NextToken(); // consume what we have peeked at
140 }
141 return true;
142}
143
144/// handles -x[=option] or --foo[=option]
145void
146Acl::OptionExtractor::extractWhole()
147{
148 debugs(28, 8, "from " << prefix_ << " value: " << sawValue_);
149 hasValue = sawValue_;
150 name = prefix_;
151 prefix_.clear();
152}
153
154/// handles one flag letter inside an -xyx[=option] or +xyz[=option] sequence
155void
156Acl::OptionExtractor::extractShort()
157{
158 debugs(28, 8, "from " << prefix_ << " at " << letterPos_ << " value: " << sawValue_);
159 name.assign(prefix_.rawContent(), 1); // leading - or +
160 name.append(prefix_.at(letterPos_++));
161 if (letterPos_ >= prefix_.length()) { // got last flag in the sequence
162 hasValue = sawValue_;
163 prefix_.clear();
164 } else {
165 hasValue = false;
166 }
167}
168
169/* Acl::OptionsParser */
170
171// being "static" is an optimization to avoid paying for vector creation/growth
172Acl::OptionsParser::Names Acl::OptionsParser::flagsToSkip;
173
174Acl::OptionsParser::OptionsParser(const Options &options, const ParameterFlags &flags):
175 options_(options),
176 parameterFlags_(flags)
177{
178}
179
180const Acl::Option *
181Acl::OptionsParser::findOption(/* const */ SBuf &rawNameBuf)
182{
183 // TODO: new std::map::find() in C++14 does not require this conversion
184 const auto rawName = rawNameBuf.c_str();
185
186 const auto optionPos = options_.find(rawName);
187 if (optionPos != options_.end())
188 return optionPos->second;
189
190 const auto flagPos = parameterFlags_.find(rawName);
191 if (flagPos != parameterFlags_.end()) {
192 flagsToSkip.push_back(*flagPos); // *flagPos is permanent unlike rawName
193 return nullptr;
194 }
195
196 throw TexcHere(ToSBuf("unsupported ACL option: ", rawNameBuf));
197}
198
199void
200Acl::OptionsParser::parse()
201{
202 flagsToSkip.clear();
203
204 OptionExtractor oex;
205 while (oex.extractOne()) {
206 /* const */ auto rawName = oex.name;
207 if (const Option *optionPtr = findOption(rawName)) {
208 const Option &option = *optionPtr;
209 if (option.configured())
210 debugs(28, 7, "acl uses multiple " << rawName << " options");
211 switch (option.valueExpectation)
212 {
213 case Option::valueNone:
214 if (oex.hasValue)
215 throw TexcHere(ToSBuf("unexpected value for an ACL option: ", rawName, '=', oex.value()));
216 option.configureDefault();
217 break;
218 case Option::valueRequired:
219 if (!oex.hasValue)
220 throw TexcHere(ToSBuf("missing required value for ACL option ", rawName));
221 option.configureWith(oex.value());
222 break;
223 case Option::valueOptional:
224 if (oex.hasValue)
225 option.configureWith(oex.value());
226 else
227 option.configureDefault();
228 break;
229 }
230 }
231 // else skip supported parameter flag
232 }
233
234 /* hack: regex code wants to parse all -i and +i flags itself */
235 for (const auto name: flagsToSkip)
236 ConfigParser::TokenPutBack(name);
237}
238
239void
240Acl::ParseFlags(const Options &options, const ParameterFlags &flags)
241{
242 OptionsParser parser(options, flags);
243 parser.parse();
244}
245
246const Acl::Options &
247Acl::NoOptions()
248{
84011c48 249 static const Options none;
4eac3407
CT
250 return none;
251}
252
253const Acl::ParameterFlags &
254Acl::NoFlags()
255{
84011c48 256 static const ParameterFlags none;
4eac3407
CT
257 return none;
258}
259
260std::ostream &
261operator <<(std::ostream &os, const Acl::Option &option)
262{
263 if (option.valued()) {
264 os << '=';
265 option.print(os);
266 }
267 return os;
268}
269
270std::ostream &
271operator <<(std::ostream &os, const Acl::Options &options)
272{
273 for (const auto pos: options) {
274 assert(pos.second);
275 const auto &option = *pos.second;
276 if (option.configured())
277 os << pos.first << option;
278 }
279 // TODO: Remember "--" presence and print that delimiter when present.
280 // Detecting its need is difficult because parameter flags start with "-".
281 return os;
282}
283