1 /** @file
2
3 A brief file description
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 "tscore/ink_platform.h"
25 #include "tscore/ink_args.h"
26 #include "tscore/I_Version.h"
27 #include "mgmtapi.h"
28 #include <cstdio>
29 #include <cstring>
30 #include <iostream>
31 #include <string_view>
32 #include "tscore/Regex.h"
33
34 /// XXX Use DFA or Regex wrappers?
35 #ifdef HAVE_PCRE_PCRE_H
36 #include <pcre/pcre.h>
37 #else
38 #include <pcre.h>
39 #endif
40
41 #define SUBSTRING_VECTOR_COUNT 30 // Should be multiple of 3
42
43 static AppVersionInfo appVersionInfo;
44
45 struct VIA {
VIAVIA46 VIA(const char *t) : title(t) {}
~VIAVIA47 ~VIA() { delete next; }
48 const char *title;
49 const char *viaData[128] = {}; // zero initialize
50 VIA *next = nullptr;
51 };
52
53 // Function to get via header table for every field/category in the via header
54 static VIA *
detailViaLookup(char flag)55 detailViaLookup(char flag)
56 {
57 VIA *viaTable;
58
59 // Detailed via codes after ":"
60 switch (flag) {
61 case 't':
62 viaTable = new VIA("Tunnel info");
63 viaTable->viaData[static_cast<unsigned char>(' ')] = "no tunneling";
64 viaTable->viaData[static_cast<unsigned char>('U')] = "tunneling because of url (url suggests dynamic content)";
65 viaTable->viaData[static_cast<unsigned char>('M')] = "tunneling due to a method (e.g. CONNECT)";
66 viaTable->viaData[static_cast<unsigned char>('O')] = "tunneling because cache is turned off";
67 viaTable->viaData[static_cast<unsigned char>('F')] = "tunneling due to a header field (such as presence of If-Range header)";
68 viaTable->viaData[static_cast<unsigned char>('N')] = "tunneling due to no forward";
69 viaTable->viaData[static_cast<unsigned char>('A')] = "tunnel authorization";
70 break;
71 case 'c':
72 // Cache type
73 viaTable = new VIA("Cache Type");
74 viaTable->viaData[static_cast<unsigned char>('C')] = "cache";
75 viaTable->viaData[static_cast<unsigned char>('L')] = "cluster, (not used)";
76 viaTable->viaData[static_cast<unsigned char>('P')] = "parent";
77 viaTable->viaData[static_cast<unsigned char>('S')] = "server";
78 viaTable->viaData[static_cast<unsigned char>(' ')] = "unknown";
79
80 // Cache Lookup Result
81 viaTable->next = new VIA("Cache Lookup Result");
82 viaTable->next->viaData[static_cast<unsigned char>('C')] = "cache hit but config forces revalidate";
83 viaTable->next->viaData[static_cast<unsigned char>('I')] =
84 "conditional miss (client sent conditional, fresh in cache, returned 412)";
85 viaTable->next->viaData[static_cast<unsigned char>(' ')] = "cache miss or no cache lookup";
86 viaTable->next->viaData[static_cast<unsigned char>('U')] = "cache hit, but client forces revalidate (e.g. Pragma: no-cache)";
87 viaTable->next->viaData[static_cast<unsigned char>('D')] = "cache hit, but method forces revalidated (e.g. ftp, not anonymous)";
88 viaTable->next->viaData[static_cast<unsigned char>('M')] = "cache miss (url not in cache)";
89 viaTable->next->viaData[static_cast<unsigned char>('N')] =
90 "conditional hit (client sent conditional, doc fresh in cache, returned 304)";
91 viaTable->next->viaData[static_cast<unsigned char>('H')] = "cache hit";
92 viaTable->next->viaData[static_cast<unsigned char>('S')] = "cache hit, but expired";
93 viaTable->next->viaData[static_cast<unsigned char>('K')] = "cookie miss";
94 break;
95 case 'p':
96 viaTable = new VIA("Parent proxy connection status");
97 viaTable->viaData[static_cast<unsigned char>(' ')] = "no parent proxy or unknown";
98 viaTable->viaData[static_cast<unsigned char>('S')] = "connection opened successfully";
99 viaTable->viaData[static_cast<unsigned char>('F')] = "connection open failed";
100 break;
101 case 's':
102 viaTable = new VIA("Origin server connection status");
103 viaTable->viaData[static_cast<unsigned char>(' ')] = "no server connection needed";
104 viaTable->viaData[static_cast<unsigned char>('S')] = "connection opened successfully";
105 viaTable->viaData[static_cast<unsigned char>('F')] = "connection open failed";
106 break;
107 default:
108 viaTable = nullptr;
109 fprintf(stderr, "%s: %s: %c\n", appVersionInfo.AppStr, "Invalid VIA header character", flag);
110 break;
111 }
112
113 return viaTable;
114 }
115
116 // Function to get via header table for every field/category in the via header
117 static VIA *
standardViaLookup(char flag)118 standardViaLookup(char flag)
119 {
120 VIA *viaTable;
121
122 // Via codes before ":"
123 switch (flag) {
124 case 'u':
125 viaTable = new VIA("Request headers received from client");
126 viaTable->viaData[static_cast<unsigned char>('C')] = "cookie";
127 viaTable->viaData[static_cast<unsigned char>('E')] = "error in request";
128 viaTable->viaData[static_cast<unsigned char>('S')] = "simple request (not conditional)";
129 viaTable->viaData[static_cast<unsigned char>('N')] = "no-cache";
130 viaTable->viaData[static_cast<unsigned char>('I')] = "IMS";
131 viaTable->viaData[static_cast<unsigned char>(' ')] = "unknown";
132 break;
133 case 'c':
134 viaTable = new VIA("Result of Traffic Server cache lookup for URL");
135 viaTable->viaData[static_cast<unsigned char>('A')] = "in cache, not acceptable (a cache \"MISS\")";
136 viaTable->viaData[static_cast<unsigned char>('H')] = "in cache, fresh (a cache \"HIT\")";
137 viaTable->viaData[static_cast<unsigned char>('S')] = "in cache, stale (a cache \"MISS\")";
138 viaTable->viaData[static_cast<unsigned char>('R')] = "in cache, fresh Ram hit (a cache \"HIT\")";
139 viaTable->viaData[static_cast<unsigned char>('M')] = "miss (a cache \"MISS\")";
140 viaTable->viaData[static_cast<unsigned char>(' ')] = "no cache lookup";
141 break;
142 case 's':
143 viaTable = new VIA("Response information received from origin server");
144 viaTable->viaData[static_cast<unsigned char>('E')] = "error in response";
145 viaTable->viaData[static_cast<unsigned char>('S')] = "connection opened successfully";
146 viaTable->viaData[static_cast<unsigned char>('N')] = "not-modified";
147 viaTable->viaData[static_cast<unsigned char>(' ')] = "no server connection needed";
148 break;
149 case 'f':
150 viaTable = new VIA("Result of document write-to-cache:");
151 viaTable->viaData[static_cast<unsigned char>('U')] = "updated old cache copy";
152 viaTable->viaData[static_cast<unsigned char>('D')] = "cached copy deleted";
153 viaTable->viaData[static_cast<unsigned char>('W')] = "written into cache (new copy)";
154 viaTable->viaData[static_cast<unsigned char>(' ')] = "no cache write performed";
155 break;
156 case 'p':
157 viaTable = new VIA("Proxy operation result");
158 viaTable->viaData[static_cast<unsigned char>('R')] = "origin server revalidated";
159 viaTable->viaData[static_cast<unsigned char>(' ')] = "unknown";
160 viaTable->viaData[static_cast<unsigned char>('S')] = "served or connection opened successfully";
161 viaTable->viaData[static_cast<unsigned char>('N')] = "not-modified";
162 break;
163 case 'e':
164 viaTable = new VIA("Error codes (if any)");
165 viaTable->viaData[static_cast<unsigned char>('A')] = "authorization failure";
166 viaTable->viaData[static_cast<unsigned char>('H')] = "header syntax unacceptable";
167 viaTable->viaData[static_cast<unsigned char>('C')] = "connection to server failed";
168 viaTable->viaData[static_cast<unsigned char>('T')] = "connection timed out";
169 viaTable->viaData[static_cast<unsigned char>('S')] = "server related error";
170 viaTable->viaData[static_cast<unsigned char>('D')] = "dns failure";
171 viaTable->viaData[static_cast<unsigned char>('N')] = "no error";
172 viaTable->viaData[static_cast<unsigned char>('F')] = "request forbidden";
173 viaTable->viaData[static_cast<unsigned char>('R')] = "cache read error";
174 viaTable->viaData[static_cast<unsigned char>('M')] = "moved temporarily";
175 viaTable->viaData[static_cast<unsigned char>('L')] = "looped detected";
176 viaTable->viaData[static_cast<unsigned char>(' ')] = "unknown";
177 break;
178 default:
179 viaTable = nullptr;
180 fprintf(stderr, "%s: %s: %c\n", appVersionInfo.AppStr, "Invalid VIA header character", flag);
181 break;
182 }
183
184 return viaTable;
185 }
186
187 // Function to print via header
188 static void
printViaHeader(std::string_view header)189 printViaHeader(std::string_view header)
190 {
191 VIA *viaTable = nullptr;
192 VIA *viaEntry = nullptr;
193 bool isDetail = false;
194
195 printf("Via Header Details:\n");
196
197 // Loop through input via header flags
198 for (char c : header) {
199 if ((c == ':') || (c == ';')) {
200 isDetail = true;
201 continue;
202 }
203
204 if (islower(c)) {
205 // Get the via header table
206 delete viaTable;
207 viaEntry = viaTable = isDetail ? detailViaLookup(c) : standardViaLookup(c);
208 } else {
209 // This is a one of the sequence of (uppercase) VIA codes.
210 if (viaEntry) {
211 unsigned char idx = c;
212 printf("%-55s:", viaEntry->title);
213 printf("%s\n", viaEntry->viaData[idx] ? viaEntry->viaData[idx] : "Invalid sequence");
214 viaEntry = viaEntry->next;
215 }
216 }
217 }
218 delete viaTable;
219 }
220
221 // Check validity of via header and then decode it
222 static TSMgmtError
decodeViaHeader(std::string_view text)223 decodeViaHeader(std::string_view text)
224 {
225 // Via header inside square brackets
226 if (!text.empty() && text.front() == '[' && text.back() == ']') {
227 text.remove_prefix(1);
228 text.remove_suffix(1);
229 }
230 if (text.empty()) {
231 return TS_ERR_FAIL;
232 }
233
234 printf("Via header is [%.*s], Length is %zu\n", int(text.size()), text.data(), text.size());
235
236 char extender[6];
237 if (text.size() == 5) { // add a trailing space in this case.
238 memcpy(extender, text.data(), text.size());
239 extender[5] = ' ';
240 text = std::string_view{extender, 6};
241 }
242
243 if (text.size() == 22 || text.size() == 6) {
244 // Decode via header
245 printViaHeader(text);
246 return TS_ERR_OKAY;
247 }
248 // Invalid header size, come out.
249 printf("\nInvalid VIA header. VIA header length should be 6 or 22 characters\n");
250 printf("Valid via header format is "
251 "[u<client-stuff>c<cache-lookup-stuff>s<server-stuff>f<cache-fill-stuff>p<proxy-stuff>e<error-codes>:t<tunneling-info>c<"
252 "cache type><cache-lookup-result>p<parent-proxy-conn-info>s<server-conn-info>]\n");
253 return TS_ERR_FAIL;
254 }
255
256 // Read user input from stdin
257 static TSMgmtError
filterViaHeader()258 filterViaHeader()
259 {
260 const pcre *compiledReg;
261 const pcre_extra *extraReg = nullptr;
262 int subStringVector[SUBSTRING_VECTOR_COUNT];
263 const char *err;
264 int errOffset;
265 int pcreExecCode;
266 int i;
267 const char *viaPattern =
268 R"(\[([ucsfpe]+[^\]]+)\])"; // Regex to match via header with in [] which can start with character class ucsfpe
269 std::string line;
270
271 // Compile PCRE via header pattern
272 compiledReg = pcre_compile(viaPattern, 0, &err, &errOffset, nullptr);
273
274 if (compiledReg == nullptr) {
275 printf("PCRE regex compilation failed with error %s at offset %d\n", err, errOffset);
276 return TS_ERR_FAIL;
277 }
278
279 // Read all lines from stdin
280 while (std::getline(std::cin, line)) {
281 // Match for via header pattern
282 pcreExecCode = pcre_exec(compiledReg, extraReg, line.data(), line.size(), 0, 0, subStringVector, SUBSTRING_VECTOR_COUNT);
283
284 // Match failed, don't worry. Continue to next line.
285 if (pcreExecCode < 0) {
286 continue;
287 }
288
289 // Match successful, but too many substrings
290 if (pcreExecCode == 0) {
291 pcreExecCode = SUBSTRING_VECTOR_COUNT / 3;
292 printf("Too many substrings were found. %d substrings couldn't fit into subStringVector\n", pcreExecCode - 1);
293 }
294
295 // Loop based on number of matches found
296 for (i = 1; i < pcreExecCode; i++) {
297 std::string_view match{line.data() + subStringVector[2 * i], size_t(subStringVector[2 * i + 1] - subStringVector[2 * i])};
298 // Decode matched substring
299 decodeViaHeader(match);
300 }
301 }
302 return TS_ERR_OKAY;
303 }
304
305 int
main(int,const char ** argv)306 main(int /* argc ATS_UNUSED */, const char **argv)
307 {
308 TSMgmtError status;
309
310 // build the application information structure
311 appVersionInfo.setup(PACKAGE_NAME, "traffic_via", PACKAGE_VERSION, __DATE__, __TIME__, BUILD_MACHINE, BUILD_PERSON, "");
312
313 /* see 'ink_args.h' for meanings of the various fields */
314 ArgumentDescription argument_descriptions[] = {
315 VERSION_ARGUMENT_DESCRIPTION(),
316 HELP_ARGUMENT_DESCRIPTION(),
317 };
318
319 process_args(&appVersionInfo, argument_descriptions, countof(argument_descriptions), argv);
320
321 for (unsigned i = 0; i < n_file_arguments; ++i) {
322 if (strcmp(file_arguments[i], "-") == 0) {
323 // Filter arguments provided from stdin
324 status = filterViaHeader();
325 } else {
326 status = decodeViaHeader(std::string_view{file_arguments[i], strlen(file_arguments[i])});
327 }
328
329 if (status != TS_ERR_OKAY) {
330 return 1;
331 }
332 }
333
334 return 0;
335 }
336