1 /** @file
2 
3   Configuration of Forwarded HTTP header option.
4 
5   @section license License
6 
7   Licensed to the Apache Software Foundation (ASF) under one
8   or more contributor license agreements.  See the NOTICE file
9   distributed with this work for additional information
10   regarding copyright ownership.  The ASF licenses this file
11   to you under the Apache License, Version 2.0 (the
12   "License"); you may not use this file except in compliance
13   with the License.  You may obtain a copy of the License at
14 
15       http://www.apache.org/licenses/LICENSE-2.0
16 
17   Unless required by applicable law or agreed to in writing, software
18   distributed under the License is distributed on an "AS IS" BASIS,
19   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20   See the License for the specific language governing permissions and
21   limitations under the License.
22  */
23 
24 #include <bitset>
25 #include <string>
26 #include <cctype>
27 
28 #include <string_view>
29 #include "tscpp/util/TextView.h"
30 
31 #include <HttpConfig.h>
32 
33 namespace
34 {
35 class BadOptionsErrMsg
36 {
37 public:
38   // Construct with reference to string that will contain error message.
39   //
BadOptionsErrMsg(ts::FixedBufferWriter & err)40   BadOptionsErrMsg(ts::FixedBufferWriter &err) : _err(err), _count(0) {}
41 
42   // Add a bad option.
43   //
44   void
add(ts::TextView badOpt)45   add(ts::TextView badOpt)
46   {
47     if (_count == 0) {
48       _err << "\"Forwarded\" configuration: ";
49       _addQuoted(badOpt);
50       _count = 1;
51     } else if (_count == 1) {
52       _saveLast = badOpt;
53       _count    = 2;
54     } else {
55       _err << ", ";
56       _addQuoted(_saveLast);
57       _saveLast = badOpt;
58       ++_count;
59     }
60   }
61 
62   // Returns true it error seen.
63   //
64   bool
done()65   done()
66   {
67     if (_count == 0) {
68       return false;
69     }
70 
71     if (_count == 1) {
72       _err << " is a bad option.";
73 
74     } else if (_count != 0) {
75       _err << " and ";
76       _addQuoted(_saveLast);
77       _err << " are bad options.";
78     }
79     return true;
80   }
81 
82 private:
83   void
_addQuoted(ts::TextView sv)84   _addQuoted(ts::TextView sv)
85   {
86     _err << '\"' << sv << '\"';
87   }
88 
89   ts::FixedBufferWriter &_err;
90 
91   ts::TextView _saveLast;
92 
93   int _count;
94 };
95 
96 // Compare a TextView to a nul-termimated string, converting the TextView to lower case and ignoring whitespace in it.
97 //
98 bool
eqIgnoreCaseWs(ts::TextView sv,const char * target)99 eqIgnoreCaseWs(ts::TextView sv, const char *target)
100 {
101   const char *s = sv.data();
102 
103   std::size_t skip = 0;
104   std::size_t i    = 0;
105 
106   while ((i + skip) < sv.size()) {
107     if (std::isspace(s[i + skip])) {
108       ++skip;
109     } else if (std::tolower(s[i + skip]) != target[i]) {
110       return false;
111     } else {
112       ++i;
113     }
114   }
115 
116   return target[i] == '\0';
117 }
118 
119 } // end anonymous namespace
120 
121 namespace HttpForwarded
122 {
123 OptionBitSet
optStrToBitset(std::string_view optConfigStr,ts::FixedBufferWriter & error)124 optStrToBitset(std::string_view optConfigStr, ts::FixedBufferWriter &error)
125 {
126   const ts::TextView Delimiters(":|");
127 
128   OptionBitSet optBS;
129 
130   // Convert to TS TextView to be able to use parsing members.
131   //
132   ts::TextView oCS{optConfigStr};
133 
134   if (eqIgnoreCaseWs(oCS, "none")) {
135     return OptionBitSet();
136   }
137 
138   BadOptionsErrMsg em(error);
139 
140   do {
141     ts::TextView optStr = oCS.take_prefix_at(Delimiters);
142 
143     if (eqIgnoreCaseWs(optStr, "for")) {
144       optBS.set(FOR);
145 
146     } else if (eqIgnoreCaseWs(optStr, "by=ip")) {
147       optBS.set(BY_IP);
148 
149     } else if (eqIgnoreCaseWs(optStr, "by=unknown")) {
150       optBS.set(BY_UNKNOWN);
151 
152     } else if (eqIgnoreCaseWs(optStr, "by=servername")) {
153       optBS.set(BY_SERVER_NAME);
154 
155     } else if (eqIgnoreCaseWs(optStr, "by=uuid")) {
156       optBS.set(BY_UUID);
157 
158     } else if (eqIgnoreCaseWs(optStr, "proto")) {
159       optBS.set(PROTO);
160 
161     } else if (eqIgnoreCaseWs(optStr, "host")) {
162       optBS.set(HOST);
163 
164     } else if (eqIgnoreCaseWs(optStr, "connection=compact")) {
165       optBS.set(CONNECTION_COMPACT);
166 
167     } else if (eqIgnoreCaseWs(optStr, "connection=std")) {
168       optBS.set(CONNECTION_STD);
169 
170     } else if (eqIgnoreCaseWs(optStr, "connection=standard")) {
171       optBS.set(CONNECTION_STD);
172 
173     } else if (eqIgnoreCaseWs(optStr, "connection=full")) {
174       optBS.set(CONNECTION_FULL);
175 
176     } else {
177       em.add(optStr);
178     }
179   } while (oCS);
180 
181   if (em.done()) {
182     return OptionBitSet();
183   }
184 
185   return optBS;
186 
187 } // end optStrToBitset()
188 
189 } // end namespace HttpForwarded
190