]>
Commit | Line | Data |
---|---|---|
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 | ||
18 | namespace Acl { | |
19 | ||
20 | /// low-level parser that extracts but does not interpret ACL options | |
21 | class OptionExtractor | |
22 | { | |
23 | public: | |
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 | ||
33 | protected: | |
34 | bool advance(); | |
35 | void extractWhole(); | |
36 | void extractShort(); | |
37 | ||
38 | private: | |
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 | |
46 | class OptionsParser | |
47 | { | |
48 | public: | |
49 | OptionsParser(const Options &options, const ParameterFlags &flags); | |
50 | ||
51 | // fill previously supplied options container, throwing on errors | |
52 | void parse(); | |
53 | ||
54 | private: | |
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 ¶meterFlags_; ///< caller-supported parameter flags | |
64 | }; | |
65 | ||
66 | } // namespace Acl | |
67 | ||
68 | /* Acl::OptionNameCmp */ | |
69 | ||
70 | bool | |
71 | Acl::OptionNameCmp::operator()(const OptionName a, const OptionName b) const | |
72 | { | |
73 | return strcmp(a, b) < 0; | |
74 | } | |
75 | ||
76 | /* Acl::OptionExtractor */ | |
77 | ||
78 | const SBuf & | |
79 | Acl::OptionExtractor::value() const | |
80 | { | |
81 | Must(hasValue); | |
82 | return value_; | |
83 | } | |
84 | ||
85 | bool | |
86 | Acl::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 | |
118 | bool | |
119 | Acl::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] | |
145 | void | |
146 | Acl::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 | |
155 | void | |
156 | Acl::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 | |
172 | Acl::OptionsParser::Names Acl::OptionsParser::flagsToSkip; | |
173 | ||
174 | Acl::OptionsParser::OptionsParser(const Options &options, const ParameterFlags &flags): | |
175 | options_(options), | |
176 | parameterFlags_(flags) | |
177 | { | |
178 | } | |
179 | ||
180 | const Acl::Option * | |
181 | Acl::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 | ||
199 | void | |
200 | Acl::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 | ||
239 | void | |
240 | Acl::ParseFlags(const Options &options, const ParameterFlags &flags) | |
241 | { | |
242 | OptionsParser parser(options, flags); | |
243 | parser.parse(); | |
244 | } | |
245 | ||
246 | const Acl::Options & | |
247 | Acl::NoOptions() | |
248 | { | |
84011c48 | 249 | static const Options none; |
4eac3407 CT |
250 | return none; |
251 | } | |
252 | ||
253 | const Acl::ParameterFlags & | |
254 | Acl::NoFlags() | |
255 | { | |
84011c48 | 256 | static const ParameterFlags none; |
4eac3407 CT |
257 | return none; |
258 | } | |
259 | ||
260 | std::ostream & | |
261 | operator <<(std::ostream &os, const Acl::Option &option) | |
262 | { | |
263 | if (option.valued()) { | |
264 | os << '='; | |
265 | option.print(os); | |
266 | } | |
267 | return os; | |
268 | } | |
269 | ||
270 | std::ostream & | |
271 | operator <<(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 |